| """ |
| Test Lua API wrapper |
| """ |
| |
| from lldbsuite.test.decorators import * |
| from lldbsuite.test.lldbtest import * |
| from lldbsuite.test import lldbutil |
| import subprocess |
| |
| def to_string(b): |
| """Return the parameter as type 'str', possibly encoding it. |
| |
| In Python2, the 'str' type is the same as 'bytes'. In Python3, the |
| 'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is |
| distinct. |
| |
| """ |
| if isinstance(b, str): |
| # In Python2, this branch is taken for types 'str' and 'bytes'. |
| # In Python3, this branch is taken only for 'str'. |
| return b |
| if isinstance(b, bytes): |
| # In Python2, this branch is never taken ('bytes' is handled as 'str'). |
| # In Python3, this is true only for 'bytes'. |
| try: |
| return b.decode('utf-8') |
| except UnicodeDecodeError: |
| # If the value is not valid Unicode, return the default |
| # repr-line encoding. |
| return str(b) |
| |
| # By this point, here's what we *don't* have: |
| # |
| # - In Python2: |
| # - 'str' or 'bytes' (1st branch above) |
| # - In Python3: |
| # - 'str' (1st branch above) |
| # - 'bytes' (2nd branch above) |
| # |
| # The last type we might expect is the Python2 'unicode' type. There is no |
| # 'unicode' type in Python3 (all the Python3 cases were already handled). In |
| # order to get a 'str' object, we need to encode the 'unicode' object. |
| try: |
| return b.encode('utf-8') |
| except AttributeError: |
| raise TypeError('not sure how to convert %s to %s' % (type(b), str)) |
| |
| class ExecuteCommandTimeoutException(Exception): |
| def __init__(self, msg, out, err, exitCode): |
| assert isinstance(msg, str) |
| assert isinstance(out, str) |
| assert isinstance(err, str) |
| assert isinstance(exitCode, int) |
| self.msg = msg |
| self.out = out |
| self.err = err |
| self.exitCode = exitCode |
| |
| |
| # Close extra file handles on UNIX (on Windows this cannot be done while |
| # also redirecting input). |
| kUseCloseFDs = not (platform.system() == 'Windows') |
| |
| |
| def executeCommand(command, cwd=None, env=None, input=None, timeout=0): |
| """Execute command ``command`` (list of arguments or string) with. |
| |
| * working directory ``cwd`` (str), use None to use the current |
| working directory |
| * environment ``env`` (dict), use None for none |
| * Input to the command ``input`` (str), use string to pass |
| no input. |
| * Max execution time ``timeout`` (int) seconds. Use 0 for no timeout. |
| |
| Returns a tuple (out, err, exitCode) where |
| * ``out`` (str) is the standard output of running the command |
| * ``err`` (str) is the standard error of running the command |
| * ``exitCode`` (int) is the exitCode of running the command |
| |
| If the timeout is hit an ``ExecuteCommandTimeoutException`` |
| is raised. |
| |
| """ |
| if input is not None: |
| input = to_bytes(input) |
| p = subprocess.Popen(command, cwd=cwd, |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| env=env, close_fds=kUseCloseFDs) |
| timerObject = None |
| # FIXME: Because of the way nested function scopes work in Python 2.x we |
| # need to use a reference to a mutable object rather than a plain |
| # bool. In Python 3 we could use the "nonlocal" keyword but we need |
| # to support Python 2 as well. |
| hitTimeOut = [False] |
| try: |
| if timeout > 0: |
| def killProcess(): |
| # We may be invoking a shell so we need to kill the |
| # process and all its children. |
| hitTimeOut[0] = True |
| killProcessAndChildren(p.pid) |
| |
| timerObject = threading.Timer(timeout, killProcess) |
| timerObject.start() |
| |
| out, err = p.communicate(input=input) |
| exitCode = p.wait() |
| finally: |
| if timerObject != None: |
| timerObject.cancel() |
| |
| # Ensure the resulting output is always of string type. |
| out = to_string(out) |
| err = to_string(err) |
| |
| if hitTimeOut[0]: |
| raise ExecuteCommandTimeoutException( |
| msg='Reached timeout of {} seconds'.format(timeout), |
| out=out, |
| err=err, |
| exitCode=exitCode |
| ) |
| |
| # Detect Ctrl-C in subprocess. |
| if exitCode == -signal.SIGINT: |
| raise KeyboardInterrupt |
| |
| return out, err, exitCode |
| |
| class TestLuaAPI(TestBase): |
| |
| mydir = TestBase.compute_mydir(__file__) |
| NO_DEBUG_INFO_TESTCASE = True |
| |
| def get_tests(self): |
| tests = [] |
| for filename in os.listdir(): |
| # Ignore dot files and excluded tests. |
| if filename.startswith('.'): |
| continue |
| |
| # Ignore files that don't start with 'Test'. |
| if not filename.startswith('Test'): |
| continue |
| |
| if not os.path.isdir(filename): |
| base, ext = os.path.splitext(filename) |
| if ext == '.lua': |
| tests.append(filename) |
| return tests |
| |
| def test_lua_api(self): |
| if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0: |
| self.skipTest("Lua API tests could not find Lua executable.") |
| return |
| lua_executable = os.environ["LUA_EXECUTABLE"] |
| |
| self.build() |
| test_exe = self.getBuildArtifact("a.out") |
| test_output = self.getBuildArtifact("output") |
| test_input = self.getBuildArtifact("input") |
| |
| lua_lldb_cpath = "%s/lua/5.3/?.so" % configuration.lldb_libs_dir |
| |
| lua_prelude = "package.cpath = '%s;' .. package.cpath" % lua_lldb_cpath |
| |
| lua_env = { |
| "TEST_EXE": os.path.join(self.getBuildDir(), test_exe), |
| "TEST_OUTPUT": os.path.join(self.getBuildDir(), test_output), |
| "TEST_INPUT": os.path.join(self.getBuildDir(), test_input) |
| } |
| |
| for lua_test in self.get_tests(): |
| cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test] |
| out, err, exitCode = executeCommand(cmd, env=lua_env) |
| |
| # Redirect Lua output |
| print(out) |
| print(err, file=sys.stderr) |
| |
| self.assertTrue( |
| exitCode == 0, |
| "Lua test '%s' failure." % lua_test |
| ) |