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

353 lines, 237 significant
1#!/usr/bin/env python2
2import unittest
3import os.path
4
5from _devbuild.gen.id_kind_asdl import Id
6from _devbuild.gen.runtime_asdl import scope_e
7from _devbuild.gen.syntax_asdl import source, SourceLine
8from _devbuild.gen.value_asdl import (value, value_e, sh_lvalue)
9from asdl import runtime
10from core import bash_impl
11from core import error
12from core import executor
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 = test_lib.MakeMem(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 = executor.SearchPath(mem, mem.exec_opts)
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.GetEnv()
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 bash_impl.BashArray_FromList(['1', '2', '3']),
192 scope_e.GlobalOnly)
193 compreply_val = mem.var_stack[0]['COMPREPLY'].val
194 self.assertEqual(['1', '2', '3'], sorted(compreply_val.d.values()))
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(['2'], mem.var_stack[0]['a'].val.d.values())
239
240 # a[1]=3
241 mem.SetValue(lhs, value.Str('3'), scope_e.Dynamic)
242 self.assertEqual(['3'], mem.var_stack[0]['a'].val.d.values())
243
244 # a[1]=(x y z) # illegal but doesn't parse anyway
245 if 0:
246 try:
247 mem.SetValue(lhs, value.InternalStringArray(['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.GetEnv()
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()