michael@0: #!/usr/bin/env python michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import math, os, posixpath, shlex, shutil, subprocess, sys, traceback michael@0: michael@0: def add_libdir_to_path(): michael@0: from os.path import dirname, exists, join, realpath michael@0: js_src_dir = dirname(dirname(realpath(sys.argv[0]))) michael@0: assert exists(join(js_src_dir,'jsapi.h')) michael@0: sys.path.insert(0, join(js_src_dir, 'lib')) michael@0: sys.path.insert(0, join(js_src_dir, 'tests', 'lib')) michael@0: michael@0: add_libdir_to_path() michael@0: michael@0: import jittests michael@0: from tests import TBPL_FLAGS michael@0: michael@0: def main(argv): michael@0: michael@0: # If no multiprocessing is available, fallback to serial test execution michael@0: max_jobs_default = 1 michael@0: if jittests.HAVE_MULTIPROCESSING: michael@0: try: michael@0: max_jobs_default = jittests.cpu_count() michael@0: except NotImplementedError: michael@0: pass michael@0: michael@0: # The [TESTS] optional arguments are paths of test files relative michael@0: # to the jit-test/tests directory. michael@0: michael@0: from optparse import OptionParser michael@0: op = OptionParser(usage='%prog [options] JS_SHELL [TESTS]') michael@0: op.add_option('-s', '--show-cmd', dest='show_cmd', action='store_true', michael@0: help='show js shell command run') michael@0: op.add_option('-f', '--show-failed-cmd', dest='show_failed', michael@0: action='store_true', help='show command lines of failed tests') michael@0: op.add_option('-o', '--show-output', dest='show_output', action='store_true', michael@0: help='show output from js shell') michael@0: op.add_option('-x', '--exclude', dest='exclude', action='append', michael@0: help='exclude given test dir or path') michael@0: op.add_option('--no-slow', dest='run_slow', action='store_false', michael@0: help='do not run tests marked as slow') michael@0: op.add_option('-t', '--timeout', dest='timeout', type=float, default=150.0, michael@0: help='set test timeout in seconds') michael@0: op.add_option('--no-progress', dest='hide_progress', action='store_true', michael@0: help='hide progress bar') michael@0: op.add_option('--tinderbox', dest='tinderbox', action='store_true', michael@0: help='Tinderbox-parseable output format') michael@0: op.add_option('--args', dest='shell_args', default='', michael@0: help='extra args to pass to the JS shell') michael@0: op.add_option('-w', '--write-failures', dest='write_failures', metavar='FILE', michael@0: help='Write a list of failed tests to [FILE]') michael@0: op.add_option('-r', '--read-tests', dest='read_tests', metavar='FILE', michael@0: help='Run test files listed in [FILE]') michael@0: op.add_option('-R', '--retest', dest='retest', metavar='FILE', michael@0: help='Retest using test list file [FILE]') michael@0: op.add_option('-g', '--debug', dest='debug', action='store_true', michael@0: help='Run test in gdb') michael@0: op.add_option('--valgrind', dest='valgrind', action='store_true', michael@0: help='Enable the |valgrind| flag, if valgrind is in $PATH.') michael@0: op.add_option('--valgrind-all', dest='valgrind_all', action='store_true', michael@0: help='Run all tests with valgrind, if valgrind is in $PATH.') michael@0: op.add_option('--jitflags', dest='jitflags', default='', michael@0: help='Example: --jitflags=m,mn to run each test with "-m" and "-m -n" [default="%default"]. ' + michael@0: 'Long flags, such as "--ion-eager", should be set using --args.') michael@0: op.add_option('--avoid-stdio', dest='avoid_stdio', action='store_true', michael@0: help='Use js-shell file indirection instead of piping stdio.') michael@0: op.add_option('--write-failure-output', dest='write_failure_output', action='store_true', michael@0: help='With --write-failures=FILE, additionally write the output of failed tests to [FILE]') michael@0: op.add_option('--ion', dest='ion', action='store_true', michael@0: help='Run tests once with --ion-eager and once with --baseline-eager (ignores --jitflags)') michael@0: op.add_option('--tbpl', dest='tbpl', action='store_true', michael@0: help='Run tests with all IonMonkey option combinations (ignores --jitflags)') michael@0: op.add_option('-j', '--worker-count', dest='max_jobs', type=int, default=max_jobs_default, michael@0: help='Number of tests to run in parallel (default %default)') michael@0: op.add_option('--remote', action='store_true', michael@0: help='Run tests on a remote device') michael@0: op.add_option('--deviceIP', action='store', michael@0: type='string', dest='device_ip', michael@0: help='IP address of remote device to test') michael@0: op.add_option('--devicePort', action='store', michael@0: type=int, dest='device_port', default=20701, michael@0: help='port of remote device to test') michael@0: op.add_option('--deviceSerial', action='store', michael@0: type='string', dest='device_serial', default=None, michael@0: help='ADB device serial number of remote device to test') michael@0: op.add_option('--deviceTransport', action='store', michael@0: type='string', dest='device_transport', default='sut', michael@0: help='The transport to use to communicate with device: [adb|sut]; default=sut') michael@0: op.add_option('--remoteTestRoot', dest='remote_test_root', action='store', michael@0: type='string', default='/data/local/tests', michael@0: help='The remote directory to use as test root (eg. /data/local/tests)') michael@0: op.add_option('--localLib', dest='local_lib', action='store', michael@0: type='string', michael@0: help='The location of libraries to push -- preferably stripped') michael@0: op.add_option('--repeat', type=int, default=1, michael@0: help='Repeat tests the given number of times.') michael@0: op.add_option('--this-chunk', type=int, default=1, michael@0: help='The test chunk to run.') michael@0: op.add_option('--total-chunks', type=int, default=1, michael@0: help='The total number of test chunks.') michael@0: michael@0: options, args = op.parse_args(argv) michael@0: if len(args) < 1: michael@0: op.error('missing JS_SHELL argument') michael@0: # We need to make sure we are using backslashes on Windows. michael@0: test_args = args[1:] michael@0: michael@0: if jittests.stdio_might_be_broken(): michael@0: # Prefer erring on the side of caution and not using stdio if michael@0: # it might be broken on this platform. The file-redirect michael@0: # fallback should work on any platform, so at worst by michael@0: # guessing wrong we might have slowed down the tests a bit. michael@0: # michael@0: # XXX technically we could check for broken stdio, but it michael@0: # really seems like overkill. michael@0: options.avoid_stdio = True michael@0: michael@0: if options.retest: michael@0: options.read_tests = options.retest michael@0: options.write_failures = options.retest michael@0: michael@0: test_list = [] michael@0: read_all = True michael@0: michael@0: if test_args: michael@0: read_all = False michael@0: for arg in test_args: michael@0: test_list += jittests.find_tests(arg) michael@0: michael@0: if options.read_tests: michael@0: read_all = False michael@0: try: michael@0: f = open(options.read_tests) michael@0: for line in f: michael@0: test_list.append(os.path.join(jittests.TEST_DIR, line.strip('\n'))) michael@0: f.close() michael@0: except IOError: michael@0: if options.retest: michael@0: read_all = True michael@0: else: michael@0: sys.stderr.write("Exception thrown trying to read test file '%s'\n"% michael@0: options.read_tests) michael@0: traceback.print_exc() michael@0: sys.stderr.write('---\n') michael@0: michael@0: if read_all: michael@0: test_list = jittests.find_tests() michael@0: michael@0: if options.exclude: michael@0: exclude_list = [] michael@0: for exclude in options.exclude: michael@0: exclude_list += jittests.find_tests(exclude) michael@0: test_list = [ test for test in test_list if test not in set(exclude_list) ] michael@0: michael@0: if not test_list: michael@0: print >> sys.stderr, "No tests found matching command line arguments." michael@0: sys.exit(0) michael@0: michael@0: test_list = [jittests.Test.from_file(_, options) for _ in test_list] michael@0: michael@0: if not options.run_slow: michael@0: test_list = [ _ for _ in test_list if not _.slow ] michael@0: michael@0: # If chunking is enabled, determine which tests are part of this chunk. michael@0: # This code was adapted from testing/mochitest/runtestsremote.py. michael@0: if options.total_chunks > 1: michael@0: total_tests = len(test_list) michael@0: tests_per_chunk = math.ceil(total_tests / float(options.total_chunks)) michael@0: start = int(round((options.this_chunk - 1) * tests_per_chunk)) michael@0: end = int(round(options.this_chunk * tests_per_chunk)) michael@0: test_list = test_list[start:end] michael@0: michael@0: # The full test list is ready. Now create copies for each JIT configuration. michael@0: job_list = [] michael@0: if options.tbpl: michael@0: # Running all bits would take forever. Instead, we test a few interesting combinations. michael@0: for test in test_list: michael@0: for variant in TBPL_FLAGS: michael@0: new_test = test.copy() michael@0: new_test.jitflags.extend(variant) michael@0: job_list.append(new_test) michael@0: elif options.ion: michael@0: flags = [['--baseline-eager'], ['--ion-eager', '--ion-parallel-compile=off']] michael@0: for test in test_list: michael@0: for variant in flags: michael@0: new_test = test.copy() michael@0: new_test.jitflags.extend(variant) michael@0: job_list.append(new_test) michael@0: else: michael@0: jitflags_list = jittests.parse_jitflags(options) michael@0: for test in test_list: michael@0: for jitflags in jitflags_list: michael@0: new_test = test.copy() michael@0: new_test.jitflags.extend(jitflags) michael@0: job_list.append(new_test) michael@0: michael@0: prefix = [os.path.abspath(args[0])] + shlex.split(options.shell_args) michael@0: prolog = os.path.join(jittests.LIB_DIR, 'prolog.js') michael@0: if options.remote: michael@0: prolog = posixpath.join(options.remote_test_root, 'jit-tests', 'jit-tests', 'lib', 'prolog.js') michael@0: michael@0: prefix += ['-f', prolog] michael@0: michael@0: # Avoid racing on the cache by having the js shell create a new cache michael@0: # subdir for each process. The js shell takes care of deleting these michael@0: # subdirs when the process exits. michael@0: if options.max_jobs > 1 and jittests.HAVE_MULTIPROCESSING: michael@0: prefix += ['--js-cache-per-process'] michael@0: michael@0: # Clean up any remnants from previous crashes etc michael@0: shutil.rmtree(jittests.JS_CACHE_DIR, ignore_errors=True) michael@0: os.mkdir(jittests.JS_CACHE_DIR) michael@0: michael@0: if options.debug: michael@0: if len(job_list) > 1: michael@0: print 'Multiple tests match command line arguments, debugger can only run one' michael@0: for tc in job_list: michael@0: print ' %s' % tc.path michael@0: sys.exit(1) michael@0: michael@0: tc = job_list[0] michael@0: cmd = ['gdb', '--args'] + tc.command(prefix, jittests.LIB_DIR) michael@0: subprocess.call(cmd) michael@0: sys.exit() michael@0: michael@0: try: michael@0: ok = None michael@0: if options.remote: michael@0: ok = jittests.run_tests_remote(job_list, prefix, options) michael@0: elif options.max_jobs > 1 and jittests.HAVE_MULTIPROCESSING: michael@0: ok = jittests.run_tests_parallel(job_list, prefix, options) michael@0: else: michael@0: ok = jittests.run_tests(job_list, prefix, options) michael@0: if not ok: michael@0: sys.exit(2) michael@0: except OSError: michael@0: if not os.path.exists(prefix[0]): michael@0: print >> sys.stderr, "JS shell argument: file does not exist: '%s'" % prefix[0] michael@0: sys.exit(1) michael@0: else: michael@0: raise michael@0: michael@0: if __name__ == '__main__': michael@0: main(sys.argv[1:])