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

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