| 1 | #!/usr/bin/env python2
|
| 2 | # Copyright 2016 Andy Chu. All rights reserved.
|
| 3 | # Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 | # you may not use this file except in compliance with the License.
|
| 5 | # You may obtain a copy of the License at
|
| 6 | #
|
| 7 | # http://www.apache.org/licenses/LICENSE-2.0
|
| 8 | """test_lib.py - Helper functions for testing"""
|
| 9 |
|
| 10 | import string
|
| 11 | import sys
|
| 12 |
|
| 13 | from _devbuild.gen.option_asdl import builtin_i, option_i
|
| 14 | from _devbuild.gen.runtime_asdl import cmd_value, scope_e
|
| 15 | from _devbuild.gen.syntax_asdl import loc, source, SourceLine, Token
|
| 16 | from _devbuild.gen.value_asdl import value, Obj
|
| 17 | from asdl import pybase
|
| 18 | from builtin import assign_osh
|
| 19 | from builtin import completion_osh
|
| 20 | from builtin import hay_ysh
|
| 21 | from builtin import io_osh
|
| 22 | from builtin import private_ysh
|
| 23 | from builtin import pure_osh
|
| 24 | from builtin import readline_osh
|
| 25 | from builtin import trap_osh
|
| 26 | from core import alloc
|
| 27 | from core import completion
|
| 28 | from core import dev
|
| 29 | from core import executor
|
| 30 | from core import main_loop
|
| 31 | from core import optview
|
| 32 | from core import process
|
| 33 | from core import pyutil
|
| 34 | from core import sh_init
|
| 35 | from core import state
|
| 36 | from display import ui
|
| 37 | from core import util
|
| 38 | from core import vm
|
| 39 | from frontend import lexer
|
| 40 | from frontend import location
|
| 41 | from frontend import parse_lib
|
| 42 | from frontend import reader
|
| 43 | from osh import cmd_eval
|
| 44 | from osh import prompt
|
| 45 | from osh import sh_expr_eval
|
| 46 | from osh import split
|
| 47 | from osh import word_eval
|
| 48 | from ysh import expr_eval
|
| 49 | from mycpp import iolib
|
| 50 | from mycpp import mylib
|
| 51 |
|
| 52 | import posix_ as posix
|
| 53 |
|
| 54 |
|
| 55 | def MakeMem(arena):
|
| 56 | return state.Mem('', [], arena, [], {})
|
| 57 |
|
| 58 |
|
| 59 | def MakeBuiltinArgv(argv):
|
| 60 | return cmd_value.Argv(argv, [loc.Missing] * len(argv), False, None, None)
|
| 61 |
|
| 62 |
|
| 63 | def FakeTok(id_, val):
|
| 64 | # type: (int, str) -> Token
|
| 65 | src = source.Interactive
|
| 66 | line = SourceLine(1, val, src)
|
| 67 | return Token(id_, len(val), 0, line, None)
|
| 68 |
|
| 69 |
|
| 70 | def PrintableString(s):
|
| 71 | """For pretty-printing in tests."""
|
| 72 | if all(c in string.printable for c in s):
|
| 73 | return s
|
| 74 | return repr(s)
|
| 75 |
|
| 76 |
|
| 77 | def TokensEqual(left, right):
|
| 78 | # Ignoring location in CompoundObj.__eq__ now, but we might want this later.
|
| 79 |
|
| 80 | if left.id != right.id:
|
| 81 | return False
|
| 82 |
|
| 83 | if left.line is not None:
|
| 84 | left_str = lexer.TokenVal(left)
|
| 85 | else:
|
| 86 | left_str = None
|
| 87 |
|
| 88 | if right.line is not None:
|
| 89 | right_str = lexer.TokenVal(right)
|
| 90 | else:
|
| 91 | right_str = None
|
| 92 |
|
| 93 | # Better error message sometimes:
|
| 94 | #assert left_str == right_str, '%r != %r' % (left_str, right_str)
|
| 95 | return left_str == right_str
|
| 96 |
|
| 97 |
|
| 98 | def TokenWordsEqual(left, right):
|
| 99 | # Ignoring location in CompoundObj.__eq__ now, but we might want this later.
|
| 100 | return TokensEqual(left.token, right.token)
|
| 101 | #return left == right
|
| 102 |
|
| 103 |
|
| 104 | def AsdlEqual(left, right):
|
| 105 | """Check if generated ASDL instances are equal.
|
| 106 |
|
| 107 | We don't use equality in the actual code, so this is relegated to
|
| 108 | test_lib.
|
| 109 | """
|
| 110 | if left is None and right is None:
|
| 111 | return True
|
| 112 |
|
| 113 | if isinstance(left, (int, str, bool, pybase.SimpleObj)):
|
| 114 | return left == right
|
| 115 |
|
| 116 | if isinstance(left, list):
|
| 117 | if len(left) != len(right):
|
| 118 | return False
|
| 119 | for a, b in zip(left, right):
|
| 120 | if not AsdlEqual(a, b):
|
| 121 | return False
|
| 122 | return True
|
| 123 |
|
| 124 | if isinstance(left, pybase.CompoundObj):
|
| 125 | if left.tag() != right.tag():
|
| 126 | return False
|
| 127 |
|
| 128 | field_names = left.__slots__ # hack for now
|
| 129 | for name in field_names:
|
| 130 | # Special case: we are not testing locations right now.
|
| 131 | if name == 'span_id':
|
| 132 | continue
|
| 133 | a = getattr(left, name)
|
| 134 | b = getattr(right, name)
|
| 135 | if not AsdlEqual(a, b):
|
| 136 | return False
|
| 137 |
|
| 138 | return True
|
| 139 |
|
| 140 | raise AssertionError(left)
|
| 141 |
|
| 142 |
|
| 143 | def AssertAsdlEqual(test, left, right):
|
| 144 | test.assertTrue(AsdlEqual(left, right),
|
| 145 | 'Expected %s, got %s' % (left, right))
|
| 146 |
|
| 147 |
|
| 148 | def MakeArena(source_name):
|
| 149 | arena = alloc.Arena(save_tokens=True)
|
| 150 | arena.PushSource(source.MainFile(source_name))
|
| 151 | return arena
|
| 152 |
|
| 153 |
|
| 154 | def InitLineLexer(s, arena):
|
| 155 | line_lexer = lexer.LineLexer(arena)
|
| 156 | src = source.Interactive
|
| 157 | line_lexer.Reset(SourceLine(1, s, src), 0)
|
| 158 | return line_lexer
|
| 159 |
|
| 160 |
|
| 161 | def InitLexer(s, arena):
|
| 162 | """For tests only."""
|
| 163 | line_lexer = lexer.LineLexer(arena)
|
| 164 | line_reader = reader.StringLineReader(s, arena)
|
| 165 | lx = lexer.Lexer(line_lexer, line_reader)
|
| 166 | return line_reader, lx
|
| 167 |
|
| 168 |
|
| 169 | def InitWordEvaluator(exec_opts=None):
|
| 170 | arena = MakeArena('<InitWordEvaluator>')
|
| 171 | mem = MakeMem(arena)
|
| 172 |
|
| 173 | if exec_opts is None:
|
| 174 | parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
|
| 175 | mem.exec_opts = exec_opts # circular dep
|
| 176 | sh_init.InitDefaultVars(mem, [])
|
| 177 | mutable_opts.InitFromEnv('')
|
| 178 | else:
|
| 179 | mutable_opts = None
|
| 180 |
|
| 181 | cmd_deps = cmd_eval.Deps()
|
| 182 | cmd_deps.trap_nodes = []
|
| 183 |
|
| 184 | splitter = split.SplitContext(mem)
|
| 185 | errfmt = ui.ErrorFormatter()
|
| 186 |
|
| 187 | tilde_ev = word_eval.TildeEvaluator(mem, exec_opts)
|
| 188 | ev = word_eval.CompletionWordEvaluator(mem, exec_opts, mutable_opts,
|
| 189 | tilde_ev, splitter, errfmt)
|
| 190 | return ev
|
| 191 |
|
| 192 |
|
| 193 | def InitCommandEvaluator(parse_ctx=None,
|
| 194 | comp_lookup=None,
|
| 195 | arena=None,
|
| 196 | mem=None,
|
| 197 | aliases=None,
|
| 198 | ext_prog=None):
|
| 199 |
|
| 200 | opt0_array = state.InitOpts()
|
| 201 | opt_stacks = [None] * option_i.ARRAY_SIZE
|
| 202 | if parse_ctx:
|
| 203 | arena = parse_ctx.arena
|
| 204 | else:
|
| 205 | parse_ctx = InitParseContext()
|
| 206 |
|
| 207 | mem = mem or MakeMem(arena)
|
| 208 | exec_opts = optview.Exec(opt0_array, opt_stacks)
|
| 209 | mutable_opts = state.MutableOpts(mem, {}, opt0_array, opt_stacks, None)
|
| 210 | mem.exec_opts = exec_opts
|
| 211 | #state.InitMem(mem, {}, '0.1')
|
| 212 | sh_init.InitDefaultVars(mem, [])
|
| 213 | mutable_opts.InitFromEnv('')
|
| 214 |
|
| 215 | # No 'readline' in the tests.
|
| 216 |
|
| 217 | errfmt = ui.ErrorFormatter()
|
| 218 | job_control = process.JobControl()
|
| 219 | job_list = process.JobList()
|
| 220 | fd_state = process.FdState(errfmt, job_control, job_list, None, None, None,
|
| 221 | exec_opts)
|
| 222 | aliases = {} if aliases is None else aliases
|
| 223 | procs = state.Procs(mem)
|
| 224 | methods = {}
|
| 225 |
|
| 226 | compopt_state = completion.OptionState()
|
| 227 | comp_lookup = comp_lookup or completion.Lookup()
|
| 228 |
|
| 229 | readline = None # simulate not having it
|
| 230 |
|
| 231 | arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, mutable_opts,
|
| 232 | parse_ctx, errfmt)
|
| 233 | new_var = assign_osh.NewVar(mem, procs, exec_opts, arith_ev, errfmt)
|
| 234 | assign_builtins = {
|
| 235 | builtin_i.declare: new_var,
|
| 236 | builtin_i.typeset: new_var,
|
| 237 | builtin_i.local: new_var,
|
| 238 | builtin_i.export_: assign_osh.Export(mem, arith_ev, errfmt),
|
| 239 | builtin_i.readonly: assign_osh.Readonly(mem, arith_ev, errfmt),
|
| 240 | }
|
| 241 | builtins = { # Lookup
|
| 242 | builtin_i.cat: private_ysh.Cat(errfmt),
|
| 243 | builtin_i.echo: io_osh.Echo(exec_opts),
|
| 244 | builtin_i.shift: assign_osh.Shift(mem),
|
| 245 |
|
| 246 | builtin_i.history: readline_osh.History(
|
| 247 | readline,
|
| 248 | mem,
|
| 249 | errfmt,
|
| 250 | mylib.Stdout(),
|
| 251 | ),
|
| 252 |
|
| 253 | builtin_i.compopt: completion_osh.CompOpt(compopt_state, errfmt),
|
| 254 | builtin_i.compadjust: completion_osh.CompAdjust(mem),
|
| 255 |
|
| 256 | builtin_i.alias: pure_osh.Alias(aliases, errfmt),
|
| 257 | builtin_i.unalias: pure_osh.UnAlias(aliases, errfmt),
|
| 258 | }
|
| 259 |
|
| 260 | debug_f = util.DebugFile(sys.stderr)
|
| 261 | cmd_deps = cmd_eval.Deps()
|
| 262 | cmd_deps.mutable_opts = mutable_opts
|
| 263 |
|
| 264 | # Set $PATH, to make core/process_test.py more realistic
|
| 265 | state.SetGlobalString(mem, 'PATH', ':'.join(executor.DEFAULT_PATH))
|
| 266 | search_path = executor.SearchPath(mem, exec_opts)
|
| 267 |
|
| 268 | ext_prog = \
|
| 269 | ext_prog or process.ExternalProgram('', fd_state, errfmt, debug_f)
|
| 270 |
|
| 271 | cmd_deps.dumper = dev.CrashDumper('', fd_state)
|
| 272 | cmd_deps.debug_f = debug_f
|
| 273 |
|
| 274 | splitter = split.SplitContext(mem)
|
| 275 |
|
| 276 | bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, mutable_opts,
|
| 277 | parse_ctx, errfmt)
|
| 278 | expr_ev = expr_eval.ExprEvaluator(mem, mutable_opts, methods, splitter,
|
| 279 | errfmt)
|
| 280 | tilde_ev = word_eval.TildeEvaluator(mem, exec_opts)
|
| 281 | word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, mutable_opts,
|
| 282 | tilde_ev, splitter, errfmt)
|
| 283 | signal_safe = iolib.InitSignalSafe()
|
| 284 | trap_state = trap_osh.TrapState(signal_safe)
|
| 285 |
|
| 286 | multi_trace = dev.MultiTracer(posix.getpid(), '', '', '', fd_state)
|
| 287 | tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, debug_f,
|
| 288 | multi_trace)
|
| 289 | waiter = process.Waiter(job_list, exec_opts, trap_state, tracer)
|
| 290 |
|
| 291 | cmd_deps.cflow_builtin = cmd_eval.ControlFlowBuiltin(
|
| 292 | mem, exec_opts, tracer, errfmt)
|
| 293 |
|
| 294 | cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs,
|
| 295 | assign_builtins, arena, cmd_deps,
|
| 296 | trap_state, signal_safe)
|
| 297 |
|
| 298 | hay_state = hay_ysh.HayState()
|
| 299 | shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs,
|
| 300 | hay_state, builtins, tracer, errfmt,
|
| 301 | search_path, ext_prog, waiter,
|
| 302 | job_control, job_list, fd_state,
|
| 303 | trap_state)
|
| 304 | pure_ex = executor.PureExecutor(mem, exec_opts, mutable_opts, procs,
|
| 305 | hay_state, builtins, tracer, errfmt)
|
| 306 |
|
| 307 | assert cmd_ev.mutable_opts is not None, cmd_ev
|
| 308 | prompt_ev = prompt.Evaluator('osh', '0.0.0', parse_ctx, mem)
|
| 309 |
|
| 310 | global_io = Obj(None, None)
|
| 311 | vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex,
|
| 312 | pure_ex, prompt_ev, global_io, tracer)
|
| 313 |
|
| 314 | try:
|
| 315 | from _devbuild.gen.help_meta import TOPICS
|
| 316 | except ImportError:
|
| 317 | TOPICS = None # minimal dev build
|
| 318 | spec_builder = completion_osh.SpecBuilder(cmd_ev, parse_ctx, word_ev,
|
| 319 | splitter, comp_lookup, TOPICS,
|
| 320 | errfmt)
|
| 321 |
|
| 322 | # Add some builtins that depend on the executor!
|
| 323 | complete_builtin = completion_osh.Complete(spec_builder, comp_lookup)
|
| 324 | builtins[builtin_i.complete] = complete_builtin
|
| 325 | builtins[builtin_i.compgen] = completion_osh.CompGen(spec_builder)
|
| 326 |
|
| 327 | return cmd_ev
|
| 328 |
|
| 329 |
|
| 330 | def EvalCode(code_str, parse_ctx, comp_lookup=None, mem=None, aliases=None):
|
| 331 | """Unit tests can evaluate code strings and then use the resulting
|
| 332 | CommandEvaluator."""
|
| 333 | arena = parse_ctx.arena
|
| 334 | errfmt = ui.ErrorFormatter()
|
| 335 |
|
| 336 | comp_lookup = comp_lookup or completion.Lookup()
|
| 337 | mem = mem or MakeMem(arena)
|
| 338 | parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
|
| 339 | mem.exec_opts = exec_opts
|
| 340 |
|
| 341 | #state.InitMem(mem, {}, '0.1')
|
| 342 | sh_init.InitDefaultVars(mem, [])
|
| 343 | mutable_opts.InitFromEnv('')
|
| 344 |
|
| 345 | line_reader, _ = InitLexer(code_str, arena)
|
| 346 | c_parser = parse_ctx.MakeOshParser(line_reader)
|
| 347 |
|
| 348 | cmd_ev = InitCommandEvaluator(parse_ctx=parse_ctx,
|
| 349 | comp_lookup=comp_lookup,
|
| 350 | arena=arena,
|
| 351 | mem=mem,
|
| 352 | aliases=aliases)
|
| 353 |
|
| 354 | main_loop.Batch(cmd_ev, c_parser, errfmt) # Parse and execute!
|
| 355 | return cmd_ev
|
| 356 |
|
| 357 |
|
| 358 | def InitParseContext(arena=None,
|
| 359 | ysh_grammar=None,
|
| 360 | aliases=None,
|
| 361 | parse_opts=None,
|
| 362 | do_lossless=False):
|
| 363 | arena = arena or MakeArena('<test_lib>')
|
| 364 |
|
| 365 | if aliases is None:
|
| 366 | aliases = {}
|
| 367 |
|
| 368 | mem = MakeMem(arena)
|
| 369 | if parse_opts is None:
|
| 370 | parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
|
| 371 |
|
| 372 | parse_ctx = parse_lib.ParseContext(arena,
|
| 373 | parse_opts,
|
| 374 | aliases,
|
| 375 | ysh_grammar,
|
| 376 | do_lossless=do_lossless)
|
| 377 |
|
| 378 | return parse_ctx
|
| 379 |
|
| 380 |
|
| 381 | def InitWordParser(word_str, oil_at=False, arena=None):
|
| 382 | arena = arena or MakeArena('<test_lib>')
|
| 383 |
|
| 384 | mem = MakeMem(arena)
|
| 385 | parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
|
| 386 |
|
| 387 | # CUSTOM SETTING
|
| 388 | mutable_opts.opt0_array[option_i.parse_at] = oil_at
|
| 389 |
|
| 390 | loader = pyutil.GetResourceLoader()
|
| 391 | ysh_grammar = pyutil.LoadYshGrammar(loader)
|
| 392 | parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, ysh_grammar)
|
| 393 | line_reader, _ = InitLexer(word_str, arena)
|
| 394 | c_parser = parse_ctx.MakeOshParser(line_reader)
|
| 395 | # Hack
|
| 396 | return c_parser.w_parser
|
| 397 |
|
| 398 |
|
| 399 | def InitCommandParser(code_str, arena=None):
|
| 400 | arena = arena or MakeArena('<test_lib>')
|
| 401 |
|
| 402 | loader = pyutil.GetResourceLoader()
|
| 403 | ysh_grammar = pyutil.LoadYshGrammar(loader)
|
| 404 |
|
| 405 | parse_ctx = InitParseContext(arena=arena, ysh_grammar=ysh_grammar)
|
| 406 | line_reader, _ = InitLexer(code_str, arena)
|
| 407 | c_parser = parse_ctx.MakeOshParser(line_reader)
|
| 408 | return c_parser
|
| 409 |
|
| 410 |
|
| 411 | def SetLocalString(mem, name, s):
|
| 412 | # type: (state.Mem, str, str) -> None
|
| 413 | """Bind a local string."""
|
| 414 | assert isinstance(s, str)
|
| 415 | mem.SetNamed(location.LName(name), value.Str(s), scope_e.LocalOnly)
|