xref: /llvm-project/lldb/test/API/lua_api/TestLuaAPI.py (revision 67f94e5a9745665dd62550c75cb16569fbb0059d)
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
134    mydir = TestBase.compute_mydir(__file__)
135    NO_DEBUG_INFO_TESTCASE = True
136
137    def get_tests(self):
138        tests = []
139        for filename in os.listdir():
140            # Ignore dot files and excluded tests.
141            if filename.startswith('.'):
142                continue
143
144            # Ignore files that don't start with 'Test'.
145            if not filename.startswith('Test'):
146                continue
147
148            if not os.path.isdir(filename):
149                base, ext = os.path.splitext(filename)
150                if ext == '.lua':
151                    tests.append(filename)
152        return tests
153
154    def test_lua_api(self):
155        if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0:
156           self.skipTest("Lua API tests could not find Lua executable.")
157           return
158        lua_executable = os.environ["LUA_EXECUTABLE"]
159
160        self.build()
161        test_exe = self.getBuildArtifact("a.out")
162        test_output = self.getBuildArtifact("output")
163        test_input = self.getBuildArtifact("input")
164
165        lua_lldb_cpath = "%s/lua/5.3/?.so" % configuration.lldb_libs_dir
166
167        lua_prelude = "package.cpath = '%s;' .. package.cpath" % lua_lldb_cpath
168
169        lua_env = {
170            "TEST_EXE":     os.path.join(self.getBuildDir(), test_exe),
171            "TEST_OUTPUT":  os.path.join(self.getBuildDir(), test_output),
172            "TEST_INPUT":   os.path.join(self.getBuildDir(), test_input)
173        }
174
175        for lua_test in self.get_tests():
176            cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test]
177            out, err, exitCode = executeCommand(cmd, env=lua_env)
178
179            # Redirect Lua output
180            print(out)
181            print(err, file=sys.stderr)
182
183            self.assertTrue(
184                exitCode == 0,
185                "Lua test '%s' failure." % lua_test
186            )
187