xref: /llvm-project/lldb/test/API/lua_api/TestLuaAPI.py (revision e19d74016971faed321e5cca20da7016047eb029)
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