OILS / core / state_test.py View on Github | oilshell.org

353 lines, 237 significant
1#!/usr/bin/env python2
2"""state_test.py: Tests for state.py."""
3
4import unittest
5import os.path
6
7from _devbuild.gen.id_kind_asdl import Id
8from _devbuild.gen.runtime_asdl import scope_e
9from _devbuild.gen.syntax_asdl import source, SourceLine
10from _devbuild.gen.value_asdl import (value, value_e, sh_lvalue)
11from asdl import runtime
12from core import error
13from core import executor
14from core import test_lib
15from core import state # module under test
16from frontend import lexer
17from frontend import location
18from mycpp.mylib import NewDict
19
20
21def _InitMem():
22 # empty environment, no arena.
23 arena = test_lib.MakeArena('<state_test.py>')
24 col = 0
25 length = 1
26 line_id = arena.AddLine(1, 'foo')
27 arena.NewToken(-1, col, length, line_id)
28 mem = state.Mem('', [], arena, [])
29
30 parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
31
32 mem.exec_opts = exec_opts
33 return mem
34
35
36class MemTest(unittest.TestCase):
37
38 def _PushShellCall(self, mem, name, tok, argv):
39 """ simulate shell function """
40 mem.argv_stack.append(state._ArgFrame(argv))
41 frame = NewDict()
42 mem.var_stack.append(frame)
43 mem.PushCall(name, tok)
44
45 def _PopShellCall(self, mem):
46 """ simulate shell function """
47 mem.PopCall()
48 mem.var_stack.pop()
49 mem.argv_stack.pop()
50
51 def testGet(self):
52 mem = _InitMem()
53
54 tok_a = lexer.DummyToken(Id.Lit_Chars, 'a')
55 tok_a.line = SourceLine(1, 'a b', source.Interactive)
56
57 self._PushShellCall(mem, 'my-func', tok_a, ['a', 'b'])
58 print(mem.GetValue('HOME'))
59 self._PopShellCall(mem)
60 print(mem.GetValue('NONEXISTENT'))
61
62 def testSearchPath(self):
63 mem = _InitMem()
64 #print(mem)
65 search_path = executor.SearchPath(mem, mem.exec_opts)
66
67 # Relative path works without $PATH
68 self.assertEqual(None, search_path.LookupOne('__nonexistent__'))
69 self.assertEqual('bin/osh', search_path.LookupOne('bin/osh'))
70 self.assertEqual(None, search_path.LookupOne('grep'))
71
72 # Set $PATH
73 mem.SetValue(location.LName('PATH'), value.Str('/bin:/usr/bin'),
74 scope_e.GlobalOnly)
75
76 self.assertEqual(None, search_path.LookupOne('__nonexistent__'))
77 self.assertEqual('bin/osh', search_path.LookupOne('bin/osh'))
78
79 # Not hermetic, but should be true on POSIX systems.
80 # Also see https://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/
81 # - on some systems, /bin is a symlink to /usr/bin
82 if os.path.isfile('/bin/env'):
83 self.assertEqual(search_path.LookupOne('env'), '/bin/env')
84 else:
85 self.assertEqual(search_path.LookupOne('env'), '/usr/bin/env')
86
87 def testPushTemp(self):
88 mem = _InitMem()
89
90 # x=1
91 mem.SetValue(location.LName('x'), value.Str('1'), scope_e.Dynamic)
92 self.assertEqual('1', mem.var_stack[-1]['x'].val.s)
93
94 mem.PushTemp()
95
96 self.assertEqual(2, len(mem.var_stack))
97
98 # x=temp E=3 read x <<< 'line'
99 mem.SetValue(location.LName('x'), value.Str('temp'), scope_e.LocalOnly)
100 mem.SetValue(location.LName('E'), value.Str('3'), scope_e.LocalOnly)
101 mem.SetValue(location.LName('x'), value.Str('line'), scope_e.LocalOnly)
102
103 self.assertEqual('3', mem.var_stack[-1]['E'].val.s)
104 self.assertEqual('line', mem.var_stack[-1]['x'].val.s)
105 self.assertEqual('1', mem.var_stack[-2]['x'].val.s)
106
107 mem.PopTemp()
108 self.assertEqual(1, len(mem.var_stack))
109 self.assertEqual('1', mem.var_stack[-1]['x'].val.s)
110
111 def testSetVarClearFlag(self):
112 mem = _InitMem()
113 print(mem)
114
115 tok_one = lexer.DummyToken(Id.Lit_Chars, 'ONE')
116 tok_one.line = SourceLine(1, 'ONE', source.Interactive)
117
118 tok_two = lexer.DummyToken(Id.Lit_Chars, 'TWO')
119 tok_two.line = SourceLine(1, 'TWO', source.Interactive)
120
121 self._PushShellCall(mem, 'my-func', tok_one, ['ONE'])
122 self.assertEqual(2, len(mem.var_stack)) # internal details
123
124 # local x=y
125 mem.SetValue(location.LName('x'), value.Str('y'), scope_e.LocalOnly)
126 self.assertEqual('y', mem.var_stack[-1]['x'].val.s)
127
128 # New frame
129 self._PushShellCall(mem, 'my-func', tok_two, ['TWO'])
130 self.assertEqual(3, len(mem.var_stack)) # internal details
131
132 # x=y -- test out dynamic scope
133 mem.SetValue(location.LName('x'), value.Str('YYY'), scope_e.Dynamic)
134 self.assertEqual('YYY', mem.var_stack[-2]['x'].val.s)
135 self.assertEqual(None, mem.var_stack[-1].get('x'))
136
137 # myglobal=g
138 mem.SetValue(location.LName('myglobal'), value.Str('g'),
139 scope_e.Dynamic)
140 self.assertEqual('g', mem.var_stack[0]['myglobal'].val.s)
141 self.assertEqual(False, mem.var_stack[0]['myglobal'].exported)
142
143 # 'export PYTHONPATH=/'
144 mem.SetValue(location.LName('PYTHONPATH'),
145 value.Str('/'),
146 scope_e.Dynamic,
147 flags=state.SetExport)
148 self.assertEqual('/', mem.var_stack[0]['PYTHONPATH'].val.s)
149 self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)
150
151 cmd_ev = mem.GetExported()
152 self.assertEqual('/', cmd_ev['PYTHONPATH'])
153
154 mem.SetValue(location.LName('PYTHONPATH'),
155 None,
156 scope_e.Dynamic,
157 flags=state.SetExport)
158 self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)
159
160 # 'export myglobal'. None means don't touch it. Undef would be confusing
161 # because it might mean "unset", but we have a separated API for that.
162 mem.SetValue(location.LName('myglobal'),
163 None,
164 scope_e.Dynamic,
165 flags=state.SetExport)
166 self.assertEqual(True, mem.var_stack[0]['myglobal'].exported)
167
168 # export g2 -- define and export empty
169 mem.SetValue(location.LName('g2'),
170 None,
171 scope_e.Dynamic,
172 flags=state.SetExport)
173 self.assertEqual(value_e.Undef, mem.var_stack[0]['g2'].val.tag())
174 self.assertEqual(True, mem.var_stack[0]['g2'].exported)
175
176 # readonly myglobal
177 self.assertEqual(False, mem.var_stack[0]['myglobal'].readonly)
178 mem.SetValue(location.LName('myglobal'),
179 None,
180 scope_e.Dynamic,
181 flags=state.SetReadOnly)
182 self.assertEqual(True, mem.var_stack[0]['myglobal'].readonly)
183
184 mem.SetValue(location.LName('PYTHONPATH'), value.Str('/lib'),
185 scope_e.Dynamic)
186 self.assertEqual('/lib', mem.var_stack[0]['PYTHONPATH'].val.s)
187 self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)
188
189 # COMPREPLY=(1 2 3)
190 # invariant to enforce: arrays can't be exported
191 mem.SetValue(location.LName('COMPREPLY'),
192 value.BashArray(['1', '2', '3']), scope_e.GlobalOnly)
193 self.assertEqual(['1', '2', '3'],
194 mem.var_stack[0]['COMPREPLY'].val.strs)
195
196 # export COMPREPLY - allowed when strict_array not set
197 mem.SetValue(location.LName('COMPREPLY'),
198 None,
199 scope_e.Dynamic,
200 flags=state.SetExport)
201
202 # readonly r=1
203 mem.SetValue(location.LName('r'),
204 value.Str('1'),
205 scope_e.Dynamic,
206 flags=state.SetReadOnly)
207 self.assertEqual('1', mem.var_stack[0]['r'].val.s)
208 self.assertEqual(False, mem.var_stack[0]['r'].exported)
209 self.assertEqual(True, mem.var_stack[0]['r'].readonly)
210 print(mem)
211
212 # r=newvalue
213 try:
214 mem.SetValue(location.LName('r'), value.Str('newvalue'),
215 scope_e.Dynamic)
216 except error.FatalRuntime as e:
217 pass
218 else:
219 self.fail("Expected failure")
220
221 # readonly r2 -- define empty readonly
222 mem.SetValue(location.LName('r2'),
223 None,
224 scope_e.Dynamic,
225 flags=state.SetReadOnly)
226 self.assertEqual(value_e.Undef, mem.var_stack[0]['r2'].val.tag())
227 self.assertEqual(True, mem.var_stack[0]['r2'].readonly)
228
229 # export -n PYTHONPATH
230 # Remove the exported property. NOTE: scope is LocalOnly for YSH?
231 self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)
232 mem.ClearFlag('PYTHONPATH', state.ClearExport)
233 self.assertEqual(False, mem.var_stack[0]['PYTHONPATH'].exported)
234
235 lhs = sh_lvalue.Indexed('a', 1, runtime.NO_SPID)
236 # a[1]=2
237 mem.SetValue(lhs, value.Str('2'), scope_e.Dynamic)
238 self.assertEqual([None, '2'], mem.var_stack[0]['a'].val.strs)
239
240 # a[1]=3
241 mem.SetValue(lhs, value.Str('3'), scope_e.Dynamic)
242 self.assertEqual([None, '3'], mem.var_stack[0]['a'].val.strs)
243
244 # a[1]=(x y z) # illegal but doesn't parse anyway
245 if 0:
246 try:
247 mem.SetValue(lhs, value.BashArray(['x', 'y', 'z']),
248 scope_e.Dynamic)
249 except error.FatalRuntime as e:
250 pass
251 else:
252 self.fail("Expected failure")
253
254 # readonly a
255 mem.SetValue(location.LName('a'),
256 None,
257 scope_e.Dynamic,
258 flags=state.SetReadOnly)
259 self.assertEqual(True, mem.var_stack[0]['a'].readonly)
260
261 try:
262 # a[1]=3
263 mem.SetValue(lhs, value.Str('3'), scope_e.Dynamic)
264 except error.FatalRuntime as e:
265 pass
266 else:
267 self.fail("Expected failure")
268
269 def testGetValue(self):
270 mem = _InitMem()
271
272 # readonly a=x
273 mem.SetValue(location.LName('a'),
274 value.Str('x'),
275 scope_e.Dynamic,
276 flags=state.SetReadOnly)
277
278 val = mem.GetValue('a', scope_e.Dynamic)
279 test_lib.AssertAsdlEqual(self, value.Str('x'), val)
280
281 val = mem.GetValue('undef', scope_e.Dynamic)
282 test_lib.AssertAsdlEqual(self, value.Undef, val)
283
284 def testExportThenAssign(self):
285 """Regression Test."""
286 mem = _InitMem()
287
288 # export U
289 mem.SetValue(location.LName('U'),
290 None,
291 scope_e.Dynamic,
292 flags=state.SetExport)
293 print(mem)
294
295 # U=u
296 mem.SetValue(location.LName('U'), value.Str('u'), scope_e.Dynamic)
297 print(mem)
298 e = mem.GetExported()
299 self.assertEqual('u', e['U'])
300
301 def testUnset(self):
302 mem = _InitMem()
303 # unset a
304 mem.Unset(location.LName('a'), scope_e.Shopt)
305
306 return # not implemented yet
307
308 # unset a[1]
309 mem.Unset(sh_lvalue.Indexed('a', 1, runtime.NO_SPID), False)
310
311 def testArgv(self):
312 mem = _InitMem()
313 src = source.Interactive
314
315 tok_a = lexer.DummyToken(Id.Lit_Chars, 'a')
316 tok_a.line = SourceLine(1, 'a b', src)
317
318 self._PushShellCall(mem, 'my-func', tok_a, ['a', 'b'])
319 self.assertEqual(['a', 'b'], mem.GetArgv())
320
321 tok_x = lexer.DummyToken(Id.Lit_Chars, 'x')
322 tok_x.line = SourceLine(2, 'x y', src)
323
324 self._PushShellCall(mem, 'my-func', tok_x, ['x', 'y'])
325 self.assertEqual(['x', 'y'], mem.GetArgv())
326
327 status = mem.Shift(1)
328 self.assertEqual(['y'], mem.GetArgv())
329 self.assertEqual(0, status)
330
331 status = mem.Shift(1)
332 self.assertEqual([], mem.GetArgv())
333 self.assertEqual(0, status)
334
335 status = mem.Shift(1)
336 self.assertEqual([], mem.GetArgv())
337 self.assertEqual(1, status) # error
338
339 self._PopShellCall(mem)
340 self.assertEqual(['a', 'b'], mem.GetArgv())
341
342 def testArgv2(self):
343 mem = state.Mem('', ['x', 'y'], None, [])
344
345 mem.Shift(1)
346 self.assertEqual(['y'], mem.GetArgv())
347
348 mem.SetArgv(['i', 'j', 'k'])
349 self.assertEqual(['i', 'j', 'k'], mem.GetArgv())
350
351
352if __name__ == '__main__':
353 unittest.main()