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