| 1 | #!/usr/bin/env python2
 | 
| 2 | """state_test.py: Tests for state.py."""
 | 
| 3 | 
 | 
| 4 | import unittest
 | 
| 5 | import os.path
 | 
| 6 | 
 | 
| 7 | from _devbuild.gen.id_kind_asdl import Id
 | 
| 8 | from _devbuild.gen.runtime_asdl import scope_e
 | 
| 9 | from _devbuild.gen.syntax_asdl import source, SourceLine
 | 
| 10 | from _devbuild.gen.value_asdl import (value, value_e, sh_lvalue)
 | 
| 11 | from asdl import runtime
 | 
| 12 | from core import error
 | 
| 13 | from core import test_lib
 | 
| 14 | from core import state  # module under test
 | 
| 15 | from frontend import lexer
 | 
| 16 | from frontend import location
 | 
| 17 | from mycpp.mylib import NewDict
 | 
| 18 | 
 | 
| 19 | 
 | 
| 20 | def _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 | 
 | 
| 35 | class 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 Oil?
 | 
| 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 | 
 | 
| 351 | if __name__ == '__main__':
 | 
| 352 |     unittest.main()
 |