DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (5b81998bb7ab)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
#!/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, re, subprocess, sys, traceback
from threading import Thread

# From this directory:
import progressbar
from taskpool import TaskPool, get_cpu_count

# Backported from Python 3.1 posixpath.py
def _relpath(path, start=None):
    """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) 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.builddir,),
                '--ex', 'set env LD_LIBRARY_PATH %s' % (OPTIONS.libdir,),
                '--ex', 'file %s' % (os.path.join(OPTIONS.builddir, 'gdb-tests'),),
                '--eval-command', 'python testlibdir=%r' % (testlibdir,),
                '--eval-command', 'python testscript=%r' % (self.test_path,),
                '--eval-command', 'python execfile(%r)' % 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):
        print "Command: ", 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:
            out.write('Command: %s\n' % (make_shell_cmd(self.cmd()),))
            self.show_output(out)
            out.write('GDB exit code: %r\n' % (self.returncode,))

def find_tests(dir, substring = None):
    ans = []
    for dirpath, dirnames, 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):
    p = subprocess.check_call(['make', 'gdb-tests'], cwd=builddir)

def run_tests(tests, summary):
    pool = TaskPool(tests, job_limit=OPTIONS.workercount, 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)

    # LIBDIR is the directory in which 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] LIBDIR [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 in [BUILDDIR].')
    (OPTIONS, args) = op.parse_args(argv)
    if len(args) < 1:
        op.error('missing LIBDIR argument')
    OPTIONS.libdir = os.path.abspath(args[0])
    test_args = args[1:]

    if not OPTIONS.workercount:
        OPTIONS.workercount = get_cpu_count()

    # Compute default for OPTIONS.builddir now, since we've computed OPTIONS.libdir.
    if not OPTIONS.builddir:
        OPTIONS.builddir = os.path.join(OPTIONS.libdir, 'gdb')

    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(test_dir, 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(test_dir, 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(test_dir, 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:])