OILS / core / test_lib.py View on Github | oils.pub

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