1*67f94e5aSSiger Yang"""
2*67f94e5aSSiger YangTest Lua API wrapper
3*67f94e5aSSiger Yang"""
4*67f94e5aSSiger Yang
5*67f94e5aSSiger Yangfrom lldbsuite.test.decorators import *
6*67f94e5aSSiger Yangfrom lldbsuite.test.lldbtest import *
7*67f94e5aSSiger Yangfrom lldbsuite.test import lldbutil
8*67f94e5aSSiger Yangimport subprocess
9*67f94e5aSSiger Yang
10*67f94e5aSSiger Yangdef to_string(b):
11*67f94e5aSSiger Yang    """Return the parameter as type 'str', possibly encoding it.
12*67f94e5aSSiger Yang
13*67f94e5aSSiger Yang    In Python2, the 'str' type is the same as 'bytes'. In Python3, the
14*67f94e5aSSiger Yang    'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is
15*67f94e5aSSiger Yang    distinct.
16*67f94e5aSSiger Yang
17*67f94e5aSSiger Yang    """
18*67f94e5aSSiger Yang    if isinstance(b, str):
19*67f94e5aSSiger Yang        # In Python2, this branch is taken for types 'str' and 'bytes'.
20*67f94e5aSSiger Yang        # In Python3, this branch is taken only for 'str'.
21*67f94e5aSSiger Yang        return b
22*67f94e5aSSiger Yang    if isinstance(b, bytes):
23*67f94e5aSSiger Yang        # In Python2, this branch is never taken ('bytes' is handled as 'str').
24*67f94e5aSSiger Yang        # In Python3, this is true only for 'bytes'.
25*67f94e5aSSiger Yang        try:
26*67f94e5aSSiger Yang            return b.decode('utf-8')
27*67f94e5aSSiger Yang        except UnicodeDecodeError:
28*67f94e5aSSiger Yang            # If the value is not valid Unicode, return the default
29*67f94e5aSSiger Yang            # repr-line encoding.
30*67f94e5aSSiger Yang            return str(b)
31*67f94e5aSSiger Yang
32*67f94e5aSSiger Yang    # By this point, here's what we *don't* have:
33*67f94e5aSSiger Yang    #
34*67f94e5aSSiger Yang    #  - In Python2:
35*67f94e5aSSiger Yang    #    - 'str' or 'bytes' (1st branch above)
36*67f94e5aSSiger Yang    #  - In Python3:
37*67f94e5aSSiger Yang    #    - 'str' (1st branch above)
38*67f94e5aSSiger Yang    #    - 'bytes' (2nd branch above)
39*67f94e5aSSiger Yang    #
40*67f94e5aSSiger Yang    # The last type we might expect is the Python2 'unicode' type. There is no
41*67f94e5aSSiger Yang    # 'unicode' type in Python3 (all the Python3 cases were already handled). In
42*67f94e5aSSiger Yang    # order to get a 'str' object, we need to encode the 'unicode' object.
43*67f94e5aSSiger Yang    try:
44*67f94e5aSSiger Yang        return b.encode('utf-8')
45*67f94e5aSSiger Yang    except AttributeError:
46*67f94e5aSSiger Yang        raise TypeError('not sure how to convert %s to %s' % (type(b), str))
47*67f94e5aSSiger Yang
48*67f94e5aSSiger Yangclass ExecuteCommandTimeoutException(Exception):
49*67f94e5aSSiger Yang    def __init__(self, msg, out, err, exitCode):
50*67f94e5aSSiger Yang        assert isinstance(msg, str)
51*67f94e5aSSiger Yang        assert isinstance(out, str)
52*67f94e5aSSiger Yang        assert isinstance(err, str)
53*67f94e5aSSiger Yang        assert isinstance(exitCode, int)
54*67f94e5aSSiger Yang        self.msg = msg
55*67f94e5aSSiger Yang        self.out = out
56*67f94e5aSSiger Yang        self.err = err
57*67f94e5aSSiger Yang        self.exitCode = exitCode
58*67f94e5aSSiger Yang
59*67f94e5aSSiger Yang
60*67f94e5aSSiger Yang# Close extra file handles on UNIX (on Windows this cannot be done while
61*67f94e5aSSiger Yang# also redirecting input).
62*67f94e5aSSiger YangkUseCloseFDs = not (platform.system() == 'Windows')
63*67f94e5aSSiger Yang
64*67f94e5aSSiger Yang
65*67f94e5aSSiger Yangdef executeCommand(command, cwd=None, env=None, input=None, timeout=0):
66*67f94e5aSSiger Yang    """Execute command ``command`` (list of arguments or string) with.
67*67f94e5aSSiger Yang
68*67f94e5aSSiger Yang    * working directory ``cwd`` (str), use None to use the current
69*67f94e5aSSiger Yang    working directory
70*67f94e5aSSiger Yang    * environment ``env`` (dict), use None for none
71*67f94e5aSSiger Yang    * Input to the command ``input`` (str), use string to pass
72*67f94e5aSSiger Yang    no input.
73*67f94e5aSSiger Yang    * Max execution time ``timeout`` (int) seconds. Use 0 for no timeout.
74*67f94e5aSSiger Yang
75*67f94e5aSSiger Yang    Returns a tuple (out, err, exitCode) where
76*67f94e5aSSiger Yang    * ``out`` (str) is the standard output of running the command
77*67f94e5aSSiger Yang    * ``err`` (str) is the standard error of running the command
78*67f94e5aSSiger Yang    * ``exitCode`` (int) is the exitCode of running the command
79*67f94e5aSSiger Yang
80*67f94e5aSSiger Yang    If the timeout is hit an ``ExecuteCommandTimeoutException``
81*67f94e5aSSiger Yang    is raised.
82*67f94e5aSSiger Yang
83*67f94e5aSSiger Yang    """
84*67f94e5aSSiger Yang    if input is not None:
85*67f94e5aSSiger Yang        input = to_bytes(input)
86*67f94e5aSSiger Yang    p = subprocess.Popen(command, cwd=cwd,
87*67f94e5aSSiger Yang                        stdin=subprocess.PIPE,
88*67f94e5aSSiger Yang                        stdout=subprocess.PIPE,
89*67f94e5aSSiger Yang                        stderr=subprocess.PIPE,
90*67f94e5aSSiger Yang                        env=env, close_fds=kUseCloseFDs)
91*67f94e5aSSiger Yang    timerObject = None
92*67f94e5aSSiger Yang    # FIXME: Because of the way nested function scopes work in Python 2.x we
93*67f94e5aSSiger Yang    # need to use a reference to a mutable object rather than a plain
94*67f94e5aSSiger Yang    # bool. In Python 3 we could use the "nonlocal" keyword but we need
95*67f94e5aSSiger Yang    # to support Python 2 as well.
96*67f94e5aSSiger Yang    hitTimeOut = [False]
97*67f94e5aSSiger Yang    try:
98*67f94e5aSSiger Yang        if timeout > 0:
99*67f94e5aSSiger Yang            def killProcess():
100*67f94e5aSSiger Yang                # We may be invoking a shell so we need to kill the
101*67f94e5aSSiger Yang                # process and all its children.
102*67f94e5aSSiger Yang                hitTimeOut[0] = True
103*67f94e5aSSiger Yang                killProcessAndChildren(p.pid)
104*67f94e5aSSiger Yang
105*67f94e5aSSiger Yang            timerObject = threading.Timer(timeout, killProcess)
106*67f94e5aSSiger Yang            timerObject.start()
107*67f94e5aSSiger Yang
108*67f94e5aSSiger Yang        out, err = p.communicate(input=input)
109*67f94e5aSSiger Yang        exitCode = p.wait()
110*67f94e5aSSiger Yang    finally:
111*67f94e5aSSiger Yang        if timerObject != None:
112*67f94e5aSSiger Yang            timerObject.cancel()
113*67f94e5aSSiger Yang
114*67f94e5aSSiger Yang    # Ensure the resulting output is always of string type.
115*67f94e5aSSiger Yang    out = to_string(out)
116*67f94e5aSSiger Yang    err = to_string(err)
117*67f94e5aSSiger Yang
118*67f94e5aSSiger Yang    if hitTimeOut[0]:
119*67f94e5aSSiger Yang        raise ExecuteCommandTimeoutException(
120*67f94e5aSSiger Yang            msg='Reached timeout of {} seconds'.format(timeout),
121*67f94e5aSSiger Yang            out=out,
122*67f94e5aSSiger Yang            err=err,
123*67f94e5aSSiger Yang            exitCode=exitCode
124*67f94e5aSSiger Yang        )
125*67f94e5aSSiger Yang
126*67f94e5aSSiger Yang    # Detect Ctrl-C in subprocess.
127*67f94e5aSSiger Yang    if exitCode == -signal.SIGINT:
128*67f94e5aSSiger Yang        raise KeyboardInterrupt
129*67f94e5aSSiger Yang
130*67f94e5aSSiger Yang    return out, err, exitCode
131*67f94e5aSSiger Yang
132*67f94e5aSSiger Yangclass TestLuaAPI(TestBase):
133*67f94e5aSSiger Yang    NO_DEBUG_INFO_TESTCASE = True
134*67f94e5aSSiger Yang
135*67f94e5aSSiger Yang    def get_tests(self):
136*67f94e5aSSiger Yang        tests = []
137*67f94e5aSSiger Yang        for filename in os.listdir():
138*67f94e5aSSiger Yang            # Ignore dot files and excluded tests.
139*67f94e5aSSiger Yang            if filename.startswith('.'):
140*67f94e5aSSiger Yang                continue
141*67f94e5aSSiger Yang
142*67f94e5aSSiger Yang            # Ignore files that don't start with 'Test'.
143*67f94e5aSSiger Yang            if not filename.startswith('Test'):
144*67f94e5aSSiger Yang                continue
145*67f94e5aSSiger Yang
146*67f94e5aSSiger Yang            if not os.path.isdir(filename):
147*67f94e5aSSiger Yang                base, ext = os.path.splitext(filename)
148*67f94e5aSSiger Yang                if ext == '.lua':
149*67f94e5aSSiger Yang                    tests.append(filename)
150*67f94e5aSSiger Yang        return tests
151*67f94e5aSSiger Yang
152*67f94e5aSSiger Yang    def test_lua_api(self):
153*67f94e5aSSiger Yang        if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0:
154*67f94e5aSSiger Yang           self.skipTest("Lua API tests could not find Lua executable.")
155*67f94e5aSSiger Yang           return
156*67f94e5aSSiger Yang        lua_executable = os.environ["LUA_EXECUTABLE"]
157*67f94e5aSSiger Yang
158*67f94e5aSSiger Yang        self.build()
159*67f94e5aSSiger Yang        test_exe = self.getBuildArtifact("a.out")
160*67f94e5aSSiger Yang        test_output = self.getBuildArtifact("output")
161*67f94e5aSSiger Yang        test_input = self.getBuildArtifact("input")
162*67f94e5aSSiger Yang
163*67f94e5aSSiger Yang        lua_lldb_cpath = "%s/lua/5.3/?.so" % configuration.lldb_libs_dir
164*67f94e5aSSiger Yang
165*67f94e5aSSiger Yang        lua_prelude = "package.cpath = '%s;' .. package.cpath" % lua_lldb_cpath
166*67f94e5aSSiger Yang
167*67f94e5aSSiger Yang        lua_env = {
168*67f94e5aSSiger Yang            "TEST_EXE":     os.path.join(self.getBuildDir(), test_exe),
169*67f94e5aSSiger Yang            "TEST_OUTPUT":  os.path.join(self.getBuildDir(), test_output),
170*67f94e5aSSiger Yang            "TEST_INPUT":   os.path.join(self.getBuildDir(), test_input)
171*67f94e5aSSiger Yang        }
172*67f94e5aSSiger Yang
173*67f94e5aSSiger Yang        for lua_test in self.get_tests():
174*67f94e5aSSiger Yang            cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test]
175*67f94e5aSSiger Yang            out, err, exitCode = executeCommand(cmd, env=lua_env)
176*67f94e5aSSiger Yang
177*67f94e5aSSiger Yang            # Redirect Lua output
178*67f94e5aSSiger Yang            print(out)
179*67f94e5aSSiger Yang            print(err, file=sys.stderr)
180*67f94e5aSSiger Yang
181*67f94e5aSSiger Yang            self.assertTrue(
182*67f94e5aSSiger Yang                exitCode == 0,
183*67f94e5aSSiger Yang                "Lua test '%s' failure." % lua_test
184*67f94e5aSSiger Yang            )
185