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