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