167f94e5aSSiger Yang""" 267f94e5aSSiger YangTest Lua API wrapper 367f94e5aSSiger Yang""" 467f94e5aSSiger Yang 567f94e5aSSiger Yangfrom lldbsuite.test.decorators import * 667f94e5aSSiger Yangfrom lldbsuite.test.lldbtest import * 767f94e5aSSiger Yangfrom lldbsuite.test import lldbutil 867f94e5aSSiger Yangimport subprocess 967f94e5aSSiger Yang 102238dcc3SJonas Devlieghere 1167f94e5aSSiger Yangdef to_string(b): 1267f94e5aSSiger Yang """Return the parameter as type 'str', possibly encoding it. 1367f94e5aSSiger Yang 1467f94e5aSSiger Yang In Python2, the 'str' type is the same as 'bytes'. In Python3, the 1567f94e5aSSiger Yang 'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is 1667f94e5aSSiger Yang distinct. 1767f94e5aSSiger Yang 1867f94e5aSSiger Yang """ 1967f94e5aSSiger Yang if isinstance(b, str): 2067f94e5aSSiger Yang # In Python2, this branch is taken for types 'str' and 'bytes'. 2167f94e5aSSiger Yang # In Python3, this branch is taken only for 'str'. 2267f94e5aSSiger Yang return b 2367f94e5aSSiger Yang if isinstance(b, bytes): 2467f94e5aSSiger Yang # In Python2, this branch is never taken ('bytes' is handled as 'str'). 2567f94e5aSSiger Yang # In Python3, this is true only for 'bytes'. 2667f94e5aSSiger Yang try: 272238dcc3SJonas Devlieghere return b.decode("utf-8") 2867f94e5aSSiger Yang except UnicodeDecodeError: 2967f94e5aSSiger Yang # If the value is not valid Unicode, return the default 3067f94e5aSSiger Yang # repr-line encoding. 3167f94e5aSSiger Yang return str(b) 3267f94e5aSSiger Yang 3367f94e5aSSiger Yang # By this point, here's what we *don't* have: 3467f94e5aSSiger Yang # 3567f94e5aSSiger Yang # - In Python2: 3667f94e5aSSiger Yang # - 'str' or 'bytes' (1st branch above) 3767f94e5aSSiger Yang # - In Python3: 3867f94e5aSSiger Yang # - 'str' (1st branch above) 3967f94e5aSSiger Yang # - 'bytes' (2nd branch above) 4067f94e5aSSiger Yang # 4167f94e5aSSiger Yang # The last type we might expect is the Python2 'unicode' type. There is no 4267f94e5aSSiger Yang # 'unicode' type in Python3 (all the Python3 cases were already handled). In 4367f94e5aSSiger Yang # order to get a 'str' object, we need to encode the 'unicode' object. 4467f94e5aSSiger Yang try: 452238dcc3SJonas Devlieghere return b.encode("utf-8") 4667f94e5aSSiger Yang except AttributeError: 472238dcc3SJonas Devlieghere raise TypeError("not sure how to convert %s to %s" % (type(b), str)) 482238dcc3SJonas Devlieghere 4967f94e5aSSiger Yang 5067f94e5aSSiger Yangclass ExecuteCommandTimeoutException(Exception): 5167f94e5aSSiger Yang def __init__(self, msg, out, err, exitCode): 5267f94e5aSSiger Yang assert isinstance(msg, str) 5367f94e5aSSiger Yang assert isinstance(out, str) 5467f94e5aSSiger Yang assert isinstance(err, str) 5567f94e5aSSiger Yang assert isinstance(exitCode, int) 5667f94e5aSSiger Yang self.msg = msg 5767f94e5aSSiger Yang self.out = out 5867f94e5aSSiger Yang self.err = err 5967f94e5aSSiger Yang self.exitCode = exitCode 6067f94e5aSSiger Yang 6167f94e5aSSiger Yang 6267f94e5aSSiger Yang# Close extra file handles on UNIX (on Windows this cannot be done while 6367f94e5aSSiger Yang# also redirecting input). 642238dcc3SJonas DevliegherekUseCloseFDs = not (platform.system() == "Windows") 6567f94e5aSSiger Yang 6667f94e5aSSiger Yang 6767f94e5aSSiger Yangdef executeCommand(command, cwd=None, env=None, input=None, timeout=0): 6867f94e5aSSiger Yang """Execute command ``command`` (list of arguments or string) with. 6967f94e5aSSiger Yang 7067f94e5aSSiger Yang * working directory ``cwd`` (str), use None to use the current 7167f94e5aSSiger Yang working directory 7267f94e5aSSiger Yang * environment ``env`` (dict), use None for none 7367f94e5aSSiger Yang * Input to the command ``input`` (str), use string to pass 7467f94e5aSSiger Yang no input. 7567f94e5aSSiger Yang * Max execution time ``timeout`` (int) seconds. Use 0 for no timeout. 7667f94e5aSSiger Yang 7767f94e5aSSiger Yang Returns a tuple (out, err, exitCode) where 7867f94e5aSSiger Yang * ``out`` (str) is the standard output of running the command 7967f94e5aSSiger Yang * ``err`` (str) is the standard error of running the command 8067f94e5aSSiger Yang * ``exitCode`` (int) is the exitCode of running the command 8167f94e5aSSiger Yang 8267f94e5aSSiger Yang If the timeout is hit an ``ExecuteCommandTimeoutException`` 8367f94e5aSSiger Yang is raised. 8467f94e5aSSiger Yang 8567f94e5aSSiger Yang """ 8667f94e5aSSiger Yang if input is not None: 8767f94e5aSSiger Yang input = to_bytes(input) 882238dcc3SJonas Devlieghere p = subprocess.Popen( 892238dcc3SJonas Devlieghere command, 902238dcc3SJonas Devlieghere cwd=cwd, 9167f94e5aSSiger Yang stdin=subprocess.PIPE, 9267f94e5aSSiger Yang stdout=subprocess.PIPE, 9367f94e5aSSiger Yang stderr=subprocess.PIPE, 942238dcc3SJonas Devlieghere env=env, 952238dcc3SJonas Devlieghere close_fds=kUseCloseFDs, 962238dcc3SJonas Devlieghere ) 9767f94e5aSSiger Yang timerObject = None 9867f94e5aSSiger Yang # FIXME: Because of the way nested function scopes work in Python 2.x we 9967f94e5aSSiger Yang # need to use a reference to a mutable object rather than a plain 10067f94e5aSSiger Yang # bool. In Python 3 we could use the "nonlocal" keyword but we need 10167f94e5aSSiger Yang # to support Python 2 as well. 10267f94e5aSSiger Yang hitTimeOut = [False] 10367f94e5aSSiger Yang try: 10467f94e5aSSiger Yang if timeout > 0: 1052238dcc3SJonas Devlieghere 10667f94e5aSSiger Yang def killProcess(): 10767f94e5aSSiger Yang # We may be invoking a shell so we need to kill the 10867f94e5aSSiger Yang # process and all its children. 10967f94e5aSSiger Yang hitTimeOut[0] = True 11067f94e5aSSiger Yang killProcessAndChildren(p.pid) 11167f94e5aSSiger Yang 11267f94e5aSSiger Yang timerObject = threading.Timer(timeout, killProcess) 11367f94e5aSSiger Yang timerObject.start() 11467f94e5aSSiger Yang 11567f94e5aSSiger Yang out, err = p.communicate(input=input) 11667f94e5aSSiger Yang exitCode = p.wait() 11767f94e5aSSiger Yang finally: 11858611451SEisuke Kawashima if timerObject is not None: 11967f94e5aSSiger Yang timerObject.cancel() 12067f94e5aSSiger Yang 12167f94e5aSSiger Yang # Ensure the resulting output is always of string type. 12267f94e5aSSiger Yang out = to_string(out) 12367f94e5aSSiger Yang err = to_string(err) 12467f94e5aSSiger Yang 12567f94e5aSSiger Yang if hitTimeOut[0]: 12667f94e5aSSiger Yang raise ExecuteCommandTimeoutException( 1272238dcc3SJonas Devlieghere msg="Reached timeout of {} seconds".format(timeout), 12867f94e5aSSiger Yang out=out, 12967f94e5aSSiger Yang err=err, 1302238dcc3SJonas Devlieghere exitCode=exitCode, 13167f94e5aSSiger Yang ) 13267f94e5aSSiger Yang 13367f94e5aSSiger Yang # Detect Ctrl-C in subprocess. 13467f94e5aSSiger Yang if exitCode == -signal.SIGINT: 13567f94e5aSSiger Yang raise KeyboardInterrupt 13667f94e5aSSiger Yang 13767f94e5aSSiger Yang return out, err, exitCode 13867f94e5aSSiger Yang 1392238dcc3SJonas Devlieghere 14067f94e5aSSiger Yangclass TestLuaAPI(TestBase): 14167f94e5aSSiger Yang NO_DEBUG_INFO_TESTCASE = True 14267f94e5aSSiger Yang 14367f94e5aSSiger Yang def get_tests(self): 14467f94e5aSSiger Yang tests = [] 14567f94e5aSSiger Yang for filename in os.listdir(): 14667f94e5aSSiger Yang # Ignore dot files and excluded tests. 1472238dcc3SJonas Devlieghere if filename.startswith("."): 14867f94e5aSSiger Yang continue 14967f94e5aSSiger Yang 15067f94e5aSSiger Yang # Ignore files that don't start with 'Test'. 1512238dcc3SJonas Devlieghere if not filename.startswith("Test"): 15267f94e5aSSiger Yang continue 15367f94e5aSSiger Yang 15467f94e5aSSiger Yang if not os.path.isdir(filename): 15567f94e5aSSiger Yang base, ext = os.path.splitext(filename) 1562238dcc3SJonas Devlieghere if ext == ".lua": 15767f94e5aSSiger Yang tests.append(filename) 15867f94e5aSSiger Yang return tests 15967f94e5aSSiger Yang 16067f94e5aSSiger Yang def test_lua_api(self): 16167f94e5aSSiger Yang if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0: 16267f94e5aSSiger Yang self.skipTest("Lua API tests could not find Lua executable.") 16367f94e5aSSiger Yang return 16467f94e5aSSiger Yang lua_executable = os.environ["LUA_EXECUTABLE"] 165*e19d7401SJonas Devlieghere lldb_lua_cpath = os.environ["LLDB_LUA_CPATH"] 16667f94e5aSSiger Yang 16767f94e5aSSiger Yang self.build() 16867f94e5aSSiger Yang test_exe = self.getBuildArtifact("a.out") 16967f94e5aSSiger Yang test_output = self.getBuildArtifact("output") 17067f94e5aSSiger Yang test_input = self.getBuildArtifact("input") 17167f94e5aSSiger Yang 172*e19d7401SJonas Devlieghere lua_prelude = "package.cpath = '%s/?.so;' .. package.cpath" % lldb_lua_cpath 17367f94e5aSSiger Yang 17467f94e5aSSiger Yang lua_env = { 17567f94e5aSSiger Yang "TEST_EXE": os.path.join(self.getBuildDir(), test_exe), 17667f94e5aSSiger Yang "TEST_OUTPUT": os.path.join(self.getBuildDir(), test_output), 1772238dcc3SJonas Devlieghere "TEST_INPUT": os.path.join(self.getBuildDir(), test_input), 17867f94e5aSSiger Yang } 17967f94e5aSSiger Yang 18067f94e5aSSiger Yang for lua_test in self.get_tests(): 18167f94e5aSSiger Yang cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test] 18267f94e5aSSiger Yang out, err, exitCode = executeCommand(cmd, env=lua_env) 18367f94e5aSSiger Yang 18467f94e5aSSiger Yang # Redirect Lua output 18567f94e5aSSiger Yang print(out) 18667f94e5aSSiger Yang print(err, file=sys.stderr) 18767f94e5aSSiger Yang 1889c246882SJordan Rupprecht self.assertEqual(exitCode, 0, "Lua test '%s' failure." % lua_test) 189