diff options
Diffstat (limited to 'js/src/gdb/run-tests.py')
-rw-r--r-- | js/src/gdb/run-tests.py | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/js/src/gdb/run-tests.py b/js/src/gdb/run-tests.py new file mode 100644 index 0000000000..da8d38cfc1 --- /dev/null +++ b/js/src/gdb/run-tests.py @@ -0,0 +1,470 @@ +#!/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. + # https://bugs.python.org/issue13146 + 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:]) |