js/src/tests/lib/jittests.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/tests/lib/jittests.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,725 @@
     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 +
    1.10 +# jit_test.py -- Python harness for JavaScript trace tests.
    1.11 +
    1.12 +from __future__ import print_function
    1.13 +import os, posixpath, sys, tempfile, traceback, time
    1.14 +import subprocess
    1.15 +from subprocess import Popen, PIPE
    1.16 +from threading import Thread
    1.17 +import signal
    1.18 +import StringIO
    1.19 +
    1.20 +try:
    1.21 +    from multiprocessing import Process, Manager, cpu_count
    1.22 +    HAVE_MULTIPROCESSING = True
    1.23 +except ImportError:
    1.24 +    HAVE_MULTIPROCESSING = False
    1.25 +
    1.26 +from progressbar import ProgressBar, NullProgressBar
    1.27 +from results import TestOutput
    1.28 +
    1.29 +TESTS_LIB_DIR = os.path.dirname(os.path.abspath(__file__))
    1.30 +JS_DIR = os.path.dirname(os.path.dirname(TESTS_LIB_DIR))
    1.31 +TOP_SRC_DIR = os.path.dirname(os.path.dirname(JS_DIR))
    1.32 +TEST_DIR = os.path.join(JS_DIR, 'jit-test', 'tests')
    1.33 +LIB_DIR = os.path.join(JS_DIR, 'jit-test', 'lib') + os.path.sep
    1.34 +JS_CACHE_DIR = os.path.join(JS_DIR, 'jit-test', '.js-cache')
    1.35 +JS_TESTS_DIR = posixpath.join(JS_DIR, 'tests')
    1.36 +
    1.37 +# Backported from Python 3.1 posixpath.py
    1.38 +def _relpath(path, start=None):
    1.39 +    """Return a relative version of a path"""
    1.40 +
    1.41 +    if not path:
    1.42 +        raise ValueError("no path specified")
    1.43 +
    1.44 +    if start is None:
    1.45 +        start = os.curdir
    1.46 +
    1.47 +    start_list = os.path.abspath(start).split(os.sep)
    1.48 +    path_list = os.path.abspath(path).split(os.sep)
    1.49 +
    1.50 +    # Work out how much of the filepath is shared by start and path.
    1.51 +    i = len(os.path.commonprefix([start_list, path_list]))
    1.52 +
    1.53 +    rel_list = [os.pardir] * (len(start_list)-i) + path_list[i:]
    1.54 +    if not rel_list:
    1.55 +        return os.curdir
    1.56 +    return os.path.join(*rel_list)
    1.57 +
    1.58 +os.path.relpath = _relpath
    1.59 +
    1.60 +class Test:
    1.61 +
    1.62 +    VALGRIND_CMD = []
    1.63 +    paths = (d for d in os.environ['PATH'].split(os.pathsep))
    1.64 +    valgrinds = (os.path.join(d, 'valgrind') for d in paths)
    1.65 +    if any(os.path.exists(p) for p in valgrinds):
    1.66 +        VALGRIND_CMD = [
    1.67 +            'valgrind', '-q', '--smc-check=all-non-file',
    1.68 +            '--error-exitcode=1', '--gen-suppressions=all',
    1.69 +            '--show-possibly-lost=no', '--leak-check=full',
    1.70 +        ]
    1.71 +        if os.uname()[0] == 'Darwin':
    1.72 +            VALGRIND_CMD.append('--dsymutil=yes')
    1.73 +
    1.74 +    del paths
    1.75 +    del valgrinds
    1.76 +
    1.77 +    def __init__(self, path):
    1.78 +        # Absolute path of the test file.
    1.79 +        self.path = path
    1.80 +
    1.81 +        # Path relative to the top mozilla/ directory.
    1.82 +        self.relpath_top = os.path.relpath(path, TOP_SRC_DIR)
    1.83 +
    1.84 +        # Path relative to mozilla/js/src/jit-test/tests/.
    1.85 +        self.relpath_tests = os.path.relpath(path, TEST_DIR)
    1.86 +
    1.87 +        self.jitflags = []     # jit flags to enable
    1.88 +        self.slow = False      # True means the test is slow-running
    1.89 +        self.allow_oom = False # True means that OOM is not considered a failure
    1.90 +        self.valgrind = False  # True means run under valgrind
    1.91 +        self.tz_pacific = False # True means force Pacific time for the test
    1.92 +        self.expect_error = '' # Errors to expect and consider passing
    1.93 +        self.expect_status = 0 # Exit status to expect from shell
    1.94 +
    1.95 +    def copy(self):
    1.96 +        t = Test(self.path)
    1.97 +        t.jitflags = self.jitflags[:]
    1.98 +        t.slow = self.slow
    1.99 +        t.allow_oom = self.allow_oom
   1.100 +        t.valgrind = self.valgrind
   1.101 +        t.tz_pacific = self.tz_pacific
   1.102 +        t.expect_error = self.expect_error
   1.103 +        t.expect_status = self.expect_status
   1.104 +        return t
   1.105 +
   1.106 +    COOKIE = '|jit-test|'
   1.107 +    CacheDir = JS_CACHE_DIR
   1.108 +
   1.109 +    @classmethod
   1.110 +    def from_file(cls, path, options):
   1.111 +        test = cls(path)
   1.112 +
   1.113 +        line = open(path).readline()
   1.114 +        i = line.find(cls.COOKIE)
   1.115 +        if i != -1:
   1.116 +            meta = line[i + len(cls.COOKIE):].strip('\n')
   1.117 +            parts = meta.split(';')
   1.118 +            for part in parts:
   1.119 +                part = part.strip()
   1.120 +                if not part:
   1.121 +                    continue
   1.122 +                name, _, value = part.partition(':')
   1.123 +                if value:
   1.124 +                    value = value.strip()
   1.125 +                    if name == 'error':
   1.126 +                        test.expect_error = value
   1.127 +                    elif name == 'exitstatus':
   1.128 +                        try:
   1.129 +                            test.expect_status = int(value, 0);
   1.130 +                        except ValueError:
   1.131 +                            print("warning: couldn't parse exit status %s" % value)
   1.132 +                    elif name == 'thread-count':
   1.133 +                        try:
   1.134 +                            test.jitflags.append('--thread-count=' + int(value, 0));
   1.135 +                        except ValueError:
   1.136 +                            print("warning: couldn't parse thread-count %s" % value)
   1.137 +                    else:
   1.138 +                        print('warning: unrecognized |jit-test| attribute %s' % part)
   1.139 +                else:
   1.140 +                    if name == 'slow':
   1.141 +                        test.slow = True
   1.142 +                    elif name == 'allow-oom':
   1.143 +                        test.allow_oom = True
   1.144 +                    elif name == 'valgrind':
   1.145 +                        test.valgrind = options.valgrind
   1.146 +                    elif name == 'tz-pacific':
   1.147 +                        test.tz_pacific = True
   1.148 +                    elif name == 'debug':
   1.149 +                        test.jitflags.append('--debugjit')
   1.150 +                    elif name == 'ion-eager':
   1.151 +                        test.jitflags.append('--ion-eager')
   1.152 +                    elif name == 'no-ion':
   1.153 +                        test.jitflags.append('--no-ion')
   1.154 +                    elif name == 'dump-bytecode':
   1.155 +                        test.jitflags.append('--dump-bytecode')
   1.156 +                    else:
   1.157 +                        print('warning: unrecognized |jit-test| attribute %s' % part)
   1.158 +
   1.159 +        if options.valgrind_all:
   1.160 +            test.valgrind = True
   1.161 +
   1.162 +        return test
   1.163 +
   1.164 +    def command(self, prefix, libdir, remote_prefix=None):
   1.165 +        path = self.path
   1.166 +        if remote_prefix:
   1.167 +            path = self.path.replace(TEST_DIR, remote_prefix)
   1.168 +
   1.169 +        scriptdir_var = os.path.dirname(path);
   1.170 +        if not scriptdir_var.endswith('/'):
   1.171 +            scriptdir_var += '/'
   1.172 +
   1.173 +        # Platforms where subprocess immediately invokes exec do not care
   1.174 +        # whether we use double or single quotes. On windows and when using
   1.175 +        # a remote device, however, we have to be careful to use the quote
   1.176 +        # style that is the opposite of what the exec wrapper uses.
   1.177 +        # This uses %r to get single quotes on windows and special cases
   1.178 +        # the remote device.
   1.179 +        fmt = 'const platform=%r; const libdir=%r; const scriptdir=%r'
   1.180 +        if remote_prefix:
   1.181 +            fmt = 'const platform="%s"; const libdir="%s"; const scriptdir="%s"'
   1.182 +        expr = fmt % (sys.platform, libdir, scriptdir_var)
   1.183 +
   1.184 +        # We may have specified '-a' or '-d' twice: once via --jitflags, once
   1.185 +        # via the "|jit-test|" line.  Remove dups because they are toggles.
   1.186 +        cmd = prefix + ['--js-cache', Test.CacheDir]
   1.187 +        cmd += list(set(self.jitflags)) + ['-e', expr, '-f', path]
   1.188 +        if self.valgrind:
   1.189 +            cmd = self.VALGRIND_CMD + cmd
   1.190 +        return cmd
   1.191 +
   1.192 +def find_tests(substring=None):
   1.193 +    ans = []
   1.194 +    for dirpath, dirnames, filenames in os.walk(TEST_DIR):
   1.195 +        dirnames.sort()
   1.196 +        filenames.sort()
   1.197 +        if dirpath == '.':
   1.198 +            continue
   1.199 +        for filename in filenames:
   1.200 +            if not filename.endswith('.js'):
   1.201 +                continue
   1.202 +            if filename in ('shell.js', 'browser.js', 'jsref.js'):
   1.203 +                continue
   1.204 +            test = os.path.join(dirpath, filename)
   1.205 +            if substring is None or substring in os.path.relpath(test, TEST_DIR):
   1.206 +                ans.append(test)
   1.207 +    return ans
   1.208 +
   1.209 +def tmppath(token):
   1.210 +    fd, path = tempfile.mkstemp(prefix=token)
   1.211 +    os.close(fd)
   1.212 +    return path
   1.213 +
   1.214 +def read_and_unlink(path):
   1.215 +    f = open(path)
   1.216 +    d = f.read()
   1.217 +    f.close()
   1.218 +    os.unlink(path)
   1.219 +    return d
   1.220 +
   1.221 +def th_run_cmd(cmdline, options, l):
   1.222 +    # close_fds is not supported on Windows and will cause a ValueError.
   1.223 +    if sys.platform != 'win32':
   1.224 +        options["close_fds"] = True
   1.225 +    p = Popen(cmdline, stdin=PIPE, stdout=PIPE, stderr=PIPE, **options)
   1.226 +
   1.227 +    l[0] = p
   1.228 +    out, err = p.communicate()
   1.229 +    l[1] = (out, err, p.returncode)
   1.230 +
   1.231 +def run_timeout_cmd(cmdline, options, timeout=60.0):
   1.232 +    l = [ None, None ]
   1.233 +    timed_out = False
   1.234 +    th = Thread(target=th_run_cmd, args=(cmdline, options, l))
   1.235 +
   1.236 +    # If our SIGINT handler is set to SIG_IGN (ignore)
   1.237 +    # then we are running as a child process for parallel
   1.238 +    # execution and we must ensure to kill our child
   1.239 +    # when we are signaled to exit.
   1.240 +    sigint_handler = signal.getsignal(signal.SIGINT)
   1.241 +    sigterm_handler = signal.getsignal(signal.SIGTERM)
   1.242 +    if (sigint_handler == signal.SIG_IGN):
   1.243 +        def handleChildSignal(sig, frame):
   1.244 +            try:
   1.245 +                if sys.platform != 'win32':
   1.246 +                    os.kill(l[0].pid, signal.SIGKILL)
   1.247 +                else:
   1.248 +                    import ctypes
   1.249 +                    ctypes.windll.kernel32.TerminateProcess(int(l[0]._handle), -1)
   1.250 +            except OSError:
   1.251 +                pass
   1.252 +            if (sig == signal.SIGTERM):
   1.253 +                sys.exit(0)
   1.254 +        signal.signal(signal.SIGINT, handleChildSignal)
   1.255 +        signal.signal(signal.SIGTERM, handleChildSignal)
   1.256 +
   1.257 +    th.start()
   1.258 +    th.join(timeout)
   1.259 +    while th.isAlive():
   1.260 +        if l[0] is not None:
   1.261 +            try:
   1.262 +                # In Python 3, we could just do l[0].kill().
   1.263 +                if sys.platform != 'win32':
   1.264 +                    os.kill(l[0].pid, signal.SIGKILL)
   1.265 +                else:
   1.266 +                    import ctypes
   1.267 +                    ctypes.windll.kernel32.TerminateProcess(int(l[0]._handle), -1)
   1.268 +                time.sleep(.1)
   1.269 +                timed_out = True
   1.270 +            except OSError:
   1.271 +                # Expecting a "No such process" error
   1.272 +                pass
   1.273 +    th.join()
   1.274 +
   1.275 +    # Restore old signal handlers
   1.276 +    if (sigint_handler == signal.SIG_IGN):
   1.277 +        signal.signal(signal.SIGINT, signal.SIG_IGN)
   1.278 +        signal.signal(signal.SIGTERM, sigterm_handler)
   1.279 +
   1.280 +    (out, err, code) = l[1]
   1.281 +
   1.282 +    return (out, err, code, timed_out)
   1.283 +
   1.284 +def run_cmd(cmdline, env, timeout):
   1.285 +    return run_timeout_cmd(cmdline, { 'env': env }, timeout)
   1.286 +
   1.287 +def run_cmd_avoid_stdio(cmdline, env, timeout):
   1.288 +    stdoutPath, stderrPath = tmppath('jsstdout'), tmppath('jsstderr')
   1.289 +    env['JS_STDOUT'] = stdoutPath
   1.290 +    env['JS_STDERR'] = stderrPath
   1.291 +    _, __, code = run_timeout_cmd(cmdline, { 'env': env }, timeout)
   1.292 +    return read_and_unlink(stdoutPath), read_and_unlink(stderrPath), code
   1.293 +
   1.294 +def run_test(test, prefix, options):
   1.295 +    cmd = test.command(prefix, LIB_DIR)
   1.296 +    if options.show_cmd:
   1.297 +        print(subprocess.list2cmdline(cmd))
   1.298 +
   1.299 +    if options.avoid_stdio:
   1.300 +        run = run_cmd_avoid_stdio
   1.301 +    else:
   1.302 +        run = run_cmd
   1.303 +
   1.304 +    env = os.environ.copy()
   1.305 +    if test.tz_pacific:
   1.306 +        env['TZ'] = 'PST8PDT'
   1.307 +
   1.308 +    # Ensure interpreter directory is in shared library path.
   1.309 +    pathvar = ''
   1.310 +    if sys.platform.startswith('linux'):
   1.311 +        pathvar = 'LD_LIBRARY_PATH'
   1.312 +    elif sys.platform.startswith('darwin'):
   1.313 +        pathvar = 'DYLD_LIBRARY_PATH'
   1.314 +    elif sys.platform.startswith('win'):
   1.315 +        pathvar = 'PATH'
   1.316 +    if pathvar:
   1.317 +        bin_dir = os.path.dirname(cmd[0])
   1.318 +        if pathvar in env:
   1.319 +            env[pathvar] = '%s%s%s' % (bin_dir, os.pathsep, env[pathvar])
   1.320 +        else:
   1.321 +            env[pathvar] = bin_dir
   1.322 +
   1.323 +    out, err, code, timed_out = run(cmd, env, options.timeout)
   1.324 +    return TestOutput(test, cmd, out, err, code, None, timed_out)
   1.325 +
   1.326 +def run_test_remote(test, device, prefix, options):
   1.327 +    cmd = test.command(prefix, posixpath.join(options.remote_test_root, 'lib/'), posixpath.join(options.remote_test_root, 'tests'))
   1.328 +    if options.show_cmd:
   1.329 +        print(subprocess.list2cmdline(cmd))
   1.330 +
   1.331 +    env = {}
   1.332 +    if test.tz_pacific:
   1.333 +        env['TZ'] = 'PST8PDT'
   1.334 +
   1.335 +    env['LD_LIBRARY_PATH'] = options.remote_test_root
   1.336 +
   1.337 +    buf = StringIO.StringIO()
   1.338 +    returncode = device.shell(cmd, buf, env=env, cwd=options.remote_test_root,
   1.339 +                              timeout=int(options.timeout))
   1.340 +
   1.341 +    out = buf.getvalue()
   1.342 +    # We can't distinguish between stdout and stderr so we pass
   1.343 +    # the same buffer to both.
   1.344 +    return TestOutput(test, cmd, out, out, returncode, None, False)
   1.345 +
   1.346 +def check_output(out, err, rc, timed_out, test):
   1.347 +    if timed_out:
   1.348 +        # The shell sometimes hangs on shutdown on Windows 7 and Windows
   1.349 +        # Server 2008. See bug 970063 comment 7 for a description of the
   1.350 +        # problem. Until bug 956899 is fixed, ignore timeouts on these
   1.351 +        # platforms (versions 6.0 and 6.1).
   1.352 +        if sys.platform == 'win32':
   1.353 +            ver = sys.getwindowsversion()
   1.354 +            if ver.major == 6 and ver.minor <= 1:
   1.355 +                return True
   1.356 +        return False
   1.357 +
   1.358 +    if test.expect_error:
   1.359 +        # The shell exits with code 3 on uncaught exceptions.
   1.360 +        # Sometimes 0 is returned on Windows for unknown reasons.
   1.361 +        # See bug 899697.
   1.362 +        if sys.platform in ['win32', 'cygwin']:
   1.363 +            if rc != 3 and rc != 0:
   1.364 +                return False
   1.365 +        else:
   1.366 +            if rc != 3:
   1.367 +                return False
   1.368 +
   1.369 +        return test.expect_error in err
   1.370 +
   1.371 +    for line in out.split('\n'):
   1.372 +        if line.startswith('Trace stats check failed'):
   1.373 +            return False
   1.374 +
   1.375 +    for line in err.split('\n'):
   1.376 +        if 'Assertion failed:' in line:
   1.377 +            return False
   1.378 +
   1.379 +    if rc != test.expect_status:
   1.380 +        # Tests which expect a timeout check for exit code 6.
   1.381 +        # Sometimes 0 is returned on Windows for unknown reasons.
   1.382 +        # See bug 899697.
   1.383 +        if sys.platform in ['win32', 'cygwin'] and rc == 0:
   1.384 +            return True
   1.385 +
   1.386 +        # Allow a non-zero exit code if we want to allow OOM, but only if we
   1.387 +        # actually got OOM.
   1.388 +        return test.allow_oom and 'out of memory' in err and 'Assertion failure' not in err
   1.389 +
   1.390 +    return True
   1.391 +
   1.392 +def print_tinderbox(ok, res):
   1.393 +    # Output test failures in a TBPL parsable format, eg:
   1.394 +    # TEST-RESULT | filename.js | Failure description (code N, args "--foobar")
   1.395 +    #
   1.396 +    # Example:
   1.397 +    # TEST-PASS | foo/bar/baz.js | (code 0, args "--ion-eager")
   1.398 +    # TEST-UNEXPECTED-FAIL | foo/bar/baz.js | TypeError: or something (code -9, args "--no-ion")
   1.399 +    # INFO exit-status     : 3
   1.400 +    # INFO timed-out       : False
   1.401 +    # INFO stdout          > foo
   1.402 +    # INFO stdout          > bar
   1.403 +    # INFO stdout          > baz
   1.404 +    # INFO stderr         2> TypeError: or something
   1.405 +    # TEST-UNEXPECTED-FAIL | jit_test.py: Test execution interrupted by user
   1.406 +    result = "TEST-PASS" if ok else "TEST-UNEXPECTED-FAIL"
   1.407 +    message = "Success" if ok else res.describe_failure()
   1.408 +    jitflags = " ".join(res.test.jitflags)
   1.409 +    print("{} | {} | {} (code {}, args \"{}\")".format(
   1.410 +          result, res.test.relpath_top, message, res.rc, jitflags))
   1.411 +
   1.412 +    # For failed tests, print as much information as we have, to aid debugging.
   1.413 +    if ok:
   1.414 +        return
   1.415 +    print("INFO exit-status     : {}".format(res.rc))
   1.416 +    print("INFO timed-out       : {}".format(res.timed_out))
   1.417 +    for line in res.out.splitlines():
   1.418 +        print("INFO stdout          > " + line.strip())
   1.419 +    for line in res.err.splitlines():
   1.420 +        print("INFO stderr         2> " + line.strip())
   1.421 +
   1.422 +def wrap_parallel_run_test(test, prefix, resultQueue, options):
   1.423 +    # Ignore SIGINT in the child
   1.424 +    signal.signal(signal.SIGINT, signal.SIG_IGN)
   1.425 +    result = run_test(test, prefix, options)
   1.426 +    resultQueue.put(result)
   1.427 +    return result
   1.428 +
   1.429 +def run_tests_parallel(tests, prefix, options):
   1.430 +    # This queue will contain the results of the various tests run.
   1.431 +    # We could make this queue a global variable instead of using
   1.432 +    # a manager to share, but this will not work on Windows.
   1.433 +    queue_manager = Manager()
   1.434 +    async_test_result_queue = queue_manager.Queue()
   1.435 +
   1.436 +    # This queue will be used by the result process to indicate
   1.437 +    # that it has received a result and we can start a new process
   1.438 +    # on our end. The advantage is that we don't have to sleep and
   1.439 +    # check for worker completion ourselves regularly.
   1.440 +    notify_queue = queue_manager.Queue()
   1.441 +
   1.442 +    # This queue will contain the return value of the function
   1.443 +    # processing the test results.
   1.444 +    total_tests = len(tests) * options.repeat
   1.445 +    result_process_return_queue = queue_manager.Queue()
   1.446 +    result_process = Process(target=process_test_results_parallel,
   1.447 +                             args=(async_test_result_queue, result_process_return_queue,
   1.448 +                                   notify_queue, total_tests, options))
   1.449 +    result_process.start()
   1.450 +
   1.451 +    # Ensure that a SIGTERM is handled the same way as SIGINT
   1.452 +    # to terminate all child processes.
   1.453 +    sigint_handler = signal.getsignal(signal.SIGINT)
   1.454 +    signal.signal(signal.SIGTERM, sigint_handler)
   1.455 +
   1.456 +    worker_processes = []
   1.457 +
   1.458 +    def remove_completed_workers(workers):
   1.459 +        new_workers = []
   1.460 +        for worker in workers:
   1.461 +            if worker.is_alive():
   1.462 +                new_workers.append(worker)
   1.463 +            else:
   1.464 +                worker.join()
   1.465 +        return new_workers
   1.466 +
   1.467 +    try:
   1.468 +        testcnt = 0
   1.469 +        # Initially start as many jobs as allowed to run parallel
   1.470 +        for i in range(min(options.max_jobs,total_tests)):
   1.471 +            notify_queue.put(True)
   1.472 +
   1.473 +        # For every item in the notify queue, start one new worker.
   1.474 +        # Every completed worker adds a new item to this queue.
   1.475 +        while notify_queue.get():
   1.476 +            if (testcnt < total_tests):
   1.477 +                # Start one new worker
   1.478 +                test = tests[testcnt % len(tests)]
   1.479 +                worker_process = Process(target=wrap_parallel_run_test, args=(test, prefix, async_test_result_queue, options))
   1.480 +                worker_processes.append(worker_process)
   1.481 +                worker_process.start()
   1.482 +                testcnt += 1
   1.483 +
   1.484 +                # Collect completed workers
   1.485 +                worker_processes = remove_completed_workers(worker_processes)
   1.486 +            else:
   1.487 +                break
   1.488 +
   1.489 +        # Wait for all processes to terminate
   1.490 +        while len(worker_processes) > 0:
   1.491 +            worker_processes = remove_completed_workers(worker_processes)
   1.492 +
   1.493 +        # Signal completion to result processor, then wait for it to complete on its own
   1.494 +        async_test_result_queue.put(None)
   1.495 +        result_process.join()
   1.496 +
   1.497 +        # Return what the result process has returned to us
   1.498 +        return result_process_return_queue.get()
   1.499 +    except (Exception, KeyboardInterrupt) as e:
   1.500 +        # Print the exception if it's not an interrupt,
   1.501 +        # might point to a bug or other faulty condition
   1.502 +        if not isinstance(e,KeyboardInterrupt):
   1.503 +            traceback.print_exc()
   1.504 +
   1.505 +        for worker in worker_processes:
   1.506 +            try:
   1.507 +                worker.terminate()
   1.508 +            except:
   1.509 +                pass
   1.510 +
   1.511 +        result_process.terminate()
   1.512 +
   1.513 +    return False
   1.514 +
   1.515 +def get_parallel_results(async_test_result_queue, notify_queue):
   1.516 +    while True:
   1.517 +        async_test_result = async_test_result_queue.get()
   1.518 +
   1.519 +        # Check if we are supposed to terminate
   1.520 +        if (async_test_result == None):
   1.521 +            return
   1.522 +
   1.523 +        # Notify parent that we got a result
   1.524 +        notify_queue.put(True)
   1.525 +
   1.526 +        yield async_test_result
   1.527 +
   1.528 +def process_test_results_parallel(async_test_result_queue, return_queue, notify_queue, num_tests, options):
   1.529 +    gen = get_parallel_results(async_test_result_queue, notify_queue)
   1.530 +    ok = process_test_results(gen, num_tests, options)
   1.531 +    return_queue.put(ok)
   1.532 +
   1.533 +def print_test_summary(num_tests, failures, complete, doing, options):
   1.534 +    if failures:
   1.535 +        if options.write_failures:
   1.536 +            try:
   1.537 +                out = open(options.write_failures, 'w')
   1.538 +                # Don't write duplicate entries when we are doing multiple failures per job.
   1.539 +                written = set()
   1.540 +                for res in failures:
   1.541 +                    if res.test.path not in written:
   1.542 +                        out.write(os.path.relpath(res.test.path, TEST_DIR) + '\n')
   1.543 +                        if options.write_failure_output:
   1.544 +                            out.write(res.out)
   1.545 +                            out.write(res.err)
   1.546 +                            out.write('Exit code: ' + str(res.rc) + "\n")
   1.547 +                        written.add(res.test.path)
   1.548 +                out.close()
   1.549 +            except IOError:
   1.550 +                sys.stderr.write("Exception thrown trying to write failure file '%s'\n"%
   1.551 +                                 options.write_failures)
   1.552 +                traceback.print_exc()
   1.553 +                sys.stderr.write('---\n')
   1.554 +
   1.555 +        def show_test(res):
   1.556 +            if options.show_failed:
   1.557 +                print('    ' + subprocess.list2cmdline(res.cmd))
   1.558 +            else:
   1.559 +                print('    ' + ' '.join(res.test.jitflags + [res.test.path]))
   1.560 +
   1.561 +        print('FAILURES:')
   1.562 +        for res in failures:
   1.563 +            if not res.timed_out:
   1.564 +                show_test(res)
   1.565 +
   1.566 +        print('TIMEOUTS:')
   1.567 +        for res in failures:
   1.568 +            if res.timed_out:
   1.569 +                show_test(res)
   1.570 +    else:
   1.571 +        print('PASSED ALL' + ('' if complete else ' (partial run -- interrupted by user %s)' % doing))
   1.572 +
   1.573 +    if options.tinderbox:
   1.574 +        num_failures = len(failures) if failures else 0
   1.575 +        print('Result summary:')
   1.576 +        print('Passed: %d' % (num_tests - num_failures))
   1.577 +        print('Failed: %d' % num_failures)
   1.578 +
   1.579 +    return not failures
   1.580 +
   1.581 +def process_test_results(results, num_tests, options):
   1.582 +    pb = NullProgressBar()
   1.583 +    if not options.hide_progress and not options.show_cmd and ProgressBar.conservative_isatty():
   1.584 +        fmt = [
   1.585 +            {'value': 'PASS',    'color': 'green'},
   1.586 +            {'value': 'FAIL',    'color': 'red'},
   1.587 +            {'value': 'TIMEOUT', 'color': 'blue'},
   1.588 +            {'value': 'SKIP',    'color': 'brightgray'},
   1.589 +        ]
   1.590 +        pb = ProgressBar(num_tests, fmt)
   1.591 +
   1.592 +    failures = []
   1.593 +    timeouts = 0
   1.594 +    complete = False
   1.595 +    doing = 'before starting'
   1.596 +    try:
   1.597 +        for i, res in enumerate(results):
   1.598 +            if options.show_output:
   1.599 +                sys.stdout.write(res.out)
   1.600 +                sys.stdout.write(res.err)
   1.601 +                sys.stdout.write('Exit code: %s\n' % res.rc)
   1.602 +            if res.test.valgrind:
   1.603 +                sys.stdout.write(res.err)
   1.604 +
   1.605 +            ok = check_output(res.out, res.err, res.rc, res.timed_out, res.test)
   1.606 +            doing = 'after %s' % res.test.relpath_tests
   1.607 +            if not ok:
   1.608 +                failures.append(res)
   1.609 +                if res.timed_out:
   1.610 +                    pb.message("TIMEOUT - %s" % res.test.relpath_tests)
   1.611 +                    timeouts += 1
   1.612 +                else:
   1.613 +                    pb.message("FAIL - %s" % res.test.relpath_tests)
   1.614 +
   1.615 +            if options.tinderbox:
   1.616 +                print_tinderbox(ok, res)
   1.617 +
   1.618 +            n = i + 1
   1.619 +            pb.update(n, {
   1.620 +                'PASS': n - len(failures),
   1.621 +                'FAIL': len(failures),
   1.622 +                'TIMEOUT': timeouts,
   1.623 +                'SKIP': 0}
   1.624 +            )
   1.625 +        complete = True
   1.626 +    except KeyboardInterrupt:
   1.627 +        print("TEST-UNEXPECTED-FAIL | jit_test.py" +
   1.628 +              " : Test execution interrupted by user")
   1.629 +
   1.630 +    pb.finish(True)
   1.631 +    return print_test_summary(num_tests, failures, complete, doing, options)
   1.632 +
   1.633 +def get_serial_results(tests, prefix, options):
   1.634 +    for i in xrange(0, options.repeat):
   1.635 +        for test in tests:
   1.636 +            yield run_test(test, prefix, options)
   1.637 +
   1.638 +def run_tests(tests, prefix, options):
   1.639 +    gen = get_serial_results(tests, prefix, options)
   1.640 +    ok = process_test_results(gen, len(tests) * options.repeat, options)
   1.641 +    return ok
   1.642 +
   1.643 +def get_remote_results(tests, device, prefix, options):
   1.644 +    for i in xrange(0, options.repeat):
   1.645 +        for test in tests:
   1.646 +            yield run_test_remote(test, device, prefix, options)
   1.647 +
   1.648 +def push_libs(options, device):
   1.649 +    # This saves considerable time in pushing unnecessary libraries
   1.650 +    # to the device but needs to be updated if the dependencies change.
   1.651 +    required_libs = ['libnss3.so', 'libmozglue.so']
   1.652 +
   1.653 +    for file in os.listdir(options.local_lib):
   1.654 +        if file in required_libs:
   1.655 +            remote_file = posixpath.join(options.remote_test_root, file)
   1.656 +            device.pushFile(os.path.join(options.local_lib, file), remote_file)
   1.657 +
   1.658 +def push_progs(options, device, progs):
   1.659 +    for local_file in progs:
   1.660 +        remote_file = posixpath.join(options.remote_test_root, os.path.basename(local_file))
   1.661 +        device.pushFile(local_file, remote_file)
   1.662 +
   1.663 +def run_tests_remote(tests, prefix, options):
   1.664 +    # Setup device with everything needed to run our tests.
   1.665 +    from mozdevice import devicemanager, devicemanagerADB, devicemanagerSUT
   1.666 +
   1.667 +    if options.device_transport == 'adb':
   1.668 +        if options.device_ip:
   1.669 +            dm = devicemanagerADB.DeviceManagerADB(options.device_ip, options.device_port, deviceSerial=options.device_serial, packageName=None, deviceRoot=options.remote_test_root)
   1.670 +        else:
   1.671 +            dm = devicemanagerADB.DeviceManagerADB(deviceSerial=options.device_serial, packageName=None, deviceRoot=options.remote_test_root)
   1.672 +    else:
   1.673 +        dm = devicemanagerSUT.DeviceManagerSUT(options.device_ip, options.device_port, deviceRoot=options.remote_test_root)
   1.674 +        if options.device_ip == None:
   1.675 +            print('Error: you must provide a device IP to connect to via the --device option')
   1.676 +            sys.exit(1)
   1.677 +
   1.678 +    # Update the test root to point to our test directory.
   1.679 +    jit_tests_dir = posixpath.join(options.remote_test_root, 'jit-tests')
   1.680 +    options.remote_test_root = posixpath.join(jit_tests_dir, 'jit-tests')
   1.681 +
   1.682 +    # Push js shell and libraries.
   1.683 +    if dm.dirExists(jit_tests_dir):
   1.684 +        dm.removeDir(jit_tests_dir)
   1.685 +    dm.mkDirs(options.remote_test_root)
   1.686 +    push_libs(options, dm)
   1.687 +    push_progs(options, dm, [prefix[0]])
   1.688 +    dm.chmodDir(options.remote_test_root)
   1.689 +
   1.690 +    Test.CacheDir = posixpath.join(options.remote_test_root, '.js-cache')
   1.691 +    dm.mkDir(Test.CacheDir)
   1.692 +
   1.693 +    dm.pushDir(JS_TESTS_DIR, posixpath.join(jit_tests_dir, 'tests'), timeout=600)
   1.694 +
   1.695 +    dm.pushDir(os.path.dirname(TEST_DIR), options.remote_test_root, timeout=600)
   1.696 +    prefix[0] = os.path.join(options.remote_test_root, 'js')
   1.697 +
   1.698 +    # Run all tests.
   1.699 +    gen = get_remote_results(tests, dm, prefix, options)
   1.700 +    ok = process_test_results(gen, len(tests) * options.repeat, options)
   1.701 +    return ok
   1.702 +
   1.703 +def parse_jitflags(options):
   1.704 +    jitflags = [ [ '-' + flag for flag in flags ]
   1.705 +                 for flags in options.jitflags.split(',') ]
   1.706 +    for flags in jitflags:
   1.707 +        for flag in flags:
   1.708 +            if flag not in ('-m', '-a', '-p', '-d', '-n'):
   1.709 +                print('Invalid jit flag: "%s"' % flag)
   1.710 +                sys.exit(1)
   1.711 +    return jitflags
   1.712 +
   1.713 +def platform_might_be_android():
   1.714 +    try:
   1.715 +        # The python package for SL4A provides an |android| module.
   1.716 +        # If that module is present, we're likely in SL4A-python on
   1.717 +        # device.  False positives and negatives are possible,
   1.718 +        # however.
   1.719 +        import android
   1.720 +        return True
   1.721 +    except ImportError:
   1.722 +        return False
   1.723 +
   1.724 +def stdio_might_be_broken():
   1.725 +    return platform_might_be_android()
   1.726 +
   1.727 +if __name__ == '__main__':
   1.728 +    print('Use ../jit-test/jit_test.py to run these tests.')

mercurial