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