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