| #!/usr/bin/env python |
| # |
| # 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. |
| |
| # for py2/py3 compatibility |
| from __future__ import absolute_import |
| from __future__ import print_function |
| from functools import reduce |
| |
| import datetime |
| import json |
| import os |
| import sys |
| import tempfile |
| |
| # Adds testrunner to the path hence it has to be imported at the beggining. |
| from . import base_runner |
| |
| from testrunner.local import utils |
| from testrunner.local.variants import ALL_VARIANTS |
| from testrunner.objects import predictable |
| from testrunner.testproc.execution import ExecutionProc |
| from testrunner.testproc.filter import StatusFileFilterProc, NameFilterProc |
| from testrunner.testproc.loader import LoadProc |
| from testrunner.testproc.seed import SeedProc |
| from testrunner.testproc.variant import VariantProc |
| |
| |
| VARIANTS = ['default'] |
| |
| MORE_VARIANTS = [ |
| 'jitless', |
| 'stress', |
| 'stress_js_bg_compile_wasm_code_gc', |
| 'stress_incremental_marking', |
| ] |
| |
| VARIANT_ALIASES = { |
| # The default for developer workstations. |
| 'dev': VARIANTS, |
| # Additional variants, run on all bots. |
| 'more': MORE_VARIANTS, |
| # Shortcut for the two above ('more' first - it has the longer running tests) |
| 'exhaustive': MORE_VARIANTS + VARIANTS, |
| # Additional variants, run on a subset of bots. |
| 'extra': ['nooptimization', 'future', 'no_wasm_traps', 'turboprop', |
| 'instruction_scheduling'], |
| } |
| |
| # Extra flags passed to all tests using the standard test runner. |
| EXTRA_DEFAULT_FLAGS = ['--testing-d8-test-runner'] |
| |
| GC_STRESS_FLAGS = ['--gc-interval=500', '--stress-compaction', |
| '--concurrent-recompilation-queue-length=64', |
| '--concurrent-recompilation-delay=500', |
| '--concurrent-recompilation', |
| '--stress-flush-bytecode', |
| '--wasm-code-gc', '--stress-wasm-code-gc'] |
| |
| RANDOM_GC_STRESS_FLAGS = ['--random-gc-interval=5000', |
| '--stress-compaction-random'] |
| |
| |
| PREDICTABLE_WRAPPER = os.path.join( |
| base_runner.BASE_DIR, 'tools', 'predictable_wrapper.py') |
| |
| |
| class StandardTestRunner(base_runner.BaseTestRunner): |
| def __init__(self, *args, **kwargs): |
| super(StandardTestRunner, self).__init__(*args, **kwargs) |
| |
| self.sancov_dir = None |
| self._variants = None |
| |
| @property |
| def framework_name(self): |
| return 'standard_runner' |
| |
| def _get_default_suite_names(self): |
| return ['default'] |
| |
| def _add_parser_options(self, parser): |
| parser.add_option('--novfp3', |
| help='Indicates that V8 was compiled without VFP3' |
| ' support', |
| default=False, action='store_true') |
| |
| # Variants |
| parser.add_option('--no-variants', '--novariants', |
| help='Deprecated. ' |
| 'Equivalent to passing --variants=default', |
| default=False, dest='no_variants', action='store_true') |
| parser.add_option('--variants', |
| help='Comma-separated list of testing variants;' |
| ' default: "%s"' % ','.join(VARIANTS)) |
| parser.add_option('--exhaustive-variants', |
| default=False, action='store_true', |
| help='Deprecated. ' |
| 'Equivalent to passing --variants=exhaustive') |
| |
| # Filters |
| parser.add_option('--slow-tests', default='dontcare', |
| help='Regard slow tests (run|skip|dontcare)') |
| parser.add_option('--pass-fail-tests', default='dontcare', |
| help='Regard pass|fail tests (run|skip|dontcare)') |
| parser.add_option('--quickcheck', default=False, action='store_true', |
| help=('Quick check mode (skip slow tests)')) |
| |
| # Stress modes |
| parser.add_option('--gc-stress', |
| help='Switch on GC stress mode', |
| default=False, action='store_true') |
| parser.add_option('--random-gc-stress', |
| help='Switch on random GC stress mode', |
| default=False, action='store_true') |
| parser.add_option('--random-seed-stress-count', default=1, type='int', |
| dest='random_seed_stress_count', |
| help='Number of runs with different random seeds. Only ' |
| 'with test processors: 0 means infinite ' |
| 'generation.') |
| |
| # Extra features. |
| parser.add_option('--time', help='Print timing information after running', |
| default=False, action='store_true') |
| |
| # Noop |
| parser.add_option('--cfi-vptr', |
| help='Run tests with UBSAN cfi_vptr option.', |
| default=False, action='store_true') |
| parser.add_option('--infra-staging', help='Use new test runner features', |
| dest='infra_staging', default=None, |
| action='store_true') |
| parser.add_option('--no-infra-staging', |
| help='Opt out of new test runner features', |
| dest='infra_staging', default=None, |
| action='store_false') |
| parser.add_option('--no-sorting', '--nosorting', |
| help='Don\'t sort tests according to duration of last' |
| ' run.', |
| default=False, dest='no_sorting', action='store_true') |
| parser.add_option('--no-presubmit', '--nopresubmit', |
| help='Skip presubmit checks (deprecated)', |
| default=False, dest='no_presubmit', action='store_true') |
| |
| # Unimplemented for test processors |
| parser.add_option('--sancov-dir', |
| help='Directory where to collect coverage data') |
| parser.add_option('--cat', help='Print the source of the tests', |
| default=False, action='store_true') |
| parser.add_option('--flakiness-results', |
| help='Path to a file for storing flakiness json.') |
| parser.add_option('--warn-unused', help='Report unused rules', |
| default=False, action='store_true') |
| parser.add_option('--report', default=False, action='store_true', |
| help='Print a summary of the tests to be run') |
| |
| def _process_options(self, options): |
| if options.sancov_dir: |
| self.sancov_dir = options.sancov_dir |
| if not os.path.exists(self.sancov_dir): |
| print('sancov-dir %s doesn\'t exist' % self.sancov_dir) |
| raise base_runner.TestRunnerError() |
| |
| if options.gc_stress: |
| options.extra_flags += GC_STRESS_FLAGS |
| |
| if options.random_gc_stress: |
| options.extra_flags += RANDOM_GC_STRESS_FLAGS |
| |
| if self.build_config.asan: |
| options.extra_flags.append('--invoke-weak-callbacks') |
| |
| if options.novfp3: |
| options.extra_flags.append('--noenable-vfp3') |
| |
| if options.no_variants: # pragma: no cover |
| print ('Option --no-variants is deprecated. ' |
| 'Pass --variants=default instead.') |
| assert not options.variants |
| options.variants = 'default' |
| |
| if options.exhaustive_variants: # pragma: no cover |
| # TODO(machenbach): Switch infra to --variants=exhaustive after M65. |
| print ('Option --exhaustive-variants is deprecated. ' |
| 'Pass --variants=exhaustive instead.') |
| # This is used on many bots. It includes a larger set of default |
| # variants. |
| # Other options for manipulating variants still apply afterwards. |
| assert not options.variants |
| options.variants = 'exhaustive' |
| |
| if options.quickcheck: |
| assert not options.variants |
| options.variants = 'stress,default' |
| options.slow_tests = 'skip' |
| options.pass_fail_tests = 'skip' |
| |
| if self.build_config.predictable: |
| options.variants = 'default' |
| options.extra_flags.append('--predictable') |
| options.extra_flags.append('--verify-predictable') |
| options.extra_flags.append('--no-inline-new') |
| # Add predictable wrapper to command prefix. |
| options.command_prefix = ( |
| [sys.executable, PREDICTABLE_WRAPPER] + options.command_prefix) |
| |
| # TODO(machenbach): Figure out how to test a bigger subset of variants on |
| # msan. |
| if self.build_config.msan: |
| options.variants = 'default' |
| |
| if options.variants == 'infra_staging': |
| options.variants = 'exhaustive' |
| |
| self._variants = self._parse_variants(options.variants) |
| |
| def CheckTestMode(name, option): # pragma: no cover |
| if option not in ['run', 'skip', 'dontcare']: |
| print('Unknown %s mode %s' % (name, option)) |
| raise base_runner.TestRunnerError() |
| CheckTestMode('slow test', options.slow_tests) |
| CheckTestMode('pass|fail test', options.pass_fail_tests) |
| if self.build_config.no_i18n: |
| base_runner.TEST_MAP['bot_default'].remove('intl') |
| base_runner.TEST_MAP['default'].remove('intl') |
| # TODO(machenbach): uncomment after infra side lands. |
| # base_runner.TEST_MAP['d8_default'].remove('intl') |
| |
| if options.time and not options.json_test_results: |
| # We retrieve the slowest tests from the JSON output file, so create |
| # a temporary output file (which will automatically get deleted on exit) |
| # if the user didn't specify one. |
| self._temporary_json_output_file = tempfile.NamedTemporaryFile( |
| prefix="v8-test-runner-") |
| options.json_test_results = self._temporary_json_output_file.name |
| |
| def _runner_flags(self): |
| return EXTRA_DEFAULT_FLAGS |
| |
| def _parse_variants(self, aliases_str): |
| # Use developer defaults if no variant was specified. |
| aliases_str = aliases_str or 'dev' |
| aliases = aliases_str.split(',') |
| user_variants = set(reduce( |
| list.__add__, [VARIANT_ALIASES.get(a, [a]) for a in aliases])) |
| |
| result = [v for v in ALL_VARIANTS if v in user_variants] |
| if len(result) == len(user_variants): |
| return result |
| |
| for v in user_variants: |
| if v not in ALL_VARIANTS: |
| print('Unknown variant: %s' % v) |
| print(' Available variants: %s' % ALL_VARIANTS) |
| print(' Available variant aliases: %s' % VARIANT_ALIASES.keys()); |
| raise base_runner.TestRunnerError() |
| assert False, 'Unreachable' |
| |
| def _setup_env(self): |
| super(StandardTestRunner, self)._setup_env() |
| |
| symbolizer_option = self._get_external_symbolizer_option() |
| |
| if self.sancov_dir: |
| os.environ['ASAN_OPTIONS'] = ':'.join([ |
| 'coverage=1', |
| 'coverage_dir=%s' % self.sancov_dir, |
| symbolizer_option, |
| 'allow_user_segv_handler=1', |
| ]) |
| |
| def _get_statusfile_variables(self, options): |
| variables = ( |
| super(StandardTestRunner, self)._get_statusfile_variables(options)) |
| |
| variables.update({ |
| 'gc_stress': options.gc_stress or options.random_gc_stress, |
| 'gc_fuzzer': options.random_gc_stress, |
| 'novfp3': options.novfp3, |
| }) |
| return variables |
| |
| def _do_execute(self, tests, args, options): |
| jobs = options.j |
| |
| print('>>> Running with test processors') |
| loader = LoadProc(tests) |
| results = self._create_result_tracker(options) |
| indicators = self._create_progress_indicators( |
| tests.test_count_estimate, options) |
| |
| outproc_factory = None |
| if self.build_config.predictable: |
| outproc_factory = predictable.get_outproc |
| execproc = ExecutionProc(jobs, outproc_factory) |
| sigproc = self._create_signal_proc() |
| |
| procs = [ |
| loader, |
| NameFilterProc(args) if args else None, |
| StatusFileFilterProc(options.slow_tests, options.pass_fail_tests), |
| VariantProc(self._variants), |
| StatusFileFilterProc(options.slow_tests, options.pass_fail_tests), |
| self._create_predictable_filter(), |
| self._create_shard_proc(options), |
| self._create_seed_proc(options), |
| sigproc, |
| ] + indicators + [ |
| results, |
| self._create_timeout_proc(options), |
| self._create_rerun_proc(options), |
| execproc, |
| ] |
| |
| self._prepare_procs(procs) |
| |
| loader.load_initial_tests(initial_batch_size=options.j * 2) |
| |
| # This starts up worker processes and blocks until all tests are |
| # processed. |
| execproc.run() |
| |
| for indicator in indicators: |
| indicator.finished() |
| |
| if tests.test_count_estimate: |
| percentage = float(results.total) / tests.test_count_estimate * 100 |
| else: |
| percentage = 0 |
| |
| print (('>>> %d base tests produced %d (%d%s)' |
| ' non-filtered tests') % ( |
| tests.test_count_estimate, results.total, percentage, '%')) |
| |
| print('>>> %d tests ran' % (results.total - results.remaining)) |
| |
| exit_code = utils.EXIT_CODE_PASS |
| if results.failed: |
| exit_code = utils.EXIT_CODE_FAILURES |
| if not results.total: |
| exit_code = utils.EXIT_CODE_NO_TESTS |
| |
| if options.time: |
| self._print_durations(options) |
| |
| # Indicate if a SIGINT or SIGTERM happened. |
| return max(exit_code, sigproc.exit_code) |
| |
| def _print_durations(self, options): |
| |
| def format_duration(duration_in_seconds): |
| duration = datetime.timedelta(seconds=duration_in_seconds) |
| time = (datetime.datetime.min + duration).time() |
| return time.strftime('%M:%S:') + '%03i' % int(time.microsecond / 1000) |
| |
| def _duration_results_text(test): |
| return [ |
| 'Test: %s' % test['name'], |
| 'Flags: %s' % ' '.join(test['flags']), |
| 'Command: %s' % test['command'], |
| 'Duration: %s' % format_duration(test['duration']), |
| ] |
| |
| assert os.path.exists(options.json_test_results) |
| with open(options.json_test_results, "r") as f: |
| output = json.load(f) |
| lines = [] |
| for test in output['slowest_tests']: |
| suffix = '' |
| if test.get('marked_slow') is False: |
| suffix = ' *' |
| lines.append( |
| '%s %s%s' % (format_duration(test['duration']), |
| test['name'], suffix)) |
| |
| # Slowest tests duration details. |
| lines.extend(['', 'Details:', '']) |
| for test in output['slowest_tests']: |
| lines.extend(_duration_results_text(test)) |
| print("\n".join(lines)) |
| |
| def _create_predictable_filter(self): |
| if not self.build_config.predictable: |
| return None |
| return predictable.PredictableFilterProc() |
| |
| def _create_seed_proc(self, options): |
| if options.random_seed_stress_count == 1: |
| return None |
| return SeedProc(options.random_seed_stress_count, options.random_seed, |
| options.j * 4) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(StandardTestRunner().execute()) |