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

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