1""" 2Test Lua API wrapper 3""" 4 5from lldbsuite.test.decorators import * 6from lldbsuite.test.lldbtest import * 7from lldbsuite.test import lldbutil 8import subprocess 9 10def to_string(b): 11 """Return the parameter as type 'str', possibly encoding it. 12 13 In Python2, the 'str' type is the same as 'bytes'. In Python3, the 14 'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is 15 distinct. 16 17 """ 18 if isinstance(b, str): 19 # In Python2, this branch is taken for types 'str' and 'bytes'. 20 # In Python3, this branch is taken only for 'str'. 21 return b 22 if isinstance(b, bytes): 23 # In Python2, this branch is never taken ('bytes' is handled as 'str'). 24 # In Python3, this is true only for 'bytes'. 25 try: 26 return b.decode('utf-8') 27 except UnicodeDecodeError: 28 # If the value is not valid Unicode, return the default 29 # repr-line encoding. 30 return str(b) 31 32 # By this point, here's what we *don't* have: 33 # 34 # - In Python2: 35 # - 'str' or 'bytes' (1st branch above) 36 # - In Python3: 37 # - 'str' (1st branch above) 38 # - 'bytes' (2nd branch above) 39 # 40 # The last type we might expect is the Python2 'unicode' type. There is no 41 # 'unicode' type in Python3 (all the Python3 cases were already handled). In 42 # order to get a 'str' object, we need to encode the 'unicode' object. 43 try: 44 return b.encode('utf-8') 45 except AttributeError: 46 raise TypeError('not sure how to convert %s to %s' % (type(b), str)) 47 48class ExecuteCommandTimeoutException(Exception): 49 def __init__(self, msg, out, err, exitCode): 50 assert isinstance(msg, str) 51 assert isinstance(out, str) 52 assert isinstance(err, str) 53 assert isinstance(exitCode, int) 54 self.msg = msg 55 self.out = out 56 self.err = err 57 self.exitCode = exitCode 58 59 60# Close extra file handles on UNIX (on Windows this cannot be done while 61# also redirecting input). 62kUseCloseFDs = not (platform.system() == 'Windows') 63 64 65def executeCommand(command, cwd=None, env=None, input=None, timeout=0): 66 """Execute command ``command`` (list of arguments or string) with. 67 68 * working directory ``cwd`` (str), use None to use the current 69 working directory 70 * environment ``env`` (dict), use None for none 71 * Input to the command ``input`` (str), use string to pass 72 no input. 73 * Max execution time ``timeout`` (int) seconds. Use 0 for no timeout. 74 75 Returns a tuple (out, err, exitCode) where 76 * ``out`` (str) is the standard output of running the command 77 * ``err`` (str) is the standard error of running the command 78 * ``exitCode`` (int) is the exitCode of running the command 79 80 If the timeout is hit an ``ExecuteCommandTimeoutException`` 81 is raised. 82 83 """ 84 if input is not None: 85 input = to_bytes(input) 86 p = subprocess.Popen(command, cwd=cwd, 87 stdin=subprocess.PIPE, 88 stdout=subprocess.PIPE, 89 stderr=subprocess.PIPE, 90 env=env, close_fds=kUseCloseFDs) 91 timerObject = None 92 # FIXME: Because of the way nested function scopes work in Python 2.x we 93 # need to use a reference to a mutable object rather than a plain 94 # bool. In Python 3 we could use the "nonlocal" keyword but we need 95 # to support Python 2 as well. 96 hitTimeOut = [False] 97 try: 98 if timeout > 0: 99 def killProcess(): 100 # We may be invoking a shell so we need to kill the 101 # process and all its children. 102 hitTimeOut[0] = True 103 killProcessAndChildren(p.pid) 104 105 timerObject = threading.Timer(timeout, killProcess) 106 timerObject.start() 107 108 out, err = p.communicate(input=input) 109 exitCode = p.wait() 110 finally: 111 if timerObject != None: 112 timerObject.cancel() 113 114 # Ensure the resulting output is always of string type. 115 out = to_string(out) 116 err = to_string(err) 117 118 if hitTimeOut[0]: 119 raise ExecuteCommandTimeoutException( 120 msg='Reached timeout of {} seconds'.format(timeout), 121 out=out, 122 err=err, 123 exitCode=exitCode 124 ) 125 126 # Detect Ctrl-C in subprocess. 127 if exitCode == -signal.SIGINT: 128 raise KeyboardInterrupt 129 130 return out, err, exitCode 131 132class TestLuaAPI(TestBase): 133 NO_DEBUG_INFO_TESTCASE = True 134 135 def get_tests(self): 136 tests = [] 137 for filename in os.listdir(): 138 # Ignore dot files and excluded tests. 139 if filename.startswith('.'): 140 continue 141 142 # Ignore files that don't start with 'Test'. 143 if not filename.startswith('Test'): 144 continue 145 146 if not os.path.isdir(filename): 147 base, ext = os.path.splitext(filename) 148 if ext == '.lua': 149 tests.append(filename) 150 return tests 151 152 def test_lua_api(self): 153 if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0: 154 self.skipTest("Lua API tests could not find Lua executable.") 155 return 156 lua_executable = os.environ["LUA_EXECUTABLE"] 157 158 self.build() 159 test_exe = self.getBuildArtifact("a.out") 160 test_output = self.getBuildArtifact("output") 161 test_input = self.getBuildArtifact("input") 162 163 lua_lldb_cpath = "%s/lua/5.3/?.so" % configuration.lldb_libs_dir 164 165 lua_prelude = "package.cpath = '%s;' .. package.cpath" % lua_lldb_cpath 166 167 lua_env = { 168 "TEST_EXE": os.path.join(self.getBuildDir(), test_exe), 169 "TEST_OUTPUT": os.path.join(self.getBuildDir(), test_output), 170 "TEST_INPUT": os.path.join(self.getBuildDir(), test_input) 171 } 172 173 for lua_test in self.get_tests(): 174 cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test] 175 out, err, exitCode = executeCommand(cmd, env=lua_env) 176 177 # Redirect Lua output 178 print(out) 179 print(err, file=sys.stderr) 180 181 self.assertTrue( 182 exitCode == 0, 183 "Lua test '%s' failure." % lua_test 184 ) 185