Source code

Revision control

Other Tools

#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# run-tests.py -- Python harness for GDB SpiderMonkey support
import os
import re
import subprocess
import sys
# From this directory:
import progressbar
from taskpool import TaskPool, get_cpu_count
def _relpath(path, start=None):
# Backported from Python 3.1 posixpath.py
"""Return a relative version of a path"""
if not path:
raise ValueError("no path specified")
if start is None:
start = os.curdir
start_list = os.path.abspath(start).split(os.sep)
path_list = os.path.abspath(path).split(os.sep)
# Work out how much of the filepath is shared by start and path.
i = len(os.path.commonprefix([start_list, path_list]))
rel_list = [os.pardir] * (len(start_list)-i) + path_list[i:]
if not rel_list:
return os.curdir
return os.path.join(*rel_list)
os.path.relpath = _relpath
# Characters that need to be escaped when used in shell words.
shell_need_escapes = re.compile('[^\w\d%+,-./:=@\'"]', re.DOTALL)
# Characters that need to be escaped within double-quoted strings.
shell_dquote_escapes = re.compile('[^\w\d%+,-./:=@"]', re.DOTALL)
def make_shell_cmd(l):
def quote(s):
if shell_need_escapes.search(s):
if s.find("'") < 0:
return "'" + s + "'"
return '"' + shell_dquote_escapes.sub('\\g<0>', s) + '"'
return s
return ' '.join([quote(_) for _ in l])
# An instance of this class collects the lists of passing, failing, and
# timing-out tests, runs the progress bar, and prints a summary at the end.
class Summary(object):
class SummaryBar(progressbar.ProgressBar):
def __init__(self, limit):
super(Summary.SummaryBar, self).__init__('', limit, 24)
def start(self):
self.label = '[starting ]'
self.update(0)
def counts(self, run, failures, timeouts):
self.label = '[%4d|%4d|%4d|%4d]' % (run - failures, failures, timeouts, run)
self.update(run)
def __init__(self, num_tests):
self.run = 0
self.failures = [] # kind of judgemental; "unexpecteds"?
self.timeouts = []
if not OPTIONS.hide_progress:
self.bar = Summary.SummaryBar(num_tests)
# Progress bar control.
def start(self):
if not OPTIONS.hide_progress:
self.bar.start()
def update(self):
if not OPTIONS.hide_progress:
self.bar.counts(self.run, len(self.failures), len(self.timeouts))
# Call 'thunk' to show some output, while getting the progress bar out of the way.
def interleave_output(self, thunk):
if not OPTIONS.hide_progress:
self.bar.clear()
thunk()
self.update()
def passed(self, test):
self.run += 1
self.update()
def failed(self, test):
self.run += 1
self.failures.append(test)
self.update()
def timeout(self, test):
self.run += 1
self.timeouts.append(test)
self.update()
def finish(self):
if not OPTIONS.hide_progress:
self.bar.finish()
if self.failures:
print("tests failed:")
for test in self.failures:
test.show(sys.stdout)
if OPTIONS.worklist:
try:
with open(OPTIONS.worklist) as out:
for test in self.failures:
out.write(test.name + '\n')
except IOError as err:
sys.stderr.write("Error writing worklist file '%s': %s"
% (OPTIONS.worklist, err))
sys.exit(1)
if OPTIONS.write_failures:
try:
with open(OPTIONS.write_failures, "w") as out:
for test in self.failures:
test.show(out)
except IOError as err:
sys.stderr.write("Error writing worklist file '%s': %s"
% (OPTIONS.write_failures, err))
sys.exit(1)
if self.timeouts:
print("tests timed out:")
for test in self.timeouts:
test.show(sys.stdout)
if self.failures or self.timeouts:
sys.exit(2)
class Test(TaskPool.Task):
def __init__(self, path, summary):
super(Test, self).__init__()
self.test_path = path # path to .py test file
self.summary = summary
# test.name is the name of the test relative to the top of the test
# directory. This is what we use to report failures and timeouts,
# and when writing test lists.
self.name = os.path.relpath(self.test_path, OPTIONS.testdir)
self.stdout = ''
self.stderr = ''
self.returncode = None
def cmd(self):
testlibdir = os.path.normpath(os.path.join(OPTIONS.testdir, '..', 'lib-for-tests'))
return [OPTIONS.gdb_executable,
'-nw', # Don't create a window (unnecessary?)
'-nx', # Don't read .gdbinit.
'--ex', 'add-auto-load-safe-path %s' % (OPTIONS.bindir,),
'--ex', 'set env LD_LIBRARY_PATH %s' % (OPTIONS.bindir,),
'--ex', 'file %s' % (os.path.join(OPTIONS.bindir, 'gdb-tests'),),
'--eval-command', 'python testlibdir=%r' % (testlibdir,),
'--eval-command', 'python testscript=%r' % (self.test_path,),
'--eval-command', 'python exec(open(%r).read())' % os.path.join(testlibdir,
'catcher.py')]
def start(self, pipe, deadline):
super(Test, self).start(pipe, deadline)
if OPTIONS.show_cmd:
self.summary.interleave_output(lambda: self.show_cmd(sys.stdout))
def onStdout(self, text):
self.stdout += text
def onStderr(self, text):
self.stderr += text
def onFinished(self, returncode):
self.returncode = returncode
if OPTIONS.show_output:
self.summary.interleave_output(lambda: self.show_output(sys.stdout))
if returncode != 0:
self.summary.failed(self)
else:
self.summary.passed(self)
def onTimeout(self):
self.summary.timeout(self)
def show_cmd(self, out):
out.write("Command: %s\n" % (make_shell_cmd(self.cmd()),))
def show_output(self, out):
if self.stdout:
out.write('Standard output:')
out.write('\n' + self.stdout + '\n')
if self.stderr:
out.write('Standard error:')
out.write('\n' + self.stderr + '\n')
def show(self, out):
out.write(self.name + '\n')
if OPTIONS.write_failure_output:
self.show_cmd(out)
self.show_output(out)
out.write('GDB exit code: %r\n' % (self.returncode,))
def find_tests(dir, substring=None):
ans = []
for dirpath, _, filenames in os.walk(dir):
if dirpath == '.':
continue
for filename in filenames:
if not filename.endswith('.py'):
continue
test = os.path.join(dirpath, filename)
if substring is None or substring in os.path.relpath(test, dir):
ans.append(test)
return ans
def build_test_exec(builddir):
subprocess.check_call(['make'], cwd=builddir)
def run_tests(tests, summary):
jobs = OPTIONS.workercount
# python 3.3 fixed a bug with concurrently writing .pyc files.
embedded_version = subprocess.check_output([
OPTIONS.gdb_executable,
'--batch',
'--ex', 'python import sys; print(sys.hexversion)'
]).decode('ascii').strip()
if hex(int(embedded_version)) < '0x3030000':
jobs = 1
pool = TaskPool(tests, job_limit=jobs, timeout=OPTIONS.timeout)
pool.run_all()
OPTIONS = None
def main(argv):
global OPTIONS
script_path = os.path.abspath(__file__)
script_dir = os.path.dirname(script_path)
# OBJDIR is a standalone SpiderMonkey build directory. This is where we
# find the SpiderMonkey shared library to link against.
#
# The [TESTS] optional arguments are paths of test files relative
# to the jit-test/tests directory.
from optparse import OptionParser
op = OptionParser(usage='%prog [options] OBJDIR [TESTS...]')
op.add_option('-s', '--show-cmd', dest='show_cmd', action='store_true',
help='show GDB shell command run')
op.add_option('-o', '--show-output', dest='show_output', action='store_true',
help='show output from GDB')
op.add_option('-x', '--exclude', dest='exclude', action='append',
help='exclude given test dir or path')
op.add_option('-t', '--timeout', dest='timeout', type=float, default=150.0,
help='set test timeout in seconds')
op.add_option('-j', '--worker-count', dest='workercount', type=int,
help='Run [WORKERCOUNT] tests at a time')
op.add_option('--no-progress', dest='hide_progress', action='store_true',
help='hide progress bar')
op.add_option('--worklist', dest='worklist', metavar='FILE',
help='Read tests to run from [FILE] (or run all if [FILE] not found);\n'
'write failures back to [FILE]')
op.add_option('-r', '--read-tests', dest='read_tests', metavar='FILE',
help='Run test files listed in [FILE]')
op.add_option('-w', '--write-failures', dest='write_failures', metavar='FILE',
help='Write failing tests to [FILE]')
op.add_option('--write-failure-output', dest='write_failure_output', action='store_true',
help='With --write-failures=FILE, additionally write the output of failed '
'tests to [FILE]')
op.add_option('--gdb', dest='gdb_executable', metavar='EXECUTABLE', default='gdb',
help='Run tests with [EXECUTABLE], rather than plain \'gdb\'.')
op.add_option('--srcdir', dest='srcdir',
default=os.path.abspath(os.path.join(script_dir, '..')),
help='Use SpiderMonkey sources in [SRCDIR].')
op.add_option('--testdir', dest='testdir', default=os.path.join(script_dir, 'tests'),
help='Find tests in [TESTDIR].')
op.add_option('--builddir', dest='builddir',
help='Build test executable from [BUILDDIR].')
op.add_option('--bindir', dest='bindir',
help='Run test executable from [BINDIR].')
(OPTIONS, args) = op.parse_args(argv)
if len(args) < 1:
op.error('missing OBJDIR argument')
OPTIONS.objdir = os.path.abspath(args[0])
test_args = args[1:]
if not OPTIONS.workercount:
OPTIONS.workercount = get_cpu_count()
# Compute defaults for OPTIONS.builddir and OPTIONS.bindir now, since we've
# computed OPTIONS.objdir.
if not OPTIONS.builddir:
OPTIONS.builddir = os.path.join(OPTIONS.objdir, 'js', 'src', 'gdb')
if not OPTIONS.bindir:
OPTIONS.bindir = os.path.join(OPTIONS.objdir, 'dist', 'bin')
test_set = set()
# All the various sources of test names accumulate.
if test_args:
for arg in test_args:
test_set.update(find_tests(OPTIONS.testdir, arg))
if OPTIONS.worklist:
try:
with open(OPTIONS.worklist) as f:
for line in f:
test_set.update(os.path.join(OPTIONS.testdir, line.strip('\n')))
except IOError:
# With worklist, a missing file means to start the process with
# the complete list of tests.
sys.stderr.write("Couldn't read worklist file '%s'; running all tests\n"
% (OPTIONS.worklist,))
test_set = set(find_tests(OPTIONS.testdir))
if OPTIONS.read_tests:
try:
with open(OPTIONS.read_tests) as f:
for line in f:
test_set.update(os.path.join(OPTIONS.testdir, line.strip('\n')))
except IOError as err:
sys.stderr.write("Error trying to read test file '%s': %s\n"
% (OPTIONS.read_tests, err))
sys.exit(1)
# If none of the above options were passed, and no tests were listed
# explicitly, use the complete set.
if not test_args and not OPTIONS.worklist and not OPTIONS.read_tests:
test_set = set(find_tests(OPTIONS.testdir))
if OPTIONS.exclude:
exclude_set = set()
for exclude in OPTIONS.exclude:
exclude_set.update(find_tests(OPTIONS.testdir, exclude))
test_set -= exclude_set
if not test_set:
sys.stderr.write("No tests found matching command line arguments.\n")
sys.exit(1)
summary = Summary(len(test_set))
test_list = [Test(_, summary) for _ in sorted(test_set)]
# Build the test executable from all the .cpp files found in the test
# directory tree.
try:
build_test_exec(OPTIONS.builddir)
except subprocess.CalledProcessError as err:
sys.stderr.write("Error building test executable: %s\n" % (err,))
sys.exit(1)
# Run the tests.
try:
summary.start()
run_tests(test_list, summary)
summary.finish()
except OSError as err:
sys.stderr.write("Error running tests: %s\n" % (err,))
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main(sys.argv[1:])