diff options
Diffstat (limited to 'deps/v8/tools/testrunner/base_runner.py')
-rw-r--r-- | deps/v8/tools/testrunner/base_runner.py | 438 |
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() |