forked from OSchip/llvm-project
413 lines
13 KiB
Python
Executable File
413 lines
13 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
"""
|
|
A simple testing framework for lldb using python's unit testing framework.
|
|
|
|
Tests for lldb are written as python scripts which take advantage of the script
|
|
bridging provided by LLDB.framework to interact with lldb core.
|
|
|
|
A specific naming pattern is followed by the .py script to be recognized as
|
|
a module which implements a test scenario, namely, Test*.py.
|
|
|
|
To specify the directories where "Test*.py" python test scripts are located,
|
|
you need to pass in a list of directory names. By default, the current
|
|
working directory is searched if nothing is specified on the command line.
|
|
|
|
Type:
|
|
|
|
./dotest.py -h
|
|
|
|
for available options.
|
|
"""
|
|
|
|
import os, signal, sys, time
|
|
import unittest2
|
|
|
|
class _WritelnDecorator(object):
|
|
"""Used to decorate file-like objects with a handy 'writeln' method"""
|
|
def __init__(self,stream):
|
|
self.stream = stream
|
|
|
|
def __getattr__(self, attr):
|
|
if attr in ('stream', '__getstate__'):
|
|
raise AttributeError(attr)
|
|
return getattr(self.stream,attr)
|
|
|
|
def writeln(self, arg=None):
|
|
if arg:
|
|
self.write(arg)
|
|
self.write('\n') # text-mode streams translate to \r\n if needed
|
|
|
|
#
|
|
# Global variables:
|
|
#
|
|
|
|
# The test suite.
|
|
suite = unittest2.TestSuite()
|
|
|
|
# The config file is optional.
|
|
configFile = None
|
|
|
|
# The dictionary as a result of sourcing configFile.
|
|
config = {}
|
|
|
|
# Delay startup in order for the debugger to attach.
|
|
delay = False
|
|
|
|
# Ignore the build search path relative to this script to locate the lldb.py module.
|
|
ignore = False
|
|
|
|
# The regular expression pattern to match against eligible filenames as our test cases.
|
|
regexp = None
|
|
|
|
# Default verbosity is 0.
|
|
verbose = 0
|
|
|
|
# By default, search from the current working directory.
|
|
testdirs = [ os.getcwd() ]
|
|
|
|
# Separator string.
|
|
separator = '-' * 70
|
|
|
|
|
|
def usage():
|
|
print """
|
|
Usage: dotest.py [option] [args]
|
|
where options:
|
|
-h : print this help message and exit (also --help)
|
|
-c : read a config file specified after this option
|
|
(see also lldb-trunk/example/test/usage-config)
|
|
-d : delay startup for 10 seconds (in order for the debugger to attach)
|
|
-i : ignore (don't bailout) if 'lldb.py' module cannot be located in the build
|
|
tree relative to this script; use PYTHONPATH to locate the module
|
|
-p : specify a regexp filename pattern for inclusion in the test suite
|
|
-t : trace lldb command execution and result
|
|
-v : do verbose mode of unittest framework
|
|
|
|
and:
|
|
args : specify a list of directory names to search for python Test*.py scripts
|
|
if empty, search from the curret working directory, instead
|
|
|
|
Running of this script also sets up the LLDB_TEST environment variable so that
|
|
individual test cases can locate their supporting files correctly. The script
|
|
tries to set up Python's search paths for modules by looking at the build tree
|
|
relative to this script. See also the '-i' option.
|
|
|
|
Environment variables related to loggings:
|
|
|
|
o LLDB_LOG: if defined, specifies the log file pathname for the 'lldb' subsystem
|
|
with a default option of 'event process' if LLDB_LOG_OPTION is not defined.
|
|
|
|
o GDB_REMOTE_LOG: if defined, specifies the log file pathname for the
|
|
'process.gdb-remote' subsystem with a default option of 'packets' if
|
|
GDB_REMOTE_LOG_OPTION is not defined.
|
|
"""
|
|
sys.exit(0)
|
|
|
|
|
|
def parseOptionsAndInitTestdirs():
|
|
"""Initialize the list of directories containing our unittest scripts.
|
|
|
|
'-h/--help as the first option prints out usage info and exit the program.
|
|
"""
|
|
|
|
global configFile
|
|
global delay
|
|
global ignore
|
|
global regexp
|
|
global verbose
|
|
global testdirs
|
|
|
|
if len(sys.argv) == 1:
|
|
return
|
|
|
|
# Process possible trace and/or verbose flag, among other things.
|
|
index = 1
|
|
for i in range(1, len(sys.argv)):
|
|
if not sys.argv[index].startswith('-'):
|
|
# End of option processing.
|
|
break
|
|
|
|
if sys.argv[index].find('-h') != -1:
|
|
usage()
|
|
elif sys.argv[index].startswith('-c'):
|
|
# Increment by 1 to fetch the config file name option argument.
|
|
index += 1
|
|
if index >= len(sys.argv) or sys.argv[index].startswith('-'):
|
|
usage()
|
|
configFile = sys.argv[index]
|
|
if not os.path.isfile(configFile):
|
|
print "Config file:", configFile, "does not exist!"
|
|
usage()
|
|
index += 1
|
|
elif sys.argv[index].startswith('-d'):
|
|
delay = True
|
|
index += 1
|
|
elif sys.argv[index].startswith('-i'):
|
|
ignore = True
|
|
index += 1
|
|
elif sys.argv[index].startswith('-p'):
|
|
# Increment by 1 to fetch the reg exp pattern argument.
|
|
index += 1
|
|
if index >= len(sys.argv) or sys.argv[index].startswith('-'):
|
|
usage()
|
|
regexp = sys.argv[index]
|
|
index += 1
|
|
elif sys.argv[index].startswith('-t'):
|
|
os.environ["LLDB_COMMAND_TRACE"] = "YES"
|
|
index += 1
|
|
elif sys.argv[index].startswith('-v'):
|
|
verbose = 2
|
|
index += 1
|
|
else:
|
|
print "Unknown option: ", sys.argv[index]
|
|
usage()
|
|
|
|
# Gather all the dirs passed on the command line.
|
|
if len(sys.argv) > index:
|
|
testdirs = map(os.path.abspath, sys.argv[index:])
|
|
|
|
# Source the configFile if specified.
|
|
# The side effect, if any, will be felt from this point on. An example
|
|
# config file may be these simple two lines:
|
|
#
|
|
# sys.stderr = open("/tmp/lldbtest-stderr", "w")
|
|
# sys.stdout = open("/tmp/lldbtest-stdout", "w")
|
|
#
|
|
# which will reassign the two file objects to sys.stderr and sys.stdout,
|
|
# respectively.
|
|
#
|
|
# See also lldb-trunk/example/test/usage-config.
|
|
global config
|
|
if configFile:
|
|
# Pass config (a dictionary) as the locals namespace for side-effect.
|
|
execfile(configFile, globals(), config)
|
|
#print "config:", config
|
|
#print "sys.stderr:", sys.stderr
|
|
#print "sys.stdout:", sys.stdout
|
|
|
|
|
|
def setupSysPath():
|
|
"""Add LLDB.framework/Resources/Python to the search paths for modules."""
|
|
|
|
# Get the directory containing the current script.
|
|
scriptPath = sys.path[0]
|
|
if not scriptPath.endswith('test'):
|
|
print "This script expects to reside in lldb's test directory."
|
|
sys.exit(-1)
|
|
|
|
os.environ["LLDB_TEST"] = scriptPath
|
|
pluginPath = os.path.join(scriptPath, 'plugins')
|
|
|
|
# Append script dir and plugin dir to the sys.path.
|
|
sys.path.append(scriptPath)
|
|
sys.path.append(pluginPath)
|
|
|
|
global ignore
|
|
|
|
# The '-i' option is used to skip looking for lldb.py in the build tree.
|
|
if ignore:
|
|
return
|
|
|
|
base = os.path.abspath(os.path.join(scriptPath, os.pardir))
|
|
dbgPath = os.path.join(base, 'build', 'Debug', 'LLDB.framework',
|
|
'Resources', 'Python')
|
|
relPath = os.path.join(base, 'build', 'Release', 'LLDB.framework',
|
|
'Resources', 'Python')
|
|
baiPath = os.path.join(base, 'build', 'BuildAndIntegration',
|
|
'LLDB.framework', 'Resources', 'Python')
|
|
|
|
lldbPath = None
|
|
if os.path.isfile(os.path.join(dbgPath, 'lldb.py')):
|
|
lldbPath = dbgPath
|
|
elif os.path.isfile(os.path.join(relPath, 'lldb.py')):
|
|
lldbPath = relPath
|
|
elif os.path.isfile(os.path.join(baiPath, 'lldb.py')):
|
|
lldbPath = baiPath
|
|
|
|
if not lldbPath:
|
|
print 'This script requires lldb.py to be in either ' + dbgPath + ',',
|
|
print relPath + ', or ' + baiPath
|
|
sys.exit(-1)
|
|
|
|
# This is to locate the lldb.py module. Insert it right after sys.path[0].
|
|
sys.path[1:1] = [lldbPath]
|
|
|
|
|
|
def doDelay(delta):
|
|
"""Delaying startup for delta-seconds to facilitate debugger attachment."""
|
|
def alarm_handler(*args):
|
|
raise Exception("timeout")
|
|
|
|
signal.signal(signal.SIGALRM, alarm_handler)
|
|
signal.alarm(delta)
|
|
sys.stdout.write("pid=%d\n" % os.getpid())
|
|
sys.stdout.write("Enter RET to proceed (or timeout after %d seconds):" %
|
|
delta)
|
|
sys.stdout.flush()
|
|
try:
|
|
text = sys.stdin.readline()
|
|
except:
|
|
text = ""
|
|
signal.alarm(0)
|
|
sys.stdout.write("proceeding...\n")
|
|
pass
|
|
|
|
|
|
def visit(prefix, dir, names):
|
|
"""Visitor function for os.path.walk(path, visit, arg)."""
|
|
|
|
global suite
|
|
global regexp
|
|
|
|
for name in names:
|
|
if os.path.isdir(os.path.join(dir, name)):
|
|
continue
|
|
|
|
if '.py' == os.path.splitext(name)[1] and name.startswith(prefix):
|
|
# Try to match the regexp pattern, if specified.
|
|
if regexp:
|
|
import re
|
|
if re.search(regexp, name):
|
|
#print "Filename: '%s' matches pattern: '%s'" % (name, regexp)
|
|
pass
|
|
else:
|
|
#print "Filename: '%s' does not match pattern: '%s'" % (name, regexp)
|
|
continue
|
|
|
|
# We found a match for our test case. Add it to the suite.
|
|
if not sys.path.count(dir):
|
|
sys.path.append(dir)
|
|
base = os.path.splitext(name)[0]
|
|
suite.addTests(unittest2.defaultTestLoader.loadTestsFromName(base))
|
|
|
|
|
|
def lldbLoggings():
|
|
"""Check and do lldb loggings if necessary."""
|
|
|
|
# Turn on logging for debugging purposes if ${LLDB_LOG} environment variable is
|
|
# defined. Use ${LLDB_LOG} to specify the log file.
|
|
ci = lldb.DBG.GetCommandInterpreter()
|
|
res = lldb.SBCommandReturnObject()
|
|
if ("LLDB_LOG" in os.environ):
|
|
if ("LLDB_LOG_OPTION" in os.environ):
|
|
lldb_log_option = os.environ["LLDB_LOG_OPTION"]
|
|
else:
|
|
lldb_log_option = "event process"
|
|
ci.HandleCommand(
|
|
"log enable -f " + os.environ["LLDB_LOG"] + " lldb " + lldb_log_option,
|
|
res)
|
|
if not res.Succeeded():
|
|
raise Exception('log enable failed (check LLDB_LOG env variable.')
|
|
# Ditto for gdb-remote logging if ${GDB_REMOTE_LOG} environment variable is defined.
|
|
# Use ${GDB_REMOTE_LOG} to specify the log file.
|
|
if ("GDB_REMOTE_LOG" in os.environ):
|
|
if ("GDB_REMOTE_LOG_OPTION" in os.environ):
|
|
gdb_remote_log_option = os.environ["GDB_REMOTE_LOG_OPTION"]
|
|
else:
|
|
gdb_remote_log_option = "packets"
|
|
ci.HandleCommand(
|
|
"log enable -f " + os.environ["GDB_REMOTE_LOG"] + " process.gdb-remote "
|
|
+ gdb_remote_log_option,
|
|
res)
|
|
if not res.Succeeded():
|
|
raise Exception('log enable failed (check GDB_REMOTE_LOG env variable.')
|
|
|
|
|
|
############################################
|
|
# #
|
|
# Execution of the test driver starts here #
|
|
# #
|
|
############################################
|
|
|
|
#
|
|
# Start the actions by first parsing the options while setting up the test
|
|
# directories, followed by setting up the search paths for lldb utilities;
|
|
# then, we walk the directory trees and collect the tests into our test suite.
|
|
#
|
|
parseOptionsAndInitTestdirs()
|
|
setupSysPath()
|
|
|
|
#
|
|
# If '-d' is specified, do a delay of 10 seconds for the debugger to attach.
|
|
#
|
|
if delay:
|
|
doDelay(10)
|
|
|
|
#
|
|
# Walk through the testdirs while collecting test cases.
|
|
#
|
|
for testdir in testdirs:
|
|
os.path.walk(testdir, visit, 'Test')
|
|
|
|
#
|
|
# Now that we have loaded all the test cases, run the whole test suite.
|
|
#
|
|
|
|
# First, write out the number of collected test cases.
|
|
sys.stderr.write(separator + "\n")
|
|
sys.stderr.write("Collected %d test%s\n\n"
|
|
% (suite.countTestCases(),
|
|
suite.countTestCases() != 1 and "s" or ""))
|
|
|
|
# For the time being, let's bracket the test runner within the
|
|
# lldb.SBDebugger.Initialize()/Terminate() pair.
|
|
import lldb, atexit
|
|
lldb.SBDebugger.Initialize()
|
|
atexit.register(lambda: lldb.SBDebugger.Terminate())
|
|
|
|
# Create a singleton SBDebugger in the lldb namespace.
|
|
lldb.DBG = lldb.SBDebugger.Create()
|
|
|
|
# Turn on lldb loggings if necessary.
|
|
lldbLoggings()
|
|
|
|
# Install the control-c handler.
|
|
unittest2.signals.installHandler()
|
|
|
|
#
|
|
# Invoke the default TextTestRunner to run the test suite, possibly iterating
|
|
# over different configurations.
|
|
#
|
|
|
|
iterArchs = False
|
|
iterCompilers = False
|
|
|
|
from types import *
|
|
if "archs" in config:
|
|
archs = config["archs"]
|
|
if type(archs) is ListType and len(archs) >= 1:
|
|
iterArchs = True
|
|
if "compilers" in config:
|
|
compilers = config["compilers"]
|
|
if type(compilers) is ListType and len(compilers) >= 1:
|
|
iterCompilers = True
|
|
|
|
for ia in range(len(archs) if iterArchs else 1):
|
|
archConfig = ""
|
|
if iterArchs:
|
|
os.environ["LLDB_ARCH"] = archs[ia]
|
|
archConfig = "arch=%s" % archs[ia]
|
|
for ic in range(len(compilers) if iterCompilers else 1):
|
|
if iterCompilers:
|
|
os.environ["LLDB_CC"] = compilers[ic]
|
|
configString = "%s compiler=%s" % (archConfig, compilers[ic])
|
|
else:
|
|
configString = archConfig
|
|
|
|
# Invoke the test runner.
|
|
if iterArchs or iterCompilers:
|
|
sys.stderr.write("\nConfiguration: " + configString + "\n")
|
|
result = unittest2.TextTestRunner(stream=sys.stderr, verbosity=verbose).run(suite)
|
|
|
|
|
|
# Terminate the test suite if ${LLDB_TESTSUITE_FORCE_FINISH} is defined.
|
|
# This should not be necessary now.
|
|
if ("LLDB_TESTSUITE_FORCE_FINISH" in os.environ):
|
|
import subprocess
|
|
print "Terminating Test suite..."
|
|
subprocess.Popen(["/bin/sh", "-c", "kill %s; exit 0" % (os.getpid())])
|
|
|
|
# Exiting.
|
|
sys.exit(not result.wasSuccessful)
|