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

415 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"""test_lib.py - Helper functions for testing"""
9
10import string
11import sys
12
13from _devbuild.gen.option_asdl import builtin_i, option_i
14from _devbuild.gen.runtime_asdl import cmd_value, scope_e
15from _devbuild.gen.syntax_asdl import loc, source, SourceLine, Token
16from _devbuild.gen.value_asdl import value, Obj
17from asdl import pybase
18from builtin import assign_osh
19from builtin import completion_osh
20from builtin import hay_ysh
21from builtin import io_osh
22from builtin import private_ysh
23from builtin import pure_osh
24from builtin import readline_osh
25from builtin import trap_osh
26from core import alloc
27from core import completion
28from core import dev
29from core import executor
30from core import main_loop
31from core import optview
32from core import process
33from core import pyutil
34from core import sh_init
35from core import state
36from display import ui
37from core import util
38from core import vm
39from frontend import lexer
40from frontend import location
41from frontend import parse_lib
42from frontend import reader
43from osh import cmd_eval
44from osh import prompt
45from osh import sh_expr_eval
46from osh import split
47from osh import word_eval
48from ysh import expr_eval
49from mycpp import iolib
50from mycpp import mylib
51
52import posix_ as posix
53
54
55def MakeMem(arena):
56 return state.Mem('', [], arena, [], {})
57
58
59def MakeBuiltinArgv(argv):
60 return cmd_value.Argv(argv, [loc.Missing] * len(argv), False, None, None)
61
62
63def 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
70def 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
77def 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
98def 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
104def 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
143def AssertAsdlEqual(test, left, right):
144 test.assertTrue(AsdlEqual(left, right),
145 'Expected %s, got %s' % (left, right))
146
147
148def MakeArena(source_name):
149 arena = alloc.Arena(save_tokens=True)
150 arena.PushSource(source.MainFile(source_name))
151 return arena
152
153
154def 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
161def 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
169def 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
193def 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
330def 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
358def 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
381def 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
399def 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
411def 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)