185 lines
6.1 KiB
Python
185 lines
6.1 KiB
Python
"""
|
|
Test Lua API wrapper
|
|
"""
|
|
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
import subprocess
|
|
|
|
def to_string(b):
|
|
"""Return the parameter as type 'str', possibly encoding it.
|
|
|
|
In Python2, the 'str' type is the same as 'bytes'. In Python3, the
|
|
'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is
|
|
distinct.
|
|
|
|
"""
|
|
if isinstance(b, str):
|
|
# In Python2, this branch is taken for types 'str' and 'bytes'.
|
|
# In Python3, this branch is taken only for 'str'.
|
|
return b
|
|
if isinstance(b, bytes):
|
|
# In Python2, this branch is never taken ('bytes' is handled as 'str').
|
|
# In Python3, this is true only for 'bytes'.
|
|
try:
|
|
return b.decode('utf-8')
|
|
except UnicodeDecodeError:
|
|
# If the value is not valid Unicode, return the default
|
|
# repr-line encoding.
|
|
return str(b)
|
|
|
|
# By this point, here's what we *don't* have:
|
|
#
|
|
# - In Python2:
|
|
# - 'str' or 'bytes' (1st branch above)
|
|
# - In Python3:
|
|
# - 'str' (1st branch above)
|
|
# - 'bytes' (2nd branch above)
|
|
#
|
|
# The last type we might expect is the Python2 'unicode' type. There is no
|
|
# 'unicode' type in Python3 (all the Python3 cases were already handled). In
|
|
# order to get a 'str' object, we need to encode the 'unicode' object.
|
|
try:
|
|
return b.encode('utf-8')
|
|
except AttributeError:
|
|
raise TypeError('not sure how to convert %s to %s' % (type(b), str))
|
|
|
|
class ExecuteCommandTimeoutException(Exception):
|
|
def __init__(self, msg, out, err, exitCode):
|
|
assert isinstance(msg, str)
|
|
assert isinstance(out, str)
|
|
assert isinstance(err, str)
|
|
assert isinstance(exitCode, int)
|
|
self.msg = msg
|
|
self.out = out
|
|
self.err = err
|
|
self.exitCode = exitCode
|
|
|
|
|
|
# Close extra file handles on UNIX (on Windows this cannot be done while
|
|
# also redirecting input).
|
|
kUseCloseFDs = not (platform.system() == 'Windows')
|
|
|
|
|
|
def executeCommand(command, cwd=None, env=None, input=None, timeout=0):
|
|
"""Execute command ``command`` (list of arguments or string) with.
|
|
|
|
* working directory ``cwd`` (str), use None to use the current
|
|
working directory
|
|
* environment ``env`` (dict), use None for none
|
|
* Input to the command ``input`` (str), use string to pass
|
|
no input.
|
|
* Max execution time ``timeout`` (int) seconds. Use 0 for no timeout.
|
|
|
|
Returns a tuple (out, err, exitCode) where
|
|
* ``out`` (str) is the standard output of running the command
|
|
* ``err`` (str) is the standard error of running the command
|
|
* ``exitCode`` (int) is the exitCode of running the command
|
|
|
|
If the timeout is hit an ``ExecuteCommandTimeoutException``
|
|
is raised.
|
|
|
|
"""
|
|
if input is not None:
|
|
input = to_bytes(input)
|
|
p = subprocess.Popen(command, cwd=cwd,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
env=env, close_fds=kUseCloseFDs)
|
|
timerObject = None
|
|
# FIXME: Because of the way nested function scopes work in Python 2.x we
|
|
# need to use a reference to a mutable object rather than a plain
|
|
# bool. In Python 3 we could use the "nonlocal" keyword but we need
|
|
# to support Python 2 as well.
|
|
hitTimeOut = [False]
|
|
try:
|
|
if timeout > 0:
|
|
def killProcess():
|
|
# We may be invoking a shell so we need to kill the
|
|
# process and all its children.
|
|
hitTimeOut[0] = True
|
|
killProcessAndChildren(p.pid)
|
|
|
|
timerObject = threading.Timer(timeout, killProcess)
|
|
timerObject.start()
|
|
|
|
out, err = p.communicate(input=input)
|
|
exitCode = p.wait()
|
|
finally:
|
|
if timerObject != None:
|
|
timerObject.cancel()
|
|
|
|
# Ensure the resulting output is always of string type.
|
|
out = to_string(out)
|
|
err = to_string(err)
|
|
|
|
if hitTimeOut[0]:
|
|
raise ExecuteCommandTimeoutException(
|
|
msg='Reached timeout of {} seconds'.format(timeout),
|
|
out=out,
|
|
err=err,
|
|
exitCode=exitCode
|
|
)
|
|
|
|
# Detect Ctrl-C in subprocess.
|
|
if exitCode == -signal.SIGINT:
|
|
raise KeyboardInterrupt
|
|
|
|
return out, err, exitCode
|
|
|
|
class TestLuaAPI(TestBase):
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
def get_tests(self):
|
|
tests = []
|
|
for filename in os.listdir():
|
|
# Ignore dot files and excluded tests.
|
|
if filename.startswith('.'):
|
|
continue
|
|
|
|
# Ignore files that don't start with 'Test'.
|
|
if not filename.startswith('Test'):
|
|
continue
|
|
|
|
if not os.path.isdir(filename):
|
|
base, ext = os.path.splitext(filename)
|
|
if ext == '.lua':
|
|
tests.append(filename)
|
|
return tests
|
|
|
|
def test_lua_api(self):
|
|
if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0:
|
|
self.skipTest("Lua API tests could not find Lua executable.")
|
|
return
|
|
lua_executable = os.environ["LUA_EXECUTABLE"]
|
|
|
|
self.build()
|
|
test_exe = self.getBuildArtifact("a.out")
|
|
test_output = self.getBuildArtifact("output")
|
|
test_input = self.getBuildArtifact("input")
|
|
|
|
lua_lldb_cpath = "%s/lua/5.3/?.so" % configuration.lldb_libs_dir
|
|
|
|
lua_prelude = "package.cpath = '%s;' .. package.cpath" % lua_lldb_cpath
|
|
|
|
lua_env = {
|
|
"TEST_EXE": os.path.join(self.getBuildDir(), test_exe),
|
|
"TEST_OUTPUT": os.path.join(self.getBuildDir(), test_output),
|
|
"TEST_INPUT": os.path.join(self.getBuildDir(), test_input)
|
|
}
|
|
|
|
for lua_test in self.get_tests():
|
|
cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test]
|
|
out, err, exitCode = executeCommand(cmd, env=lua_env)
|
|
|
|
# Redirect Lua output
|
|
print(out)
|
|
print(err, file=sys.stderr)
|
|
|
|
self.assertTrue(
|
|
exitCode == 0,
|
|
"Lua test '%s' failure." % lua_test
|
|
)
|