summaryrefslogtreecommitdiff
path: root/deps/v8/tools/testrunner/base_runner.py
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/tools/testrunner/base_runner.py')
-rw-r--r--deps/v8/tools/testrunner/base_runner.py438
1 files changed, 438 insertions, 0 deletions
diff --git a/deps/v8/tools/testrunner/base_runner.py b/deps/v8/tools/testrunner/base_runner.py
new file mode 100644
index 0000000000..b6ef6fb5cd
--- /dev/null
+++ b/deps/v8/tools/testrunner/base_runner.py
@@ -0,0 +1,438 @@
+# Copyright 2017 the V8 project authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+import json
+import optparse
+import os
+import sys
+
+
+# Add testrunner to the path.
+sys.path.insert(
+ 0,
+ os.path.dirname(
+ os.path.dirname(os.path.abspath(__file__))))
+
+
+from local import utils
+
+
+BASE_DIR = (
+ os.path.dirname(
+ os.path.dirname(
+ os.path.dirname(
+ os.path.abspath(__file__)))))
+
+DEFAULT_OUT_GN = 'out.gn'
+
+ARCH_GUESS = utils.DefaultArch()
+
+# Map of test name synonyms to lists of test suites. Should be ordered by
+# expected runtimes (suites with slow test cases first). These groups are
+# invoked in separate steps on the bots.
+TEST_MAP = {
+ # This needs to stay in sync with test/bot_default.isolate.
+ "bot_default": [
+ "debugger",
+ "mjsunit",
+ "cctest",
+ "wasm-spec-tests",
+ "inspector",
+ "webkit",
+ "mkgrokdump",
+ "fuzzer",
+ "message",
+ "preparser",
+ "intl",
+ "unittests",
+ ],
+ # This needs to stay in sync with test/default.isolate.
+ "default": [
+ "debugger",
+ "mjsunit",
+ "cctest",
+ "wasm-spec-tests",
+ "inspector",
+ "mkgrokdump",
+ "fuzzer",
+ "message",
+ "preparser",
+ "intl",
+ "unittests",
+ ],
+ # This needs to stay in sync with test/optimize_for_size.isolate.
+ "optimize_for_size": [
+ "debugger",
+ "mjsunit",
+ "cctest",
+ "inspector",
+ "webkit",
+ "intl",
+ ],
+ "unittests": [
+ "unittests",
+ ],
+}
+
+
+class ModeConfig(object):
+ def __init__(self, flags, timeout_scalefactor, status_mode, execution_mode):
+ self.flags = flags
+ self.timeout_scalefactor = timeout_scalefactor
+ self.status_mode = status_mode
+ self.execution_mode = execution_mode
+
+
+DEBUG_FLAGS = ["--nohard-abort", "--enable-slow-asserts", "--verify-heap"]
+RELEASE_FLAGS = ["--nohard-abort"]
+MODES = {
+ "debug": ModeConfig(
+ flags=DEBUG_FLAGS,
+ timeout_scalefactor=4,
+ status_mode="debug",
+ execution_mode="debug",
+ ),
+ "optdebug": ModeConfig(
+ flags=DEBUG_FLAGS,
+ timeout_scalefactor=4,
+ status_mode="debug",
+ execution_mode="debug",
+ ),
+ "release": ModeConfig(
+ flags=RELEASE_FLAGS,
+ timeout_scalefactor=1,
+ status_mode="release",
+ execution_mode="release",
+ ),
+ # Normal trybot release configuration. There, dchecks are always on which
+ # implies debug is set. Hence, the status file needs to assume debug-like
+ # behavior/timeouts.
+ "tryrelease": ModeConfig(
+ flags=RELEASE_FLAGS,
+ timeout_scalefactor=1,
+ status_mode="debug",
+ execution_mode="release",
+ ),
+ # This mode requires v8 to be compiled with dchecks and slow dchecks.
+ "slowrelease": ModeConfig(
+ flags=RELEASE_FLAGS + ["--enable-slow-asserts"],
+ timeout_scalefactor=2,
+ status_mode="debug",
+ execution_mode="release",
+ ),
+}
+
+
+class TestRunnerError(Exception):
+ pass
+
+
+class BuildConfig(object):
+ def __init__(self, build_config):
+ # In V8 land, GN's x86 is called ia32.
+ if build_config['v8_target_cpu'] == 'x86':
+ self.arch = 'ia32'
+ else:
+ self.arch = build_config['v8_target_cpu']
+
+ self.is_debug = build_config['is_debug']
+ self.asan = build_config['is_asan']
+ self.cfi_vptr = build_config['is_cfi']
+ self.dcheck_always_on = build_config['dcheck_always_on']
+ self.gcov_coverage = build_config['is_gcov_coverage']
+ self.msan = build_config['is_msan']
+ self.no_i18n = not build_config['v8_enable_i18n_support']
+ self.no_snap = not build_config['v8_use_snapshot']
+ self.predictable = build_config['v8_enable_verify_predictable']
+ self.tsan = build_config['is_tsan']
+ self.ubsan_vptr = build_config['is_ubsan_vptr']
+
+ def __str__(self):
+ detected_options = []
+
+ if self.asan:
+ detected_options.append('asan')
+ if self.cfi_vptr:
+ detected_options.append('cfi_vptr')
+ if self.dcheck_always_on:
+ detected_options.append('dcheck_always_on')
+ if self.gcov_coverage:
+ detected_options.append('gcov_coverage')
+ if self.msan:
+ detected_options.append('msan')
+ if self.no_i18n:
+ detected_options.append('no_i18n')
+ if self.no_snap:
+ detected_options.append('no_snap')
+ if self.predictable:
+ detected_options.append('predictable')
+ if self.tsan:
+ detected_options.append('tsan')
+ if self.ubsan_vptr:
+ detected_options.append('ubsan_vptr')
+
+ return '\n'.join(detected_options)
+
+
+class BaseTestRunner(object):
+ def __init__(self):
+ self.outdir = None
+ self.build_config = None
+ self.mode_name = None
+ self.mode_options = None
+
+ def execute(self):
+ try:
+ parser = self._create_parser()
+ options, args = self._parse_args(parser)
+
+ self._load_build_config(options)
+
+ try:
+ self._process_default_options(options)
+ self._process_options(options)
+ except TestRunnerError:
+ parser.print_help()
+ raise
+
+ self._setup_env()
+ return self._do_execute(options, args)
+ except TestRunnerError:
+ return 1
+
+ def _create_parser(self):
+ parser = optparse.OptionParser()
+ parser.usage = '%prog [options] [tests]'
+ parser.description = """TESTS: %s""" % (TEST_MAP["default"])
+ self._add_parser_default_options(parser)
+ self._add_parser_options(parser)
+ return parser
+
+ def _add_parser_default_options(self, parser):
+ parser.add_option("--gn", help="Scan out.gn for the last built"
+ " configuration",
+ default=False, action="store_true")
+ parser.add_option("--outdir", help="Base directory with compile output",
+ default="out")
+ parser.add_option("--buildbot", help="DEPRECATED!",
+ default=False, action="store_true")
+ parser.add_option("--arch",
+ help="The architecture to run tests for")
+ parser.add_option("-m", "--mode",
+ help="The test mode in which to run (uppercase for ninja"
+ " and buildbot builds): %s" % MODES.keys())
+ parser.add_option("--shell-dir", help="DEPRECATED! Executables from build "
+ "directory will be used")
+ parser.add_option("-v", "--verbose", help="Verbose output",
+ default=False, action="store_true")
+
+ def _add_parser_options(self, parser):
+ pass
+
+ def _parse_args(self, parser):
+ options, args = parser.parse_args()
+
+ if any(map(lambda v: v and ',' in v,
+ [options.arch, options.mode])):
+ print 'Multiple arch/mode are deprecated'
+ raise TestRunnerError()
+
+ return options, args
+
+ def _load_build_config(self, options):
+ for outdir in self._possible_outdirs(options):
+ try:
+ self.build_config = self._do_load_build_config(outdir, options.verbose)
+ except TestRunnerError:
+ pass
+
+ if not self.build_config:
+ print 'Failed to load build config'
+ raise TestRunnerError
+
+ print 'Build found: %s' % self.outdir
+ if str(self.build_config):
+ print '>>> Autodetected:'
+ print self.build_config
+
+ # Returns possible build paths in order:
+ # gn
+ # outdir
+ # outdir/arch.mode
+ # Each path is provided in two versions: <path> and <path>/mode for buildbot.
+ def _possible_outdirs(self, options):
+ def outdirs():
+ if options.gn:
+ yield self._get_gn_outdir()
+ return
+
+ yield options.outdir
+ if options.arch and options.mode:
+ yield os.path.join(options.outdir,
+ '%s.%s' % (options.arch, options.mode))
+
+ for outdir in outdirs():
+ yield os.path.join(BASE_DIR, outdir)
+
+ # buildbot option
+ if options.mode:
+ yield os.path.join(BASE_DIR, outdir, options.mode)
+
+ def _get_gn_outdir(self):
+ gn_out_dir = os.path.join(BASE_DIR, DEFAULT_OUT_GN)
+ latest_timestamp = -1
+ latest_config = None
+ for gn_config in os.listdir(gn_out_dir):
+ gn_config_dir = os.path.join(gn_out_dir, gn_config)
+ if not os.path.isdir(gn_config_dir):
+ continue
+ if os.path.getmtime(gn_config_dir) > latest_timestamp:
+ latest_timestamp = os.path.getmtime(gn_config_dir)
+ latest_config = gn_config
+ if latest_config:
+ print(">>> Latest GN build found: %s" % latest_config)
+ return os.path.join(DEFAULT_OUT_GN, latest_config)
+
+ def _do_load_build_config(self, outdir, verbose=False):
+ build_config_path = os.path.join(outdir, "v8_build_config.json")
+ if not os.path.exists(build_config_path):
+ if verbose:
+ print("Didn't find build config: %s" % build_config_path)
+ raise TestRunnerError()
+
+ with open(build_config_path) as f:
+ try:
+ build_config_json = json.load(f)
+ except Exception:
+ print("%s exists but contains invalid json. Is your build up-to-date?"
+ % build_config_path)
+ raise TestRunnerError()
+
+ # In auto-detect mode the outdir is always where we found the build config.
+ # This ensures that we'll also take the build products from there.
+ self.outdir = os.path.dirname(build_config_path)
+
+ return BuildConfig(build_config_json)
+
+ def _process_default_options(self, options):
+ # We don't use the mode for more path-magic.
+ # Therefore transform the buildbot mode here to fix build_config value.
+ if options.mode:
+ options.mode = self._buildbot_to_v8_mode(options.mode)
+
+ build_config_mode = 'debug' if self.build_config.is_debug else 'release'
+ if options.mode:
+ if options.mode not in MODES:
+ print '%s mode is invalid' % options.mode
+ raise TestRunnerError()
+ if MODES[options.mode].execution_mode != build_config_mode:
+ print ('execution mode (%s) for %s is inconsistent with build config '
+ '(%s)' % (
+ MODES[options.mode].execution_mode,
+ options.mode,
+ build_config_mode))
+ raise TestRunnerError()
+
+ self.mode_name = options.mode
+ else:
+ self.mode_name = build_config_mode
+
+ self.mode_options = MODES[self.mode_name]
+
+ if options.arch and options.arch != self.build_config.arch:
+ print('--arch value (%s) inconsistent with build config (%s).' % (
+ options.arch, self.build_config.arch))
+ raise TestRunnerError()
+
+ if options.shell_dir:
+ print('Warning: --shell-dir is deprecated. Searching for executables in '
+ 'build directory (%s) instead.' % self.outdir)
+
+ def _buildbot_to_v8_mode(self, config):
+ """Convert buildbot build configs to configs understood by the v8 runner.
+
+ V8 configs are always lower case and without the additional _x64 suffix
+ for 64 bit builds on windows with ninja.
+ """
+ mode = config[:-4] if config.endswith('_x64') else config
+ return mode.lower()
+
+ def _process_options(self, options):
+ pass
+
+ def _setup_env(self):
+ # Use the v8 root as cwd as some test cases use "load" with relative paths.
+ os.chdir(BASE_DIR)
+
+ # Many tests assume an English interface.
+ os.environ['LANG'] = 'en_US.UTF-8'
+
+ symbolizer_option = self._get_external_symbolizer_option()
+
+ if self.build_config.asan:
+ asan_options = [
+ symbolizer_option,
+ 'allow_user_segv_handler=1',
+ 'allocator_may_return_null=1',
+ ]
+ if not utils.GuessOS() in ['macos', 'windows']:
+ # LSAN is not available on mac and windows.
+ asan_options.append('detect_leaks=1')
+ else:
+ asan_options.append('detect_leaks=0')
+ os.environ['ASAN_OPTIONS'] = ":".join(asan_options)
+
+ if self.build_config.cfi_vptr:
+ os.environ['UBSAN_OPTIONS'] = ":".join([
+ 'print_stacktrace=1',
+ 'print_summary=1',
+ 'symbolize=1',
+ symbolizer_option,
+ ])
+
+ if self.build_config.ubsan_vptr:
+ os.environ['UBSAN_OPTIONS'] = ":".join([
+ 'print_stacktrace=1',
+ symbolizer_option,
+ ])
+
+ if self.build_config.msan:
+ os.environ['MSAN_OPTIONS'] = symbolizer_option
+
+ if self.build_config.tsan:
+ suppressions_file = os.path.join(
+ BASE_DIR,
+ 'tools',
+ 'sanitizers',
+ 'tsan_suppressions.txt')
+ os.environ['TSAN_OPTIONS'] = " ".join([
+ symbolizer_option,
+ 'suppressions=%s' % suppressions_file,
+ 'exit_code=0',
+ 'report_thread_leaks=0',
+ 'history_size=7',
+ 'report_destroy_locked=0',
+ ])
+
+ def _get_external_symbolizer_option(self):
+ external_symbolizer_path = os.path.join(
+ BASE_DIR,
+ 'third_party',
+ 'llvm-build',
+ 'Release+Asserts',
+ 'bin',
+ 'llvm-symbolizer',
+ )
+
+ if utils.IsWindows():
+ # Quote, because sanitizers might confuse colon as option separator.
+ external_symbolizer_path = '"%s.exe"' % external_symbolizer_path
+
+ return 'external_symbolizer_path=%s' % external_symbolizer_path
+
+
+ # TODO(majeski): remove options & args parameters
+ def _do_execute(self, options, args):
+ raise NotImplementedError()