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