blob: ff5839111068d514609a981993c3348413dce39f [file] [log] [blame]
#!/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())