js/src/gdb/run-tests.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/gdb/run-tests.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,348 @@
     1.4 +#!/usr/bin/env python
     1.5 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.8 +
     1.9 +# run-tests.py -- Python harness for GDB SpiderMonkey support
    1.10 +
    1.11 +import os, re, subprocess, sys, traceback
    1.12 +from threading import Thread
    1.13 +
    1.14 +# From this directory:
    1.15 +import progressbar
    1.16 +from taskpool import TaskPool, get_cpu_count
    1.17 +
    1.18 +# Backported from Python 3.1 posixpath.py
    1.19 +def _relpath(path, start=None):
    1.20 +    """Return a relative version of a path"""
    1.21 +
    1.22 +    if not path:
    1.23 +        raise ValueError("no path specified")
    1.24 +
    1.25 +    if start is None:
    1.26 +        start = os.curdir
    1.27 +
    1.28 +    start_list = os.path.abspath(start).split(os.sep)
    1.29 +    path_list = os.path.abspath(path).split(os.sep)
    1.30 +
    1.31 +    # Work out how much of the filepath is shared by start and path.
    1.32 +    i = len(os.path.commonprefix([start_list, path_list]))
    1.33 +
    1.34 +    rel_list = [os.pardir] * (len(start_list)-i) + path_list[i:]
    1.35 +    if not rel_list:
    1.36 +        return os.curdir
    1.37 +    return os.path.join(*rel_list)
    1.38 +
    1.39 +os.path.relpath = _relpath
    1.40 +
    1.41 +# Characters that need to be escaped when used in shell words.
    1.42 +shell_need_escapes = re.compile('[^\w\d%+,-./:=@\'"]', re.DOTALL)
    1.43 +# Characters that need to be escaped within double-quoted strings.
    1.44 +shell_dquote_escapes = re.compile('[^\w\d%+,-./:=@"]', re.DOTALL)
    1.45 +def make_shell_cmd(l):
    1.46 +    def quote(s):
    1.47 +        if shell_need_escapes.search(s):
    1.48 +            if s.find("'") < 0:
    1.49 +                return "'" + s + "'"
    1.50 +            return '"' + shell_dquote_escapes.sub('\\g<0>', s) + '"'
    1.51 +        return s
    1.52 +
    1.53 +    return ' '.join([quote(_) for _ in l])
    1.54 +
    1.55 +# An instance of this class collects the lists of passing, failing, and
    1.56 +# timing-out tests, runs the progress bar, and prints a summary at the end.
    1.57 +class Summary(object):
    1.58 +
    1.59 +    class SummaryBar(progressbar.ProgressBar):
    1.60 +        def __init__(self, limit):
    1.61 +            super(Summary.SummaryBar, self).__init__('', limit, 24)
    1.62 +        def start(self):
    1.63 +            self.label = '[starting           ]'
    1.64 +            self.update(0)
    1.65 +        def counts(self, run, failures, timeouts):
    1.66 +            self.label = '[%4d|%4d|%4d|%4d]' % (run - failures, failures, timeouts, run)
    1.67 +            self.update(run)
    1.68 +
    1.69 +    def __init__(self, num_tests):
    1.70 +        self.run = 0
    1.71 +        self.failures = []              # kind of judgemental; "unexpecteds"?
    1.72 +        self.timeouts = []
    1.73 +        if not OPTIONS.hide_progress:
    1.74 +            self.bar = Summary.SummaryBar(num_tests)
    1.75 +
    1.76 +    # Progress bar control.
    1.77 +    def start(self):
    1.78 +        if not OPTIONS.hide_progress:
    1.79 +            self.bar.start()
    1.80 +    def update(self):
    1.81 +        if not OPTIONS.hide_progress:
    1.82 +            self.bar.counts(self.run, len(self.failures), len(self.timeouts))
    1.83 +    # Call 'thunk' to show some output, while getting the progress bar out of the way.
    1.84 +    def interleave_output(self, thunk):
    1.85 +        if not OPTIONS.hide_progress:
    1.86 +            self.bar.clear()
    1.87 +        thunk()
    1.88 +        self.update()
    1.89 +
    1.90 +    def passed(self, test):
    1.91 +        self.run += 1
    1.92 +        self.update()
    1.93 +
    1.94 +    def failed(self, test):
    1.95 +        self.run += 1
    1.96 +        self.failures.append(test)
    1.97 +        self.update()
    1.98 +
    1.99 +    def timeout(self, test):
   1.100 +        self.run += 1
   1.101 +        self.timeouts.append(test)
   1.102 +        self.update()
   1.103 +
   1.104 +    def finish(self):
   1.105 +        if not OPTIONS.hide_progress:
   1.106 +            self.bar.finish()
   1.107 +
   1.108 +        if self.failures:
   1.109 +
   1.110 +            print "tests failed:"
   1.111 +            for test in self.failures:
   1.112 +                test.show(sys.stdout)
   1.113 +
   1.114 +            if OPTIONS.worklist:
   1.115 +                try:
   1.116 +                    with open(OPTIONS.worklist) as out:
   1.117 +                        for test in self.failures:
   1.118 +                            out.write(test.name + '\n')
   1.119 +                except IOError as err:
   1.120 +                    sys.stderr.write("Error writing worklist file '%s': %s"
   1.121 +                                     % (OPTIONS.worklist, err))
   1.122 +                    sys.exit(1)
   1.123 +
   1.124 +            if OPTIONS.write_failures:
   1.125 +                try:
   1.126 +                    with open(OPTIONS.write_failures) as out:
   1.127 +                        for test in self.failures:
   1.128 +                            test.show(out)
   1.129 +                except IOError as err:
   1.130 +                    sys.stderr.write("Error writing worklist file '%s': %s"
   1.131 +                                     % (OPTIONS.write_failures, err))
   1.132 +                    sys.exit(1)
   1.133 +
   1.134 +        if self.timeouts:
   1.135 +            print "tests timed out:"
   1.136 +            for test in self.timeouts:
   1.137 +                test.show(sys.stdout)
   1.138 +
   1.139 +        if self.failures or self.timeouts:
   1.140 +            sys.exit(2)
   1.141 +
   1.142 +class Test(TaskPool.Task):
   1.143 +    def __init__(self, path, summary):
   1.144 +        super(Test, self).__init__()
   1.145 +        self.test_path = path           # path to .py test file
   1.146 +        self.summary = summary
   1.147 +
   1.148 +        # test.name is the name of the test relative to the top of the test
   1.149 +        # directory. This is what we use to report failures and timeouts,
   1.150 +        # and when writing test lists.
   1.151 +        self.name = os.path.relpath(self.test_path, OPTIONS.testdir)
   1.152 +
   1.153 +        self.stdout = ''
   1.154 +        self.stderr = ''
   1.155 +        self.returncode = None
   1.156 +
   1.157 +    def cmd(self):
   1.158 +        testlibdir = os.path.normpath(os.path.join(OPTIONS.testdir, '..', 'lib-for-tests'))
   1.159 +        return [OPTIONS.gdb_executable,
   1.160 +                '-nw',          # Don't create a window (unnecessary?)
   1.161 +                '-nx',          # Don't read .gdbinit.
   1.162 +                '--ex', 'add-auto-load-safe-path %s' % (OPTIONS.builddir,),
   1.163 +                '--ex', 'set env LD_LIBRARY_PATH %s' % (OPTIONS.libdir,),
   1.164 +                '--ex', 'file %s' % (os.path.join(OPTIONS.builddir, 'gdb-tests'),),
   1.165 +                '--eval-command', 'python testlibdir=%r' % (testlibdir,),
   1.166 +                '--eval-command', 'python testscript=%r' % (self.test_path,),
   1.167 +                '--eval-command', 'python execfile(%r)' % os.path.join(testlibdir, 'catcher.py')]
   1.168 +
   1.169 +    def start(self, pipe, deadline):
   1.170 +        super(Test, self).start(pipe, deadline)
   1.171 +        if OPTIONS.show_cmd:
   1.172 +            self.summary.interleave_output(lambda: self.show_cmd(sys.stdout))
   1.173 +
   1.174 +    def onStdout(self, text):
   1.175 +        self.stdout += text
   1.176 +
   1.177 +    def onStderr(self, text):
   1.178 +        self.stderr += text
   1.179 +
   1.180 +    def onFinished(self, returncode):
   1.181 +        self.returncode = returncode
   1.182 +        if OPTIONS.show_output:
   1.183 +            self.summary.interleave_output(lambda: self.show_output(sys.stdout))
   1.184 +        if returncode != 0:
   1.185 +            self.summary.failed(self)
   1.186 +        else:
   1.187 +            self.summary.passed(self)
   1.188 +
   1.189 +    def onTimeout(self):
   1.190 +        self.summary.timeout(self)
   1.191 +
   1.192 +    def show_cmd(self, out):
   1.193 +        print "Command: ", make_shell_cmd(self.cmd())
   1.194 +
   1.195 +    def show_output(self, out):
   1.196 +        if self.stdout:
   1.197 +            out.write('Standard output:')
   1.198 +            out.write('\n' + self.stdout + '\n')
   1.199 +        if self.stderr:
   1.200 +            out.write('Standard error:')
   1.201 +            out.write('\n' + self.stderr + '\n')
   1.202 +
   1.203 +    def show(self, out):
   1.204 +        out.write(self.name + '\n')
   1.205 +        if OPTIONS.write_failure_output:
   1.206 +            out.write('Command: %s\n' % (make_shell_cmd(self.cmd()),))
   1.207 +            self.show_output(out)
   1.208 +            out.write('GDB exit code: %r\n' % (self.returncode,))
   1.209 +
   1.210 +def find_tests(dir, substring = None):
   1.211 +    ans = []
   1.212 +    for dirpath, dirnames, filenames in os.walk(dir):
   1.213 +        if dirpath == '.':
   1.214 +            continue
   1.215 +        for filename in filenames:
   1.216 +            if not filename.endswith('.py'):
   1.217 +                continue
   1.218 +            test = os.path.join(dirpath, filename)
   1.219 +            if substring is None or substring in os.path.relpath(test, dir):
   1.220 +                ans.append(test)
   1.221 +    return ans
   1.222 +
   1.223 +def build_test_exec(builddir):
   1.224 +    p = subprocess.check_call(['make', 'gdb-tests'], cwd=builddir)
   1.225 +
   1.226 +def run_tests(tests, summary):
   1.227 +    pool = TaskPool(tests, job_limit=OPTIONS.workercount, timeout=OPTIONS.timeout)
   1.228 +    pool.run_all()
   1.229 +
   1.230 +OPTIONS = None
   1.231 +def main(argv):
   1.232 +    global OPTIONS
   1.233 +    script_path = os.path.abspath(__file__)
   1.234 +    script_dir = os.path.dirname(script_path)
   1.235 +
   1.236 +    # LIBDIR is the directory in which we find the SpiderMonkey shared
   1.237 +    # library, to link against.
   1.238 +    #
   1.239 +    # The [TESTS] optional arguments are paths of test files relative
   1.240 +    # to the jit-test/tests directory.
   1.241 +    from optparse import OptionParser
   1.242 +    op = OptionParser(usage='%prog [options] LIBDIR [TESTS...]')
   1.243 +    op.add_option('-s', '--show-cmd', dest='show_cmd', action='store_true',
   1.244 +                  help='show GDB shell command run')
   1.245 +    op.add_option('-o', '--show-output', dest='show_output', action='store_true',
   1.246 +                  help='show output from GDB')
   1.247 +    op.add_option('-x', '--exclude', dest='exclude', action='append',
   1.248 +                  help='exclude given test dir or path')
   1.249 +    op.add_option('-t', '--timeout', dest='timeout',  type=float, default=150.0,
   1.250 +                  help='set test timeout in seconds')
   1.251 +    op.add_option('-j', '--worker-count', dest='workercount', type=int,
   1.252 +                  help='Run [WORKERCOUNT] tests at a time')
   1.253 +    op.add_option('--no-progress', dest='hide_progress', action='store_true',
   1.254 +                  help='hide progress bar')
   1.255 +    op.add_option('--worklist', dest='worklist', metavar='FILE',
   1.256 +                  help='Read tests to run from [FILE] (or run all if [FILE] not found);\n'
   1.257 +                       'write failures back to [FILE]')
   1.258 +    op.add_option('-r', '--read-tests', dest='read_tests', metavar='FILE',
   1.259 +                  help='Run test files listed in [FILE]')
   1.260 +    op.add_option('-w', '--write-failures', dest='write_failures', metavar='FILE',
   1.261 +                  help='Write failing tests to [FILE]')
   1.262 +    op.add_option('--write-failure-output', dest='write_failure_output', action='store_true',
   1.263 +                  help='With --write-failures=FILE, additionally write the output of failed tests to [FILE]')
   1.264 +    op.add_option('--gdb', dest='gdb_executable', metavar='EXECUTABLE', default='gdb',
   1.265 +                  help='Run tests with [EXECUTABLE], rather than plain \'gdb\'.')
   1.266 +    op.add_option('--srcdir', dest='srcdir',
   1.267 +                  default=os.path.abspath(os.path.join(script_dir, '..')),
   1.268 +                  help='Use SpiderMonkey sources in [SRCDIR].')
   1.269 +    op.add_option('--testdir', dest='testdir', default=os.path.join(script_dir, 'tests'),
   1.270 +                  help='Find tests in [TESTDIR].')
   1.271 +    op.add_option('--builddir', dest='builddir',
   1.272 +                  help='Build test executable in [BUILDDIR].')
   1.273 +    (OPTIONS, args) = op.parse_args(argv)
   1.274 +    if len(args) < 1:
   1.275 +        op.error('missing LIBDIR argument')
   1.276 +    OPTIONS.libdir = os.path.abspath(args[0])
   1.277 +    test_args = args[1:]
   1.278 +
   1.279 +    if not OPTIONS.workercount:
   1.280 +        OPTIONS.workercount = get_cpu_count()
   1.281 +
   1.282 +    # Compute default for OPTIONS.builddir now, since we've computed OPTIONS.libdir.
   1.283 +    if not OPTIONS.builddir:
   1.284 +        OPTIONS.builddir = os.path.join(OPTIONS.libdir, 'gdb')
   1.285 +
   1.286 +    test_set = set()
   1.287 +
   1.288 +    # All the various sources of test names accumulate.
   1.289 +    if test_args:
   1.290 +        for arg in test_args:
   1.291 +            test_set.update(find_tests(OPTIONS.testdir, arg))
   1.292 +    if OPTIONS.worklist:
   1.293 +        try:
   1.294 +            with open(OPTIONS.worklist) as f:
   1.295 +                for line in f:
   1.296 +                    test_set.update(os.path.join(test_dir, line.strip('\n')))
   1.297 +        except IOError:
   1.298 +            # With worklist, a missing file means to start the process with
   1.299 +            # the complete list of tests.
   1.300 +            sys.stderr.write("Couldn't read worklist file '%s'; running all tests\n"
   1.301 +                             % (OPTIONS.worklist,))
   1.302 +            test_set = set(find_tests(OPTIONS.testdir))
   1.303 +    if OPTIONS.read_tests:
   1.304 +        try:
   1.305 +            with open(OPTIONS.read_tests) as f:
   1.306 +                for line in f:
   1.307 +                    test_set.update(os.path.join(test_dir, line.strip('\n')))
   1.308 +        except IOError as err:
   1.309 +            sys.stderr.write("Error trying to read test file '%s': %s\n"
   1.310 +                             % (OPTIONS.read_tests, err))
   1.311 +            sys.exit(1)
   1.312 +
   1.313 +    # If none of the above options were passed, and no tests were listed
   1.314 +    # explicitly, use the complete set.
   1.315 +    if not test_args and not OPTIONS.worklist and not OPTIONS.read_tests:
   1.316 +        test_set = set(find_tests(OPTIONS.testdir))
   1.317 +
   1.318 +    if OPTIONS.exclude:
   1.319 +        exclude_set = set()
   1.320 +        for exclude in OPTIONS.exclude:
   1.321 +            exclude_set.update(find_tests(test_dir, exclude))
   1.322 +        test_set -= exclude_set
   1.323 +
   1.324 +    if not test_set:
   1.325 +        sys.stderr.write("No tests found matching command line arguments.\n")
   1.326 +        sys.exit(1)
   1.327 +
   1.328 +    summary = Summary(len(test_set))
   1.329 +    test_list = [ Test(_, summary) for _ in sorted(test_set) ]
   1.330 +
   1.331 +    # Build the test executable from all the .cpp files found in the test
   1.332 +    # directory tree.
   1.333 +    try:
   1.334 +        build_test_exec(OPTIONS.builddir)
   1.335 +    except subprocess.CalledProcessError as err:
   1.336 +        sys.stderr.write("Error building test executable: %s\n" % (err,))
   1.337 +        sys.exit(1)
   1.338 +
   1.339 +    # Run the tests.
   1.340 +    try:
   1.341 +        summary.start()
   1.342 +        run_tests(test_list, summary)
   1.343 +        summary.finish()
   1.344 +    except OSError as err:
   1.345 +        sys.stderr.write("Error running tests: %s\n" % (err,))
   1.346 +        sys.exit(1)
   1.347 +
   1.348 +    sys.exit(0)
   1.349 +
   1.350 +if __name__ == '__main__':
   1.351 +    main(sys.argv[1:])

mercurial