Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

1
deps/v8/tools/testrunner/OWNERS vendored Normal file
View File

@ -0,0 +1 @@
file:../../INFRA_OWNERS

26
deps/v8/tools/testrunner/PRESUBMIT.py vendored Normal file
View File

@ -0,0 +1,26 @@
# 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.
# This line is 'magic' in that git-cl looks for it to decide whether to
# use Python3 instead of Python2 when running the code in this file.
USE_PYTHON3 = True
def _CommonChecks(input_api, output_api):
return input_api.RunTests(
input_api.canned_checks.GetUnitTestsRecursively(
input_api,
output_api,
input_api.os_path.join(input_api.PresubmitLocalPath()),
files_to_check=[r'.+_test\.py$'],
files_to_skip=[],
run_on_python2=False,
))
def CheckChangeOnUpload(input_api, output_api):
return _CommonChecks(input_api, output_api)
def CheckChangeOnCommit(input_api, output_api):
return _CommonChecks(input_api, output_api)

26
deps/v8/tools/testrunner/__init__.py vendored Normal file
View File

@ -0,0 +1,26 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

683
deps/v8/tools/testrunner/base_runner.py vendored Normal file
View File

@ -0,0 +1,683 @@
# 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.
from collections import OrderedDict, namedtuple
from functools import cached_property
from functools import reduce
from pathlib import Path
import json
import logging
import multiprocessing
import optparse
import os
import shlex
import sys
import traceback
from testrunner.build_config import BuildConfig
from testrunner.local import testsuite
from testrunner.local import utils
from testrunner.local.variants import REQUIRED_BUILD_VARIABLES
from testrunner.local.context import os_context
from testrunner.test_config import TestConfig
from testrunner.testproc import util
from testrunner.testproc.indicators import PROGRESS_INDICATORS
from testrunner.testproc.sigproc import SignalProc
from testrunner.utils.augmented_options import AugmentedOptions
DEFAULT_OUT_GN = Path('out.gn')
# 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.
# The mapping from names used here to GN targets (which must stay in sync)
# is defined in infra/mb/gn_isolate_map.pyl.
TEST_MAP = {
# This needs to stay in sync with group("v8_bot_default") in test/BUILD.gn.
"bot_default": [
"debugger",
"mjsunit",
"cctest",
"wasm-spec-tests",
"inspector",
"webkit",
"mkgrokdump",
"wasm-js",
"fuzzer",
"message",
"intl",
"unittests",
"wasm-api-tests",
],
# This needs to stay in sync with group("v8_default") in test/BUILD.gn.
"default": [
"debugger",
"mjsunit",
"cctest",
"wasm-spec-tests",
"inspector",
"mkgrokdump",
"wasm-js",
"fuzzer",
"message",
"intl",
"unittests",
"wasm-api-tests",
],
# This needs to stay in sync with group("v8_d8_default") in test/BUILD.gn.
"d8_default": [
"debugger",
"mjsunit",
"webkit",
"message",
"intl",
],
# This needs to stay in sync with "v8_optimize_for_size" in test/BUILD.gn.
"optimize_for_size": [
"debugger",
"mjsunit",
"cctest",
"inspector",
"webkit",
"intl",
],
"unittests": [
"unittests",
],
}
DEFAULT_FLAGS = {
'standard_runner': [
'--testing-d8-test-runner',
],
'num_fuzzer': [
'--fuzzing',
'--exit-on-contradictory-flags',
'--testing-d8-test-runner',
'--no-fail',
],
}
ModeConfig = namedtuple(
'ModeConfig', 'label flags timeout_scalefactor status_mode')
RELEASE_FLAGS = ["--nohard-abort"]
RELEASE_MODE = ModeConfig(
label='release',
flags=RELEASE_FLAGS,
timeout_scalefactor=1,
status_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.
TRY_RELEASE_MODE = ModeConfig(
label='release+dchecks',
flags=RELEASE_FLAGS,
timeout_scalefactor=4,
status_mode="debug",
)
# Set up logging. No need to log a date in timestamps as we can get that from
# test run start times.
logging.basicConfig(
format='%(asctime)s %(message)s',
datefmt='%H:%M:%S',
level=logging.WARNING,
)
class TestRunnerError(Exception):
pass
class BaseTestRunner(object):
def __init__(self, basedir=None):
self.v8_root = Path(__file__).absolute().parent.parent.parent
self.basedir = Path(basedir or self.v8_root)
self.outdir = None
self.build_config = None
self.mode_options = None
self.target_os = None
self.infra_staging = False
self.options = None
@property
def default_framework_name(self):
"""Default value for framework_name if not provided on the command line."""
raise NotImplementedError() # pragma: no cover
@cached_property
def framework_name(self):
"""String name of the framework flavor that tweaks runner behavior."""
assert self.options
if self.options.framework != 'default':
return self.options.framework
else:
return self.default_framework_name
def execute(self, sys_args=None):
if sys_args is None: # pragma: no cover
sys_args = sys.argv[1:]
parser = self._create_parser()
self.options, args = self._parse_args(parser, sys_args)
self.infra_staging = self.options.infra_staging
if self.options.swarming:
logging.getLogger().setLevel(logging.INFO)
# Swarming doesn't print how isolated commands are called. Lets make
# this less cryptic by printing it ourselves.
print(' '.join(sys.argv))
# TODO(machenbach): Print used Python version until we have switched to
# Python3 everywhere.
print('Running with:')
print(sys.version)
# Kill stray processes from previous tasks on swarming.
util.kill_processes_linux()
try:
self._load_build_config()
try:
self._process_default_options()
self._process_options()
except TestRunnerError:
parser.print_help()
raise
args = self._parse_test_args(args)
with os_context(self.target_os, self.options) as ctx:
self._setup_env()
names = self._args_to_suite_names(args)
tests = self._load_testsuite_generators(ctx, names)
print(">>> Running tests for %s.%s" % (self.build_config.arch,
self.mode_options.label))
return self._do_execute(tests, args, ctx)
except TestRunnerError:
traceback.print_exc()
return utils.EXIT_CODE_INTERNAL_ERROR
except KeyboardInterrupt:
return utils.EXIT_CODE_INTERRUPTED
except Exception:
traceback.print_exc()
return utils.EXIT_CODE_INTERNAL_ERROR
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):
framework_choices = ('default', 'standard_runner', 'num_fuzzer')
parser.add_option('--framework',
type='choice',
choices=framework_choices,
default='default',
help=f'Choose framework from: {framework_choices}')
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("--arch",
help="The architecture to run tests for")
parser.add_option("--shell-dir", help="DEPRECATED! Executables from build "
"directory will be used")
parser.add_option("--test-root", help="Root directory of the test suites",
default=self.basedir / 'test')
parser.add_option("--total-timeout-sec", default=0, type="int",
help="How long should fuzzer run")
parser.add_option("--swarming", default=False, action="store_true",
help="Indicates running test driver on swarming.")
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("-j", help="The number of parallel tasks to run",
default=0, type=int)
parser.add_option("-d", "--device",
help="The device ID to run Android tests on. If not "
"given it will be autodetected.")
# Shard
parser.add_option("--shard-count", default=1, type=int,
help="Split tests into this number of shards")
parser.add_option("--shard-run", default=1, type=int,
help="Run this shard from the split up tests.")
# Progress
parser.add_option("-p", "--progress",
choices=list(PROGRESS_INDICATORS.keys()), default="mono",
help="The style of progress indicator (verbose, dots, "
"color, mono)")
parser.add_option("--json-test-results",
help="Path to a file for storing json results.")
parser.add_option("--log-system-memory",
help="Path to a file for storing system memory stats.")
parser.add_option("--log-test-schedule",
help="Path to a file for streaming the test schedule to.")
parser.add_option('--slow-tests-cutoff', type="int", default=100,
help='Collect N slowest tests')
parser.add_option("--exit-after-n-failures", type="int", default=100,
help="Exit after the first N failures instead of "
"running all tests. Pass 0 to disable this feature.")
parser.add_option("--ci-test-completion",
help="Path to a file for logging test completion in the "
"context of CI progress indicator. Ignored if "
"progress indicator is other than 'ci'.")
# Rerun
parser.add_option("--rerun-failures-count", default=0, type=int,
help="Number of times to rerun each failing test case. "
"Very slow tests will be rerun only once.")
parser.add_option("--rerun-failures-max", default=100, type=int,
help="Maximum number of failing test cases to rerun")
# Test config
parser.add_option("--command-prefix", default="",
help="Prepended to each shell command used to run a test")
parser.add_option('--dont-skip-slow-simulator-tests',
help='Don\'t skip more slow tests when using a'
' simulator.', default=False, action='store_true',
dest='dont_skip_simulator_slow_tests')
parser.add_option("--extra-flags", action="append", default=[],
help="Additional flags to pass to each test command")
parser.add_option("--isolates", action="store_true", default=False,
help="Whether to test isolates")
parser.add_option("--no-harness", "--noharness",
default=False, action="store_true",
help="Run without test harness of a given suite")
parser.add_option("--random-seed", default=0, type=int,
help="Default seed for initializing random generator")
parser.add_option("--run-skipped", help="Also run skipped tests.",
default=False, action="store_true")
parser.add_option("-t", "--timeout", default=60, type=int,
help="Timeout for single test in seconds")
parser.add_option("-v", "--verbose", default=False, action="store_true",
help="Verbose output")
parser.add_option('--regenerate-expected-files', default=False, action='store_true',
help='Regenerate expected files')
# TODO(machenbach): Temporary options for rolling out new test runner
# features.
parser.add_option("--mastername", default='',
help="Mastername property from infrastructure. Not "
"setting this option indicates manual usage.")
parser.add_option("--buildername", default='',
help="Buildername property from infrastructure. Not "
"setting this option indicates manual usage.")
def _add_parser_options(self, parser):
pass # pragma: no cover
def _parse_args(self, parser, sys_args):
options, args = parser.parse_args(sys_args)
options.test_root = Path(options.test_root)
options.outdir = Path(options.outdir)
if options.arch and ',' in options.arch: # pragma: no cover
print('Multiple architectures are deprecated')
raise TestRunnerError()
# We write a test schedule and the system memory stats by default
# alongside json test results on bots.
if options.json_test_results:
result_dir = Path(options.json_test_results).parent
if not options.log_test_schedule:
options.log_test_schedule = result_dir / 'test_schedule.log'
if not options.log_system_memory:
options.log_system_memory = result_dir / 'memory_stats.log'
return AugmentedOptions.augment(options), args
def _load_build_config(self):
for outdir in self._possible_outdirs():
try:
self.build_config = self._do_load_build_config(outdir)
# 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 = outdir
break
except TestRunnerError:
pass
if not self.build_config: # pragma: no cover
print('Failed to load build config')
raise TestRunnerError
print('Build found: %s' % self.outdir)
# Represents the OS where tests are run on. Same as host OS except for
# Android and iOS, which are determined by build output.
if self.build_config.is_android:
self.target_os = 'android'
elif self.build_config.is_ios:
self.target_os = 'ios'
else:
self.target_os = utils.GuessOS()
# Verify integrity between build variables and variant configs.
self.build_config.ensure_vars(REQUIRED_BUILD_VARIABLES)
def _do_load_build_config(self, outdir):
build_config_path = outdir / "v8_build_config.json"
if not build_config_path.exists():
if self.options.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: # pragma: no cover
print("%s exists but contains invalid json. Is your build up-to-date?"
% build_config_path)
raise TestRunnerError()
return BuildConfig(build_config_json)
# Returns possible build paths in order:
# gn
# outdir
# outdir on bots
def _possible_outdirs(self):
def outdirs():
if self.options.gn:
yield self._get_gn_outdir()
return
yield self.options.outdir
if self.options.outdir.name != 'build':
yield self.options.outdir / 'build'
for outdir in outdirs():
yield self.basedir / outdir
def _get_gn_outdir(self):
gn_out_dir = self.basedir / DEFAULT_OUT_GN
latest_timestamp = -1
latest_config = None
for gn_config_dir in gn_out_dir.iterdir():
if not gn_config_dir.is_dir():
continue
if gn_config_dir.stat().st_mtime > latest_timestamp:
latest_timestamp = gn_config_dir.stat().st_mtime
latest_config = gn_config_dir.name
if latest_config:
print(">>> Latest GN build found: %s" % latest_config)
return DEFAULT_OUT_GN / latest_config
def _custom_debug_mode(self):
custom_debug_flags = ["--nohard-abort"]
if self.build_config.verify_heap:
custom_debug_flags += ["--verify-heap"]
if self.build_config.slow_dchecks:
custom_debug_flags += ["--enable-slow-asserts"]
return ModeConfig(
label='debug',
flags=custom_debug_flags,
timeout_scalefactor=4,
status_mode="debug",
)
def _process_default_options(self):
if self.build_config.debugging_features:
self.mode_options = self._custom_debug_mode()
elif self.build_config.dcheck_always_on:
self.mode_options = TRY_RELEASE_MODE
else:
self.mode_options = RELEASE_MODE
if self.options.arch and self.options.arch != self.build_config.arch:
print('--arch value (%s) inconsistent with build config (%s).' % (
self.options.arch, self.build_config.arch))
raise TestRunnerError()
if self.options.shell_dir: # pragma: no cover
print('Warning: --shell-dir is deprecated. Searching for executables in '
'build directory (%s) instead.' % self.outdir)
if self.options.j == 0:
if self.build_config.is_android:
# Adb isn't happy about multi-processed file pushing.
self.options.j = 1
else:
self.options.j = multiprocessing.cpu_count()
self.options.command_prefix = shlex.split(self.options.command_prefix)
self.options.extra_flags = sum(list(map(shlex.split, self.options.extra_flags)), [])
self.options.extra_d8_flags = []
def _process_options(self):
pass # pragma: no cover
def _setup_env(self):
# Use the v8 root as cwd as some test cases use "load" with relative paths.
os.chdir(self.basedir)
# 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 self.build_config.component_build:
# Some abseil symbols are observed as defined more than once in
# component builds.
asan_options += ['detect_odr_violation=0']
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')
if utils.GuessOS() == 'windows':
# https://crbug.com/967663
asan_options.append('detect_stack_use_after_return=0')
os.environ['ASAN_OPTIONS'] = ":".join(asan_options)
if self.build_config.cfi:
os.environ['UBSAN_OPTIONS'] = ":".join([
'print_stacktrace=1',
'print_summary=1',
'symbolize=1',
symbolizer_option,
])
if self.build_config.ubsan:
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 = (
self.basedir / '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 = (
self.basedir / 'third_party' / 'llvm-build' / 'Release+Asserts' /
'bin' / 'llvm-symbolizer')
if utils.IsWindows():
external_symbolizer_path = external_symbolizer_path.with_suffix('.exe')
# Quote, because sanitizers might confuse colon as option separator.
external_symbolizer_path = f'"{external_symbolizer_path}"'
return f'external_symbolizer_path={external_symbolizer_path}'
def _parse_test_args(self, args):
if not args:
args = self._get_default_suite_names()
# Expand arguments with grouped tests. The args should reflect the list
# of suites as otherwise filters would break.
def expand_test_group(name):
return TEST_MAP.get(name, [name])
return reduce(list.__add__, list(map(expand_test_group, args)), [])
def _args_to_suite_names(self, args):
# Use default tests if no test configuration was provided at the cmd line.
all_names = set(utils.GetSuitePaths(self.options.test_root))
args_names = OrderedDict([(arg.split('/')[0], None) for arg in args]) # set
return [name for name in args_names if name in all_names]
def _get_default_suite_names(self):
return [] # pragma: no cover
def _load_testsuite_generators(self, ctx, names):
test_config = self._create_test_config()
variables = self._get_statusfile_variables(ctx)
print('>>> Statusfile variables:')
print(', '.join(f'{k}={v}' for k, v in sorted(variables.items())))
# Head generator with no elements
test_chain = testsuite.TestGenerator(0, [], [], [])
for name in names:
if self.options.verbose:
print('>>> Loading test suite: %s' % name)
suite = testsuite.TestSuite.Load(
ctx, self.options.test_root / name, test_config)
if self._is_testsuite_supported(suite):
tests = suite.load_tests_from_disk(variables)
test_chain.merge(tests)
return test_chain
def _is_testsuite_supported(self, suite):
"""A predicate that can be overridden to filter out unsupported TestSuite
instances (see NumFuzzer for usage)."""
return True
@property
def _no_simd_hardware(self):
# TODO(liviurau): Add some tests.
# We try to find all the reasons why we have no_simd.
if any(i in self.options.extra_flags for i in [
'--noenable-sse3', '--no-enable-sse3', '--noenable-ssse3',
'--no-enable-ssse3', '--noenable-sse4-1', '--no-enable-sse4_1'
]):
return True
# Set no_simd_hardware on architectures without Simd enabled.
if (self.build_config.arch == 'mips64el' and
not self.build_config.simd_mips):
return True
if self.build_config.arch == 'loong64':
return True
# S390 hosts without VEF1 do not support Simd.
if (self.build_config.arch == 's390x' and
not self.build_config.simulator_run and
not utils.IsS390SimdSupported()):
return True
# Ppc64 processors earlier than POWER9 do not support Simd instructions
if (self.build_config.arch == 'ppc64' and
not self.build_config.simulator_run and
utils.GuessPowerProcessorVersion() < 9):
return True
return False
def _get_statusfile_variables(self, context):
"""Returns all attributes accessible in status files.
All build-time flags from V8's BUILD.gn file as defined by the action
v8_dump_build_config can be accessed in status files.
"""
variables = dict(self.build_config.items())
variables.update({
"byteorder": sys.byteorder,
"deopt_fuzzer": False,
"device_type": context.device_type,
"endurance_fuzzer": False,
"gc_fuzzer": False,
"gc_stress": False,
"isolates": self.options.isolates,
"interrupt_fuzzer": False,
"mode": self.mode_options.status_mode,
"no_harness": self.options.no_harness,
"no_simd_hardware": self._no_simd_hardware,
"novfp3": False,
"optimize_for_size": "--optimize-for-size" in self.options.extra_flags,
"simulator_run": variables["simulator_run"]
and not self.options.dont_skip_simulator_slow_tests,
"system": self.target_os,
})
return variables
def _runner_flags(self):
"""Extra default flags specific to the test runner implementation."""
return DEFAULT_FLAGS[self.framework_name]
def _create_test_config(self):
shard_id, shard_count = self.options.shard_info
timeout = self.build_config.timeout_scalefactor(
self.options.timeout * self.mode_options.timeout_scalefactor)
return TestConfig(
command_prefix=self.options.command_prefix,
extra_flags=self.options.extra_flags,
extra_d8_flags=self.options.extra_d8_flags,
framework_name=self.framework_name,
isolates=self.options.isolates,
log_process_stats=self.options.json_test_results,
mode_flags=self.mode_options.flags + self._runner_flags(),
no_harness=self.options.no_harness,
noi18n=not self.build_config.i18n,
random_seed=self.options.random_seed,
run_skipped=self.options.run_skipped,
shard_count=shard_count,
shard_id=shard_id,
shell_dir=self.outdir,
target_os=self.target_os,
timeout=timeout,
verbose=self.options.verbose,
regenerate_expected_files=self.options.regenerate_expected_files,
)
# TODO(majeski): remove options & args parameters
def _do_execute(self, suites, args):
raise NotImplementedError() # pragma: no coverage
def _prepare_procs(self, procs):
for i in range(0, len(procs) - 1):
procs[i].connect_to(procs[i + 1])
def _create_signal_proc(self):
return SignalProc()

View File

@ -0,0 +1,70 @@
# Copyright 2022 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.
# Increase the timeout for these:
SLOW_ARCHS = [
"arm", "arm64", "mips64", "mips64el", "s390x", "riscv32", "riscv64",
"loong64"
]
# Timeout scale factor per build flag.
SCALE_FACTOR = dict(
full_debug=4,
lite_mode=2,
tsan=2,
use_sanitizer=1.5,
verify_predictable=8,
)
INITIALIZATION_ERROR = f"""
Missing property '%s'. If you see this error in testing, you might need
to add the property to tools/testrunner/testdata/v8_build_config.json. If
you see this error in production, ensure to add the property to the
v8_dump_build_config action in V8's top-level BUILD.gn file.
"""
class BuildConfig(object):
"""Enables accessing all build-time flags as set in V8's BUILD.gn file.
All flags are auto-generated based on the output of V8's
v8_dump_build_config action.
"""
def __init__(self, build_config):
for key, value in build_config.items():
setattr(self, key, value)
self.keys = list(build_config.keys())
bool_options = [key for key, value in self.items() if value is True]
string_options = [
f'{key}="{value}"'
for key, value in self.items() if value and isinstance(value, str)]
self._str_rep = ', '.join(sorted(bool_options + string_options))
def items(self):
for key in self.keys:
yield key, getattr(self, key)
def ensure_vars(self, build_vars):
for var in build_vars:
if var not in self.keys:
raise Exception(INITIALIZATION_ERROR % var)
def timeout_scalefactor(self, initial_factor):
"""Increases timeout for slow build configurations."""
result = initial_factor
for key, value in SCALE_FACTOR.items():
try:
if getattr(self, key):
result *= value
except AttributeError:
raise Exception(INITIALIZATION_ERROR % key)
if self.arch in SLOW_ARCHS:
result *= 4.5
return result
def __str__(self):
return self._str_rep

View File

@ -0,0 +1,26 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,224 @@
# Copyright 2018 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.
"""
Wrapper around the Android device abstraction from src/build/android.
"""
import logging
import os
import sys
import re
from pathlib import Path
BASE_DIR = os.path.normpath(
os.path.join(os.path.dirname(__file__), '..', '..', '..'))
ANDROID_DIR = os.path.join(BASE_DIR, 'build', 'android')
DEVICE_DIR = '/data/local/tmp/v8/'
FILTER_LINES_EXPRESSIONS = [
r'WARNING: linker: .+ unsupported flags DT_FLAGS_1=0x8000001',
r'WARNING: linker: .+ unused DT entry:.*type 0x70000001 arg 0x0.*',
]
FILTER_LINES_EXPRESSIONS = [re.compile(exp) for exp in FILTER_LINES_EXPRESSIONS]
class TimeoutException(Exception):
def __init__(self, timeout, output=None):
self.timeout = timeout
self.output = output
class CommandFailedException(Exception):
def __init__(self, status, output):
self.status = status
self.output = output
class Driver(object):
__instance = None
@staticmethod
def instance(device):
if not Driver.__instance:
Driver.__instance = Driver(device)
return Driver.__instance
"""Helper class to execute shell commands on an Android device."""
def __init__(self, device=None):
assert os.path.exists(ANDROID_DIR)
sys.path.insert(0, ANDROID_DIR)
# We import the dependencies only on demand, so that this file can be
# imported unconditionally.
import devil_chromium
from devil.android import device_errors # pylint: disable=import-error
from devil.android import device_utils # pylint: disable=import-error
from devil.android.perf import cache_control # pylint: disable=import-error
from devil.android.perf import perf_control # pylint: disable=import-error
global cache_control
global device_errors
global perf_control
devil_chromium.Initialize()
# Find specified device or a single attached device if none was specified.
# In case none or multiple devices are attached, this raises an exception.
self.device = device_utils.DeviceUtils.HealthyDevices(
retries=5, enable_usb_resets=True, device_arg=device)[0]
# Retrieve device parameters.
product_prop = 'getprop ro.build.product'
self.device_type = self.device.adb.Shell(product_prop).rstrip('\n')
assert self.device_type, 'No device type found in android environment.'
# This remembers what we have already pushed to the device.
self.pushed = set()
def tear_down(self):
"""Clean up files after running all tests."""
self.device.RemovePath(DEVICE_DIR, force=True, recursive=True)
def push_file(self, host_dir, file_name, target_rel='.',
skip_if_missing=False):
"""Push a single file to the device (cached).
Args:
host_dir: Absolute parent directory of the file to push.
file_name: Name of the file to push.
target_rel: Parent directory of the target location on the device
(relative to the device's base dir for testing).
skip_if_missing: Keeps silent about missing files when set. Otherwise logs
error.
"""
# TODO(sergiyb): Implement this method using self.device.PushChangedFiles to
# avoid accessing low-level self.device.adb.
file_on_host = os.path.join(host_dir, file_name)
# Only push files not yet pushed in one execution.
if file_on_host in self.pushed:
return
file_on_device_tmp = os.path.join(DEVICE_DIR, '_tmp_', file_name)
file_on_device = os.path.join(DEVICE_DIR, target_rel, file_name)
folder_on_device = os.path.dirname(file_on_device)
# Only attempt to push files that exist.
if not os.path.exists(file_on_host):
if not skip_if_missing:
logging.critical('Missing file on host: %s' % file_on_host)
return
# Work-around for 'text file busy' errors. Push the files to a temporary
# location and then copy them with a shell command.
output = self.device.adb.Push(file_on_host, file_on_device_tmp)
# Success looks like this: '3035 KB/s (12512056 bytes in 4.025s)'.
# Errors look like this: 'failed to copy ... '.
if output and not re.search('^[0-9]', output.splitlines()[-1]):
logging.critical('PUSH FAILED: ' + output)
self.device.adb.Shell('mkdir -p %s' % folder_on_device)
self.device.adb.Shell('cp %s %s' % (file_on_device_tmp, file_on_device))
self.pushed.add(file_on_host)
def push_files_rec(self, host_dir, target_rel='.'):
"""As above, but push the whole directory tree under host_dir."""
root = Path(host_dir)
for entry in root.rglob('*'):
if entry.is_file():
self.push_file(host_dir, entry.relative_to(root), target_rel)
def push_executable(self, shell_dir, target_dir, binary):
"""Push files required to run a V8 executable.
Args:
shell_dir: Absolute parent directory of the executable on the host.
target_dir: Parent directory of the executable on the device (relative to
devices' base dir for testing).
binary: Name of the binary to push.
"""
self.push_file(shell_dir, binary, target_dir)
# Push external startup data. Backwards compatible for revisions where
# these files didn't exist. Or for bots that don't produce these files.
self.push_file(
shell_dir,
'natives_blob.bin',
target_dir,
skip_if_missing=True,
)
self.push_file(
shell_dir,
'snapshot_blob.bin',
target_dir,
skip_if_missing=True,
)
self.push_file(
shell_dir,
'icudtl.dat',
target_dir,
skip_if_missing=True,
)
def filter_line(self, line):
for exp in FILTER_LINES_EXPRESSIONS:
if exp.match(line):
return False
return True
def run(self, target_dir, binary, args, rel_path, timeout, env=None,
logcat_file=False):
"""Execute a command on the device's shell.
Args:
target_dir: Parent directory of the executable on the device (relative to
devices' base dir for testing).
binary: Name of the binary.
args: List of arguments to pass to the binary.
rel_path: Relative path on device to use as CWD.
timeout: Timeout in seconds.
env: The environment variables with which the command should be run.
logcat_file: File into which to stream adb logcat log.
"""
binary_on_device = os.path.join(DEVICE_DIR, target_dir, binary)
cmd = [binary_on_device] + args
def run_inner():
try:
output = self.device.RunShellCommand(
cmd,
cwd=os.path.join(DEVICE_DIR, rel_path),
check_return=True,
env=env,
timeout=timeout,
retries=0,
)
# Return output without linker warnings (https://crbug.com/1454414).
return '\n'.join(filter(self.filter_line, output))
except device_errors.AdbCommandFailedError as e:
raise CommandFailedException(e.status, e.output)
except device_errors.CommandTimeoutError as e:
raise TimeoutException(timeout, e.output)
if logcat_file:
with self.device.GetLogcatMonitor(output_file=logcat_file) as logmon:
result = run_inner()
logmon.Close()
return result
else:
return run_inner()
def drop_ram_caches(self):
"""Drop ran caches on device."""
cache = cache_control.CacheControl(self.device)
cache.DropRamCaches()
def set_high_perf_mode(self):
"""Set device into high performance mode."""
perf = perf_control.PerfControl(self.device)
perf.SetHighPerfMode()
def set_default_perf_mode(self):
"""Set device into default performance mode."""
perf = perf_control.PerfControl(self.device)
perf.SetDefaultPerfMode()

View File

@ -0,0 +1,457 @@
# 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.
from contextlib import contextmanager
from pathlib import Path
import logging
import os
import re
import signal
import subprocess
import sys
import threading
import time
from ..local.android import (Driver, CommandFailedException, TimeoutException)
from ..local.pool import AbortException
from ..objects import output
from .process_utils import ProcessStats, EMPTY_PROCESS_LOGGER, PROCESS_LOGGER
BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent
SEM_INVALID_VALUE = -1
SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
def setup_testing():
"""For testing only: We use threading under the hood instead of
multiprocessing to make coverage work. Signal handling is only supported
in the main thread, so we disable it for testing.
"""
signal.signal = lambda *_: None
@contextmanager
def handle_sigterm(process, abort_fun, enabled):
"""Call`abort_fun` on sigterm and restore previous handler to prevent
erroneous termination of an already terminated process.
Args:
process: The process to terminate.
abort_fun: Function taking two parameters: the process to terminate and
an array with a boolean for storing if an abort occured.
enabled: If False, this wrapper will be a no-op.
"""
# Variable to communicate with the signal handler.
abort_occured = [False]
if enabled:
# TODO(https://crbug.com/v8/13113): There is a race condition on
# signal handler registration. In rare cases, the SIGTERM for stopping
# a worker might be caught right after a long running process has been
# started (or logic that starts it isn't interrupted), but before the
# registration of the abort_fun. In this case, process.communicate will
# block until the process is done.
previous = signal.getsignal(signal.SIGTERM)
def handler(signum, frame):
abort_fun(process, abort_occured)
if previous and callable(previous):
# Call default signal handler. If this command is called from a worker
# process, its signal handler will gracefully stop processing.
previous(signum, frame)
signal.signal(signal.SIGTERM, handler)
try:
yield
finally:
if enabled:
signal.signal(signal.SIGTERM, previous)
if abort_occured[0]:
raise AbortException()
class BaseCommand(object):
def __init__(self, shell, args=None, cmd_prefix=None, timeout=60, env=None,
verbose=False, test_case=None, handle_sigterm=False,
log_process_stats=False):
"""Initialize the command.
Args:
shell: The name of the executable (e.g. d8).
args: List of args to pass to the executable.
cmd_prefix: Prefix of command (e.g. a wrapper script).
timeout: Timeout in seconds.
env: Environment dict for execution.
verbose: Print additional output.
test_case: Test case reference.
handle_sigterm: Flag indicating if SIGTERM will be used to terminate the
underlying process. Should not be used from the main thread, e.g. when
using a command to list tests.
log_process_stats: Indicate if we want to probe for process statistics like
memory consumption.
"""
assert(timeout > 0)
self.shell = Path(shell)
self.args = list(map(str, args or []))
self.cmd_prefix = cmd_prefix or []
self.timeout = timeout
self.env = env or {}
self.verbose = verbose
self.handle_sigterm = handle_sigterm
if log_process_stats:
self.process_logger = self.get_process_logger()
else:
self.process_logger = EMPTY_PROCESS_LOGGER
def _result_overrides(self, returncode):
pass
@contextmanager
def log_errors(self):
try:
yield
except:
logging.exception(f'Error executing: {self}\n')
raise
def get_process_logger(self):
return EMPTY_PROCESS_LOGGER
def execute(self):
if self.verbose:
print('# %s' % self)
process = self._start_process()
with handle_sigterm(process, self._abort, self.handle_sigterm):
# Variable to communicate with the timer.
timeout_occured = [False]
timer = threading.Timer(
self.timeout, self._abort, [process, timeout_occured])
timer.start()
start_time = time.time()
with self.log_errors():
with self.process_logger.log_stats(process) as stats:
stdout, stderr = process.communicate()
end_time = time.time()
timer.cancel()
self._result_overrides(process)
return output.Output(
process.returncode,
timeout_occured[0],
stdout.decode('utf-8', 'replace'),
stderr.decode('utf-8', 'replace'),
process.pid,
start_time,
end_time,
stats=stats,
)
def _start_process(self):
with self.log_errors():
return subprocess.Popen(
args=self._get_popen_args(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self._get_env(),
)
def _get_popen_args(self):
return self._to_args_list()
def _get_env(self):
env = os.environ.copy()
env.update(self.env)
# GTest shard information is read by the V8 tests runner. Make sure it
# doesn't leak into the execution of gtests we're wrapping. Those might
# otherwise apply a second level of sharding and as a result skip tests.
env.pop('GTEST_TOTAL_SHARDS', None)
env.pop('GTEST_SHARD_INDEX', None)
return env
def _kill_process(self, process):
raise NotImplementedError()
def _abort(self, process, abort_called):
abort_called[0] = True
started_as = self.to_string(relative=True)
process_text = 'process %d started as:\n %s\n' % (process.pid, started_as)
try:
logging.warning('Attempting to kill %s', process_text)
self._kill_process(process)
except OSError:
logging.exception('Unruly %s', process_text)
def __str__(self):
return self.to_string()
def to_string(self, relative=False):
def escape(part):
# Escape spaces. We may need to escape more characters for this to work
# properly.
if ' ' in part:
return '"%s"' % part
return part
parts = map(escape, self._to_args_list())
cmd = ' '.join(parts)
if relative:
cmd = cmd.replace(os.getcwd() + os.sep, '')
return cmd
def _to_args_list(self):
return list(map(str, self.cmd_prefix + [self.shell])) + self.args
class DesktopCommand(BaseCommand):
def get_process_logger(self):
return PROCESS_LOGGER
class PosixCommand(DesktopCommand):
# TODO(machenbach): Use base process start without shell once
# https://crbug.com/v8/8889 is resolved.
def _start_process(self):
def wrapped(arg):
if set('() \'"') & set(arg):
return "'%s'" % arg.replace("'", "'\"'\"'")
return arg
try:
return subprocess.Popen(
args=' '.join(map(wrapped, self._get_popen_args())),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self._get_env(),
shell=True,
# Make the new shell create its own process group. This allows to kill
# all spawned processes reliably (https://crbug.com/v8/8292).
preexec_fn=os.setsid,
)
except Exception as e:
sys.stderr.write('Error executing: %s\n' % self)
raise e
def _kill_process(self, process):
# Kill the whole process group (PID == GPID after setsid).
# First try a soft term to allow some feedback
os.killpg(process.pid, signal.SIGTERM)
# Give the process some time to cleanly terminate.
time.sleep(0.1)
# Forcefully kill processes.
os.killpg(process.pid, signal.SIGKILL)
class IOSCommand(BaseCommand):
def __init__(self,
shell,
args=None,
cmd_prefix=None,
timeout=120,
env=None,
verbose=False,
test_case=None,
handle_sigterm=False,
log_process_stats=False):
"""Initialize the command and set a large enough timeout required for runs
through the iOS Simulator.
"""
super(IOSCommand, self).__init__(
shell,
args=args,
cmd_prefix=cmd_prefix,
timeout=timeout,
env=env,
verbose=verbose,
handle_sigterm=handle_sigterm,
log_process_stats=log_process_stats)
def _result_overrides(self, process):
# TODO(crbug.com/1445694): if iossim returns with code 65, force a
# successful exit instead.
if (process.returncode == 65):
process.returncode = 0
def _start_process(self):
try:
return subprocess.Popen(
args=self._get_popen_args(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self._get_env(),
shell=True,
# Make the new shell create its own process group. This allows to kill
# all spawned processes reliably (https://crbug.com/v8/8292).
preexec_fn=os.setsid,
)
except Exception as e:
sys.stderr.write('Error executing: %s\n' % self)
raise e
def _kill_process(self, process):
# Kill the whole process group (PID == GPID after setsid).
# First try a soft term to allow some feedback
os.killpg(process.pid, signal.SIGTERM)
# Give the process some time to cleanly terminate.
time.sleep(0.1)
# Forcefully kill processes.
os.killpg(process.pid, signal.SIGKILL)
def _to_args_list(self):
return list(map(str, self.cmd_prefix + [self.shell]))
def terminate_process_windows(process):
try:
import _winapi
handle = _winapi.OpenProcess(
_winapi.PROCESS_ALL_ACCESS, False, process.pid)
_winapi.TerminateProcess(handle, 1)
_winapi.CloseHandle(handle)
except Exception:
logging.exception('Problem terminating process %s', process.pid)
class WindowsCommand(DesktopCommand):
def _start_process(self, **kwargs):
# Try to change the error mode to avoid dialogs on fatal errors. Don't
# touch any existing error mode flags by merging the existing error mode.
# See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
def set_error_mode(mode):
prev_error_mode = SEM_INVALID_VALUE
try:
import ctypes
prev_error_mode = (
ctypes.windll.kernel32.SetErrorMode(mode)) #@UndefinedVariable
except ImportError:
pass
return prev_error_mode
error_mode = SEM_NOGPFAULTERRORBOX
prev_error_mode = set_error_mode(error_mode)
set_error_mode(error_mode | prev_error_mode)
try:
return super(WindowsCommand, self)._start_process(**kwargs)
finally:
if prev_error_mode != SEM_INVALID_VALUE:
set_error_mode(prev_error_mode)
def _get_popen_args(self):
return subprocess.list2cmdline(self._to_args_list())
def _kill_process(self, process):
terminate_process_windows(process)
class AndroidCommand(BaseCommand):
# This must be initialized before creating any instances of this class.
driver = None
def __init__(self, shell, args=None, cmd_prefix=None, timeout=60, env=None,
verbose=False, test_case=None, handle_sigterm=False,
log_process_stats=False):
"""Initialize the command and all files that need to be pushed to the
Android device.
"""
super(AndroidCommand, self).__init__(
shell, args=args, cmd_prefix=cmd_prefix, timeout=timeout, env=env,
verbose=verbose, handle_sigterm=handle_sigterm,
log_process_stats=log_process_stats)
rel_args, files_from_args = args_with_relative_paths(args)
self.args = rel_args
test_case_resources = test_case.get_android_resources() if test_case else []
self.files_to_push = test_case_resources + files_from_args
def execute(self, **additional_popen_kwargs):
"""Execute the command on the device.
This pushes all required files to the device and then runs the command.
"""
if self.verbose:
print('# %s' % self)
shell_name = self.shell.name
shell_dir = self.shell.parent
self.driver.push_executable(shell_dir, 'bin', shell_name)
self.push_test_resources()
start_time = time.time()
return_code = 0
timed_out = False
try:
stdout = self.driver.run(
'bin', shell_name, self.args, '.', self.timeout, self.env)
except CommandFailedException as e:
return_code = e.status
stdout = e.output
except TimeoutException as e:
return_code = 1
timed_out = True
# Sadly the Android driver doesn't provide output on timeout.
stdout = ''
end_time = time.time()
return output.Output(
return_code,
timed_out,
stdout,
'', # No stderr available.
-1, # No pid available.
start_time,
end_time,
)
def push_test_resources(self):
for abs_file in self.files_to_push:
abs_dir = abs_file.parent
file_name = abs_file.name
rel_dir = abs_dir.relative_to(BASE_DIR)
self.driver.push_file(abs_dir, file_name, rel_dir)
def args_with_relative_paths(args):
base_dir_str = re.escape(BASE_DIR.as_posix())
rel_args = []
files_to_push = []
find_path_re = re.compile(r'.*(%s/[^\'"]+).*' % base_dir_str)
for arg in (args or []):
match = find_path_re.match(str(arg))
if match:
files_to_push.append(Path(match.group(1)).resolve())
rel_args.append(re.sub(r'(.*)%s/(.*)' % base_dir_str, r'\1\2', str(arg)))
return rel_args, files_to_push
Command = None
# Deprecated : use context.os_context
def setup(target_os, device):
"""Set the Command class to the OS-specific version."""
global Command
if target_os == 'android':
AndroidCommand.driver = Driver.instance(device)
Command = AndroidCommand
elif target_os == 'ios':
Command = IOSCommand
elif target_os == 'windows':
Command = WindowsCommand
else:
Command = PosixCommand
# Deprecated : use context.os_context
def tear_down():
"""Clean up after using commands."""
if Command == AndroidCommand:
AndroidCommand.driver.tear_down()

View File

@ -0,0 +1,138 @@
# Copyright 2022 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 os
import signal
import subprocess
import sys
from contextlib import contextmanager
from ..local.android import Driver
from .command import (
AndroidCommand,
IOSCommand,
PosixCommand,
WindowsCommand,
terminate_process_windows)
from .pool import DefaultExecutionPool
from .process_utils import EMPTY_PROCESS_LOGGER, PROCESS_LOGGER
from ..testproc.util import list_processes_linux
class DefaultOSContext:
def __init__(self, command, pool=None):
self.command = command
self.pool = pool or DefaultExecutionPool(self)
@contextmanager
def handle_context(self, options):
yield
def list_processes(self):
return []
def terminate_process(self, process):
pass
def platform_shell(self, shell, args, outdir):
return outdir.resolve() / shell
@property
def device_type(self):
return None
class DesktopContext(DefaultOSContext):
@contextmanager
def handle_context(self, options):
log_path = options.log_system_memory
logger = PROCESS_LOGGER if log_path else EMPTY_PROCESS_LOGGER
with logger.log_system_memory(log_path):
yield
class PosixContext(DesktopContext):
def __init__(self):
super().__init__(PosixCommand)
def list_processes(self):
return list_processes_linux()
def terminate_process(self, process):
os.kill(process.pid, signal.SIGTERM)
class WindowsContext(DesktopContext):
def __init__(self):
super().__init__(WindowsCommand)
def terminate_process(self, process):
terminate_process_windows(process)
def platform_shell(self, shell, args, outdir):
return outdir.resolve() / f'{shell}.exe'
class AndroidOSContext(DefaultOSContext):
def __init__(self):
super().__init__(AndroidCommand)
@contextmanager
def handle_context(self, options):
try:
AndroidCommand.driver = Driver.instance(options.device)
yield
finally:
AndroidCommand.driver.tear_down()
@property
def device_type(self):
return AndroidCommand.driver.device_type
class IOSContext(DefaultOSContext):
def __init__(self):
super().__init__(IOSCommand)
def terminate_process(self, process):
os.kill(process.pid, signal.SIGTERM)
def platform_shell(self, shell, appargs, outdir):
# Rather than having to use a physical device (iPhone, iPad, etc), we use
# the iOS Simulator for the test runners in order to ease the job of
# builders and testers.
# At the moment Chromium's iossim tool is being used, which is a wrapper
# around 'simctl' macOS command utility.
iossim = outdir.resolve() / "iossim -d 'iPhone X' "
if isinstance(appargs, list):
appargs = ' '.join(map(str, appargs))
if appargs != "":
iossim = f'{iossim}-c '
appargs = '\"' + appargs + '\"'
app = outdir.resolve() / f'{shell}.app'
return f'{iossim}{appargs} {app}'
# TODO(liviurau): Add documentation with diagrams to describe how context and
# its components gets initialized and eventually teared down and how does it
# interact with both tests and underlying platform specific concerns.
def find_os_context_factory(target_os):
registry = dict(
android=AndroidOSContext, ios=IOSContext, windows=WindowsContext)
return registry.get(target_os, PosixContext)
@contextmanager
def os_context(target_os, options):
factory = find_os_context_factory(target_os)
context_instance = factory()
with context_instance.handle_context(options):
yield context_instance

View File

@ -0,0 +1,5 @@
# Copyright 2019 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.
[]

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python3
# Copyright 2019 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.
from pathlib import Path
from testrunner.local import testsuite, statusfile
from testrunner.objects.testcase import TestCase
class TestLoader(testsuite.TestLoader):
def _list_test_filenames(self):
return ["fast", "slow", "heavy"]
def list_tests(self):
self.test_count_estimation = 3
fast = self._create_test(Path("fast"), self.suite)
slow = self._create_test(Path("slow"), self.suite)
heavy = self._create_test(Path("heavy"), self.suite)
slow._statusfile_outcomes.append(statusfile.SLOW)
heavy._statusfile_outcomes.append(statusfile.HEAVY)
yield fast
yield slow
yield heavy
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase

401
deps/v8/tools/testrunner/local/pool.py vendored Normal file
View File

@ -0,0 +1,401 @@
#!/usr/bin/env python3
# Copyright 2014 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 collections
import logging
import os
import signal
import threading
import traceback
from contextlib import contextmanager
from multiprocessing import Process, Queue
from queue import Empty
def setup_testing():
"""For testing only: Use threading under the hood instead of multiprocessing
to make coverage work.
"""
global Queue
global Process
del Queue
del Process
from queue import Queue
from threading import Thread as Process
# Monkeypatch os.kill and add fake pid property on Thread.
os.kill = lambda *args: None
Process.pid = property(lambda self: None)
class AbortException(Exception):
"""Indicates early abort on SIGINT, SIGTERM or internal hard timeout."""
pass
class NormalResult():
def __init__(self, result):
self.result = result
self.exception = None
class ExceptionResult():
def __init__(self, exception):
self.exception = exception
class MaybeResult():
def __init__(self, heartbeat, value):
self.heartbeat = heartbeat
self.value = value
@staticmethod
def create_heartbeat():
return MaybeResult(True, None)
@staticmethod
def create_result(value):
return MaybeResult(False, value)
def Worker(fn, work_queue, done_queue,
process_context_fn=None, process_context_args=None):
"""Worker to be run in a child process.
The worker stops when the poison pill "STOP" is reached.
"""
# Install a default signal handler for SIGTERM that stops the processing
# loop below on the next occasion. The job function "fn" is supposed to
# register their own handler to avoid blocking, but still chain to this
# handler on SIGTERM to terminate the loop quickly.
stop = [False]
def handler(signum, frame):
stop[0] = True
signal.signal(signal.SIGTERM, handler)
try:
kwargs = {}
if process_context_fn and process_context_args is not None:
kwargs.update(process_context=process_context_fn(*process_context_args))
for args in iter(work_queue.get, "STOP"):
if stop[0]:
# SIGINT, SIGTERM or internal hard timeout caught outside the execution
# of "fn".
break
try:
done_queue.put(NormalResult(fn(*args, **kwargs)))
except AbortException:
# SIGINT, SIGTERM or internal hard timeout caught during execution of
# "fn".
break
except Exception as e:
logging.exception('Unhandled error during worker execution.')
done_queue.put(ExceptionResult(e))
except KeyboardInterrupt:
assert False, 'Unreachable'
@contextmanager
def without_sig():
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
term_handler = signal.signal(signal.SIGTERM, signal.SIG_IGN)
try:
yield
finally:
signal.signal(signal.SIGINT, int_handler)
signal.signal(signal.SIGTERM, term_handler)
@contextmanager
def drain_queue_async(queue):
"""Drains a queue in a background thread until the wrapped code unblocks.
This can be used to unblock joining a child process that might still write
to the queue. The join should be wrapped by this context manager.
"""
keep_running = True
def empty_queue():
elem_count = 0
while keep_running:
try:
while True:
queue.get(True, 0.1)
elem_count += 1
if elem_count < 200:
logging.info('Drained an element from queue.')
except Empty:
pass
except:
logging.exception('Error draining queue.')
emptier = threading.Thread(target=empty_queue)
emptier.start()
yield
keep_running = False
emptier.join()
class ContextPool():
def __init__(self):
self.abort_now = False
def init(self, num_workers, heartbeat_timeout=1, notify_function=None):
"""
Delayed initialization. At context creation time we have no access to the
below described parameters.
Args:
num_workers: Number of worker processes to run in parallel.
heartbeat_timeout: Timeout in seconds for waiting for results. Each time
the timeout is reached, a heartbeat is signalled and timeout is reset.
notify_function: Callable called to signal some events like termination. The
event name is passed as string.
"""
pass
def add_jobs(self, jobs):
pass
def results(self, requirement):
pass
def abort(self):
self.abort_now = True
ProcessContext = collections.namedtuple('ProcessContext', ['result_reduction'])
class DefaultExecutionPool(ContextPool):
"""Distributes tasks to a number of worker processes.
New tasks can be added dynamically even after the workers have been started.
Requirement: Tasks can only be added from the parent process, e.g. while
consuming the results generator."""
# Factor to calculate the maximum number of items in the work/done queue.
# Necessary to not overflow the queue's pipe if a keyboard interrupt happens.
BUFFER_FACTOR = 4
def __init__(self, os_context=None):
super(DefaultExecutionPool, self).__init__()
self.os_context = os_context
self.processes = []
self.terminated = False
# Invariant: processing_count >= #work_queue + #done_queue. It is greater
# when a worker takes an item from the work_queue and before the result is
# submitted to the done_queue. It is equal when no worker is working,
# e.g. when all workers have finished, and when no results are processed.
# Count is only accessed by the parent process. Only the parent process is
# allowed to remove items from the done_queue and to add items to the
# work_queue.
self.processing_count = 0
# Disable sigint and sigterm to prevent subprocesses from capturing the
# signals.
with without_sig():
self.work_queue = Queue()
self.done_queue = Queue()
def init(self, num_workers=1, heartbeat_timeout=1, notify_function=None):
"""
Args:
num_workers: Number of worker processes to run in parallel.
heartbeat_timeout: Timeout in seconds for waiting for results. Each time
the timeout is reached, a heartbeat is signalled and timeout is reset.
notify_function: Callable called to signal some events like termination. The
event name is passed as string.
"""
self.num_workers = num_workers
self.heartbeat_timeout = heartbeat_timeout
self.notify = notify_function or (lambda x: x)
def add_jobs(self, jobs):
self.add(jobs)
def results(self, requirement):
return self.imap_unordered(
fn=run_job,
gen=[],
process_context_fn=ProcessContext,
process_context_args=[requirement],
)
def imap_unordered(self, fn, gen,
process_context_fn=None, process_context_args=None):
"""Maps function "fn" to items in generator "gen" on the worker processes
in an arbitrary order. The items are expected to be lists of arguments to
the function. Returns a results iterator. A result value of type
MaybeResult either indicates a heartbeat of the runner, i.e. indicating
that the runner is still waiting for the result to be computed, or it wraps
the real result.
Args:
process_context_fn: Function executed once by each worker. Expected to
return a process-context object. If present, this object is passed
as additional argument to each call to fn.
process_context_args: List of arguments for the invocation of
process_context_fn. All arguments will be pickled and sent beyond the
process boundary.
"""
if self.terminated:
return
try:
internal_error = False
gen = iter(gen)
self.advance = self._advance_more
# Disable sigint and sigterm to prevent subprocesses from capturing the
# signals.
with without_sig():
for w in range(self.num_workers):
p = Process(target=Worker, args=(fn,
self.work_queue,
self.done_queue,
process_context_fn,
process_context_args))
p.start()
self.processes.append(p)
self.advance(gen)
while self.processing_count > 0:
while True:
try:
# Read from result queue in a responsive fashion. If available,
# this will return a normal result immediately or a heartbeat on
# heartbeat timeout (default 1 second).
result = self._get_result_from_queue()
except:
# TODO(machenbach): Handle a few known types of internal errors
# gracefully, e.g. missing test files.
logging.exception('Internal error in a worker process.')
internal_error = True
continue
finally:
if self.abort_now:
# SIGINT, SIGTERM or internal hard timeout.
return
yield result
break
self.advance(gen)
except KeyboardInterrupt:
assert False, 'Unreachable'
except Exception:
logging.exception('Unhandled error during pool execution.')
finally:
self._terminate()
if internal_error:
raise Exception('Internal error in a worker process.')
def _advance_more(self, gen):
while self.processing_count < self.num_workers * self.BUFFER_FACTOR:
try:
self.work_queue.put(next(gen))
self.processing_count += 1
except StopIteration:
self.advance = self._advance_empty
break
def _advance_empty(self, gen):
pass
def add(self, args):
"""Adds an item to the work queue. Can be called dynamically while
processing the results from imap_unordered."""
assert not self.terminated
self.work_queue.put(args)
self.processing_count += 1
def abort(self):
"""Schedules abort on next queue read.
This is safe to call when handling SIGINT, SIGTERM or when an internal
hard timeout is reached.
"""
self.abort_now = True
def _terminate_processes(self):
for p in self.processes:
self.os_context.terminate_process(p)
def _terminate(self):
"""Terminates execution and cleans up the queues.
If abort() was called before termination, this also terminates the
subprocesses and doesn't wait for ongoing tests.
"""
if self.terminated:
return
self.terminated = True
# Drain out work queue from tests
try:
while True:
self.work_queue.get(True, 0.1)
except Empty:
pass
# Make sure all processes stop
for _ in self.processes:
# During normal tear down the workers block on get(). Feed a poison pill
# per worker to make them stop.
self.work_queue.put("STOP")
# Send a SIGTERM to all workers. They will gracefully terminate their
# processing loop and if the signal is caught during job execution they
# will try to terminate the ongoing test processes quickly.
if self.abort_now:
self._terminate_processes()
self.notify("Joining workers")
with drain_queue_async(self.done_queue):
for p in self.processes:
p.join()
self.notify("Pool terminated")
def _get_result_from_queue(self):
"""Attempts to get the next result from the queue.
Returns: A wrapped result if one was available within heartbeat timeout,
a heartbeat result otherwise.
Raises:
Exception: If an exception occured when processing the task on the
worker side, it is reraised here.
"""
while True:
try:
result = self.done_queue.get(timeout=self.heartbeat_timeout)
self.processing_count -= 1
if result.exception:
raise result.exception
return MaybeResult.create_result(result.result)
except Empty:
return MaybeResult.create_heartbeat()
class SingleThreadedExecutionPool(ContextPool):
def __init__(self):
super(SingleThreadedExecutionPool, self).__init__()
self.work_queue = []
def add_jobs(self, jobs):
self.work_queue.extend(jobs)
def results(self, requirement):
while self.work_queue and not self.abort_now:
job = self.work_queue.pop()
yield MaybeResult.create_result(job.run(ProcessContext(requirement)))
# Global function for multiprocessing, because pickling a static method doesn't
# work on Windows.
def run_job(job, process_context):
return job.run(process_context)

82
deps/v8/tools/testrunner/local/pool_test.py vendored Executable file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
# Copyright 2014 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 os
import sys
import unittest
from queue import Empty, Full, Queue
# Needed because the test runner contains relative imports.
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
from testrunner.local.pool import DefaultExecutionPool, drain_queue_async
def Run(x):
if x == 10:
raise Exception("Expected exception triggered by test.")
return x
class PoolTest(unittest.TestCase):
def testNormal(self):
results = set()
pool = DefaultExecutionPool()
pool.init(3)
for result in pool.imap_unordered(Run, [[x] for x in range(0, 10)]):
if result.heartbeat:
# Any result can be a heartbeat due to timings.
continue
results.add(result.value)
self.assertEqual(set(range(0, 10)), results)
def testException(self):
results = set()
pool = DefaultExecutionPool()
pool.init(3)
with self.assertRaises(Exception):
for result in pool.imap_unordered(Run, [[x] for x in range(0, 12)]):
if result.heartbeat:
# Any result can be a heartbeat due to timings.
continue
# Item 10 will not appear in results due to an internal exception.
results.add(result.value)
expect = set(range(0, 12))
expect.remove(10)
self.assertEqual(expect, results)
def testAdd(self):
results = set()
pool = DefaultExecutionPool()
pool.init(3)
for result in pool.imap_unordered(Run, [[x] for x in range(0, 10)]):
if result.heartbeat:
# Any result can be a heartbeat due to timings.
continue
results.add(result.value)
if result.value < 30:
pool.add([result.value + 20])
self.assertEqual(
set(range(0, 10)) | set(range(20, 30)) | set(range(40, 50)), results)
class QueueTest(unittest.TestCase):
def testDrainQueueAsync(self):
queue = Queue(1)
queue.put('foo')
with self.assertRaises(Full):
queue.put('bar', timeout=0.01)
with drain_queue_async(queue):
queue.put('bar')
with self.assertRaises(Empty):
queue.get(False)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,170 @@
# Copyright 2023 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.
"""
Tools for tracking process statistics like memory consumption.
"""
import platform
import time
from contextlib import contextmanager
from datetime import datetime
from threading import Thread, Event
PROBING_INTERVAL_SEC = 0.2
FLUSH_LOG_BUFFER_SEC = 2
class ProcessStats:
"""Storage class for process statistics indicating if data is available."""
def __init__(self):
self._max_rss = 0
self._max_vms = 0
self._available = False
@property
def max_rss(self):
return self._max_rss
@property
def max_vms(self):
return self._max_vms
@property
def available(self):
return self._available
def update(self, memory_info):
self._max_rss = max(self._max_rss, memory_info.rss)
self._max_vms = max(self._max_vms, memory_info.vms)
self._available = True
class EmptyProcessLogger:
@contextmanager
def log_stats(self, process):
"""When wrapped, logs memory statistics of the Popen process argument.
This base-class version can be used as a null object to turn off the
feature, yielding null-object stats.
"""
yield ProcessStats()
@contextmanager
def log_system_memory(self, log_path):
"""When wrapped, logs system memory statistics to 'log_path'.
This base-class version keeps logging off.
"""
yield
class PSUtilProcessLogger(EmptyProcessLogger):
def __init__(
self, probing_interval_sec=PROBING_INTERVAL_SEC,
flush_log_buffer_sec=FLUSH_LOG_BUFFER_SEC):
self.probing_interval_sec = probing_interval_sec
self.log_buffer_max = int(flush_log_buffer_sec / probing_interval_sec)
def get_pid(self, pid):
return pid
@contextmanager
def log_stats(self, process):
try:
process_handle = psutil.Process(self.get_pid(process.pid))
except (psutil.AccessDenied, psutil.NoSuchProcess):
# Fetching process stats has an expected race condition with the
# running process, which might just have ended already.
yield ProcessStats()
return
stats = ProcessStats()
finished = Event()
def run_logger():
try:
while True:
stats.update(process_handle.memory_info())
if finished.wait(self.probing_interval_sec):
break
except (psutil.AccessDenied, psutil.NoSuchProcess):
pass
logger = Thread(target=run_logger)
logger.start()
try:
yield stats
finally:
finished.set()
# Until we have joined the logger thread, we can't access the stats
# without a race condition.
logger.join()
@contextmanager
def log_system_memory(self, log_path):
with open(log_path, 'w') as handle:
finished = Event()
buffer = []
def flush_buffer():
time_str = datetime.utcfromtimestamp(time.time())
values = ', '.join(map(lambda s: f'{s}%', buffer))
print(f'{time_str} - {values}', file=handle)
buffer.clear()
def run_logger():
while True:
buffer.append(psutil.virtual_memory().percent)
if len(buffer) >= self.log_buffer_max:
flush_buffer()
if finished.wait(self.probing_interval_sec):
if buffer:
flush_buffer()
break
logger = Thread(target=run_logger)
logger.start()
try:
yield
finally:
finished.set()
logger.join()
class LinuxPSUtilProcessLogger(PSUtilProcessLogger):
def get_pid(self, pid):
"""Try to get the correct PID on Linux.
On Linux, we call subprocesses using shell, which on some systems (Debian)
has an optimization using exec and reusing the parent PID, while others
(Ubuntu) create a child process with its own PID. We don't want to log
memory stats of the shell parent.
"""
try:
with open(f'/proc/{pid}/task/{pid}/children') as f:
children = f.read().strip().split(' ')
if children and children[0]:
# On Debian, we don't have child processes here.
return int(children[0])
except FileNotFoundError:
# A quick process might already have finished.
pass
return pid
EMPTY_PROCESS_LOGGER = EmptyProcessLogger()
try:
# Process utils are only supported when we use vpython or when psutil is
# installed.
import psutil
if platform.system() == 'Linux':
PROCESS_LOGGER = LinuxPSUtilProcessLogger()
else:
PROCESS_LOGGER = PSUtilProcessLogger()
except:
PROCESS_LOGGER = EMPTY_PROCESS_LOGGER

View File

@ -0,0 +1,153 @@
#!/usr/bin/env python3
# Copyright 2023 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 os
import psutil
import threading
import unittest
from collections import namedtuple
from mock import patch
from pathlib import Path
from pyfakefs import fake_filesystem_unittest
import process_utils
Process = namedtuple('Process', 'pid')
MemoryInfo = namedtuple('MemoryInfo', 'rss, vms')
SystemMemory = namedtuple('SystemMemory', 'percent')
class TestStats(unittest.TestCase):
def test_update(self):
stats = process_utils.ProcessStats()
self.assertFalse(stats.available)
stats.update(MemoryInfo(17, 42))
self.assertTrue(stats.available)
self.assertEqual(17, stats.max_rss)
self.assertEqual(42, stats.max_vms)
stats.update(MemoryInfo(23, 23))
self.assertTrue(stats.available)
self.assertEqual(23, stats.max_rss)
self.assertEqual(42, stats.max_vms)
class TestProcessLogger(unittest.TestCase):
def test_null(self):
with process_utils.EmptyProcessLogger().log_stats(None) as stats:
pass
self.assertFalse(stats.available)
self.assertEqual(0, stats.max_rss)
self.assertEqual(0, stats.max_vms)
def test_basic(self):
"""Test three iterations of probing with mocked psutil."""
done = threading.Event()
results = [MemoryInfo(17, 101), MemoryInfo(2, 11), MemoryInfo(23, 42)]
class FakeProcess:
def __init__(self, process):
self.iter = iter(results)
def memory_info(self):
try:
return self.iter.__next__()
except StopIteration:
done.set()
raise psutil.NoSuchProcess(123)
logger = process_utils.PSUtilProcessLogger(0.01)
with patch('psutil.Process', FakeProcess):
with logger.log_stats(Process(123)) as stats:
done.wait()
self.assertTrue(stats.available)
self.assertEqual(23, stats.max_rss)
self.assertEqual(101, stats.max_vms)
def test_fast_process(self):
"""Test a process that finished fast."""
class FakeProcess:
def __init__(self, process):
raise psutil.NoSuchProcess(123)
logger = process_utils.PSUtilProcessLogger(0.01)
with patch('psutil.Process', FakeProcess):
with logger.log_stats(Process(123)) as stats:
pass
self.assertFalse(stats.available)
self.assertEqual(0, stats.max_rss)
self.assertEqual(0, stats.max_vms)
class TestLinuxProcessLogger(fake_filesystem_unittest.TestCase):
def test_child_process(self):
self.setUpPyfakefs(allow_root_user=True)
os.makedirs('/proc/123/task/123')
with open('/proc/123/task/123/children', 'w') as f:
f.write('42')
logger = process_utils.LinuxPSUtilProcessLogger()
self.assertEqual(42, logger.get_pid(123))
def test_no_child_process(self):
self.setUpPyfakefs(allow_root_user=True)
os.makedirs('/proc/123/task/123')
with open('/proc/123/task/123/children', 'w') as f:
f.write('')
logger = process_utils.LinuxPSUtilProcessLogger()
self.assertEqual(123, logger.get_pid(123))
def test_process_ended(self):
self.setUpPyfakefs(allow_root_user=True)
logger = process_utils.LinuxPSUtilProcessLogger()
self.assertEqual(123, logger.get_pid(123))
class TestSystemMemoryLogger(fake_filesystem_unittest.TestCase):
def test_log(self):
"""Test logging memory stats from a background thread.
Simulate deterministic stats and timestamps. The main thread waits
for at least two iterations.
"""
done = threading.Event()
results = 3 * [SystemMemory(1.7)]
it = iter(results)
def side_effect():
try:
return it.__next__()
except StopIteration:
done.set()
# If the main thread is slow, we might keep running into this.
return results[0]
output_file = Path('dir') / 'memory.log'
self.setUpPyfakefs()
self.fs.create_dir(output_file.parent)
logger = process_utils.PSUtilProcessLogger(0.01, 0.02)
with patch('psutil.virtual_memory', side_effect=side_effect):
with patch('time.time', return_value=13123212323):
with logger.log_system_memory(output_file):
done.wait()
# We get at least two lines of deterministic log. We might get more
# due to unpredictable delay after done.wait() on the main thread.
with open(output_file) as f:
lines = f.readlines()
expected = '2385-11-10 00:45:23 - 1.7%, 1.7%\n'
self.assertLess(1, len(lines))
self.assertEqual(expected, lines[0])
self.assertEqual(expected, lines[1])
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,363 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import re
from testrunner.build_config import INITIALIZATION_ERROR
from testrunner.local.variants import ALL_VARIANTS
from testrunner.local.utils import Freeze
# Possible outcomes
FAIL = "FAIL"
PASS = "PASS"
TIMEOUT = "TIMEOUT"
CRASH = "CRASH"
# Outcomes only for status file, need special handling
FAIL_OK = "FAIL_OK"
FAIL_SLOPPY = "FAIL_SLOPPY"
# Modifiers
HEAVY = "HEAVY"
SKIP = "SKIP"
SLOW = "SLOW"
NO_VARIANTS = "NO_VARIANTS"
FAIL_PHASE_ONLY = "FAIL_PHASE_ONLY"
ALWAYS = "ALWAYS"
KEYWORDS = {}
for key in [SKIP, FAIL, PASS, CRASH, HEAVY, SLOW, FAIL_OK, NO_VARIANTS,
FAIL_SLOPPY, ALWAYS, FAIL_PHASE_ONLY]:
KEYWORDS[key] = key
# Support arches, modes to be written as keywords instead of strings.
VARIABLES = {ALWAYS: True}
for var in [
"debug", "release", "big", "little", "android", "arm", "arm64", "ia32",
"mips64", "mips64el", "x64", "ppc64", "s390x", "macos", "windows", "linux",
"aix", "r1", "r2", "r3", "r5", "r6", "riscv32", "riscv64", "loong64", "zos",
"bullhead", "panther",
]:
assert var not in VARIABLES
VARIABLES[var] = var
# Allow using variants as keywords.
for var in ALL_VARIANTS:
assert var not in VARIABLES
VARIABLES[var] = var
class StatusFile(object):
def __init__(self, path, variables):
"""
_rules: {variant: {test name: [rule]}}
_prefix_rules: {variant: {test name prefix: [rule]}}
"""
self.variables = variables
with open(path) as f:
self._rules, self._prefix_rules = ReadStatusFile(f.read(), variables)
def get_outcomes(self, testname, variant=None):
"""Merges variant dependent and independent rules."""
outcomes = frozenset()
for key in set([variant or '', '']):
rules = self._rules.get(key, {})
prefix_rules = self._prefix_rules.get(key, {})
if testname in rules:
outcomes |= rules[testname]
for prefix in prefix_rules:
if testname.startswith(prefix):
outcomes |= prefix_rules[prefix]
return outcomes
def warn_unused_rules(self, tests, check_variant_rules=False):
"""Finds and prints unused rules in status file.
Rule X is unused when it doesn't apply to any tests, which can also mean
that all matching tests were skipped by another rule before evaluating X.
Args:
tests: list of pairs (testname, variant)
check_variant_rules: if set variant dependent rules are checked
"""
if check_variant_rules:
variants = list(ALL_VARIANTS)
else:
variants = ['']
used_rules = set()
for testname, variant in tests:
variant = variant or ''
if testname in self._rules.get(variant, {}):
used_rules.add((testname, variant))
if SKIP in self._rules[variant][testname]:
continue
for prefix in self._prefix_rules.get(variant, {}):
if testname.startswith(prefix):
used_rules.add((prefix, variant))
if SKIP in self._prefix_rules[variant][prefix]:
break
for variant in variants:
for rule, value in (
list(self._rules.get(variant, {}).items()) +
list(self._prefix_rules.get(variant, {}).items())):
if (rule, variant) not in used_rules:
if variant == '':
variant_desc = 'variant independent'
else:
variant_desc = 'variant: %s' % variant
print('Unused rule: %s -> %s (%s)' % (rule, value, variant_desc))
def _JoinsPassAndFail(outcomes1, outcomes2):
"""Indicates if we join PASS and FAIL from two different outcome sets and
the first doesn't already contain both.
"""
return (
PASS in outcomes1 and
not (FAIL in outcomes1 or FAIL_OK in outcomes1) and
(FAIL in outcomes2 or FAIL_OK in outcomes2)
)
VARIANT_EXPRESSION = object()
def _EvalExpression(exp, variables):
"""Evaluates expression and returns its result. In case of NameError caused by
undefined "variant" identifier returns VARIANT_EXPRESSION marker.
"""
try:
return eval(exp, variables)
except NameError as e:
identifier = re.match("name '(.*)' is not defined", str(e)).group(1)
# If it's not a variant expression, it points to a missing build flag.
assert identifier == "variant", INITIALIZATION_ERROR % identifier
return VARIANT_EXPRESSION
def _EvalVariantExpression(
condition, section, variables, variant, rules, prefix_rules):
variables_with_variant = dict(variables)
variables_with_variant["variant"] = variant
result = _EvalExpression(condition, variables_with_variant)
assert result != VARIANT_EXPRESSION
if result is True:
_ReadSection(
section,
variables_with_variant,
rules[variant],
prefix_rules[variant],
)
else:
assert result is False, "Make sure expressions evaluate to boolean values"
def _ParseOutcomeList(rule, outcomes, variables, target_dict):
"""Outcome list format: [condition, outcome, outcome, ...]"""
result = set([])
if type(outcomes) == str:
outcomes = [outcomes]
for item in outcomes:
if type(item) == str:
result.add(item)
elif type(item) == list:
condition = item[0]
exp = _EvalExpression(condition, variables)
assert exp != VARIANT_EXPRESSION, (
"Nested variant expressions are not supported")
if exp is False:
continue
# Ensure nobody uses an identifier by mistake, like "default",
# which would evaluate to true here otherwise.
assert exp is True, "Make sure expressions evaluate to boolean values"
for outcome in item[1:]:
assert type(outcome) == str
result.add(outcome)
else:
assert False
if len(result) == 0:
return
if rule in target_dict:
# A FAIL without PASS in one rule has always precedence over a single
# PASS (without FAIL) in another. Otherwise the default PASS expectation
# in a rule with a modifier (e.g. PASS, SLOW) would be joined to a FAIL
# from another rule (which intended to mark a test as FAIL and not as
# PASS and FAIL).
if _JoinsPassAndFail(target_dict[rule], result):
target_dict[rule] -= set([PASS])
if _JoinsPassAndFail(result, target_dict[rule]):
result -= set([PASS])
target_dict[rule] |= result
else:
target_dict[rule] = result
def ReadContent(content):
return eval(content, KEYWORDS)
def ReadStatusFile(content, variables):
"""Status file format
Status file := [section]
section = [CONDITION, section_rules]
section_rules := {path: outcomes}
outcomes := outcome | [outcome, ...]
outcome := SINGLE_OUTCOME | [CONDITION, SINGLE_OUTCOME, SINGLE_OUTCOME, ...]
"""
# Empty defaults for rules and prefix_rules. Variant-independent
# rules are mapped by "", others by the variant name.
rules = {variant: {} for variant in ALL_VARIANTS}
rules[""] = {}
prefix_rules = {variant: {} for variant in ALL_VARIANTS}
prefix_rules[""] = {}
# This method can be called with the same `variables` object multiple times.
# Ensure we only update `variables` (and check it for consistency) once.
if ALWAYS not in variables:
# Ensure we don't silently overwrite any build variables with our set of
# default keywords in VARIABLES.
for var in VARIABLES:
assert var not in variables, (
"build_config variable '%s' conflicts with VARIABLES" % var)
variables.update(VARIABLES)
for conditional_section in ReadContent(content):
assert type(conditional_section) == list
assert len(conditional_section) == 2
condition, section = conditional_section
exp = _EvalExpression(condition, variables)
# The expression is variant-independent and evaluates to False.
if exp is False:
continue
# The expression is variant-independent and evaluates to True.
if exp is True:
_ReadSection(
section,
variables,
rules[''],
prefix_rules[''],
)
continue
# The expression is variant-dependent (contains "variant" keyword)
if exp == VARIANT_EXPRESSION:
# If the expression contains one or more "variant" keywords, we evaluate
# it for all possible variants and create rules for those that apply.
for variant in ALL_VARIANTS:
_EvalVariantExpression(
condition, section, variables, variant, rules, prefix_rules)
continue
assert False, "Make sure expressions evaluate to boolean values"
return Freeze(rules), Freeze(prefix_rules)
def _ReadSection(section, variables, rules, prefix_rules):
assert type(section) == dict
for rule, outcome_list in list(section.items()):
assert type(rule) == str
if rule[-1] == '*':
_ParseOutcomeList(rule[:-1], outcome_list, variables, prefix_rules)
else:
_ParseOutcomeList(rule, outcome_list, variables, rules)
JS_TEST_PATHS = {
'debugger': [[]],
'inspector': [[]],
'intl': [[]],
'message': [[]],
'mjsunit': [[]],
'mozilla': [['data']],
'test262': [['data', 'test'], ['local-tests', 'test']],
'wasm-js': [['tests']],
'wasm-spec-tests': [['tests']],
'webkit': [[]],
}
DEFAULT_JS_TEST_EXTENSIONS = ['.js', '.mjs']
JS_TEST_EXTENSIONS = {
'wasm-js': ['.any.js'],
}
def PresubmitCheck(path):
with open(path) as f:
contents = ReadContent(f.read())
basename = os.path.basename(os.path.dirname(path))
root_prefix = basename + "/"
test_extensions = JS_TEST_EXTENSIONS.get(
basename, DEFAULT_JS_TEST_EXTENSIONS)
status = {"success": True}
def _assert(check, message): # Like "assert", but doesn't throw.
if not check:
print("%s: Error: %s" % (path, message))
status["success"] = False
try:
for section in contents:
_assert(type(section) == list, "Section must be a list")
_assert(len(section) == 2, "Section list must have exactly 2 entries")
section = section[1]
_assert(type(section) == dict,
"Second entry of section must be a dictionary")
for rule in section:
_assert(type(rule) == str, "Rule key must be a string")
_assert(not rule.startswith(root_prefix),
"Suite name prefix must not be used in rule keys")
_assert(not rule.endswith('.js'),
".js extension must not be used in rule keys.")
_assert('*' not in rule or (rule.count('*') == 1 and rule[-1] == '*'),
"Only the last character of a rule key can be a wildcard")
if basename in JS_TEST_PATHS and '*' not in rule:
def _any_exist(paths):
return any(
os.path.exists(
os.path.join(
os.path.dirname(path), *(paths + [rule + ext])))
for ext in test_extensions)
_assert(any(_any_exist(paths)
for paths in JS_TEST_PATHS[basename]),
"missing file for %s test %s" % (basename, rule))
return status["success"]
except Exception as e:
print(e)
return False

View File

@ -0,0 +1,165 @@
#!/usr/bin/env python3
# Copyright 2016 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 os
import sys
import unittest
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
from testrunner.local import statusfile
from testrunner.local.utils import Freeze
TEST_VARIABLES = {
'system': 'linux',
'mode': 'release',
}
TEST_STATUS_FILE = """
[
[ALWAYS, {
'foo/bar': [PASS, SKIP],
'baz/bar': [PASS, FAIL],
'foo/*': [PASS, SLOW],
}], # ALWAYS
['%s', {
'baz/bar': [PASS, SLOW],
'foo/*': [FAIL],
}],
]
"""
def make_variables():
variables = {}
variables.update(TEST_VARIABLES)
return variables
class UtilsTest(unittest.TestCase):
def test_freeze(self):
self.assertEqual(2, Freeze({1: [2]})[1][0])
self.assertEqual(set([3]), Freeze({1: [2], 2: set([3])})[2])
with self.assertRaises(Exception):
Freeze({1: [], 2: set([3])})[2] = 4
with self.assertRaises(Exception):
Freeze({1: [], 2: set([3])}).update({3: 4})
with self.assertRaises(Exception):
Freeze({1: [], 2: set([3])})[1].append(2)
with self.assertRaises(Exception):
Freeze({1: [], 2: set([3])})[2] |= set([3])
# Sanity check that we can do the same calls on a non-frozen object.
{1: [], 2: set([3])}[2] = 4
{1: [], 2: set([3])}.update({3: 4})
{1: [], 2: set([3])}[1].append(2)
{1: [], 2: set([3])}[2] |= set([3])
class StatusFileTest(unittest.TestCase):
def test_eval_expression(self):
variables = make_variables()
variables.update(statusfile.VARIABLES)
self.assertTrue(
statusfile._EvalExpression('system==linux and mode==release',
variables))
self.assertTrue(
statusfile._EvalExpression('system==linux or variant==default',
variables))
self.assertFalse(
statusfile._EvalExpression('system==linux and mode==debug', variables))
self.assertRaises(
AssertionError, lambda: statusfile._EvalExpression(
'system==linux and mode==foo', variables))
self.assertRaises(
SyntaxError, lambda: statusfile._EvalExpression(
'system==linux and mode=release', variables))
self.assertEqual(
statusfile.VARIANT_EXPRESSION,
statusfile._EvalExpression('system==linux and variant==default',
variables))
def test_read_statusfile_section_true(self):
rules, prefix_rules = statusfile.ReadStatusFile(
TEST_STATUS_FILE % 'system==linux', make_variables())
self.assertEqual(
{
'foo/bar': set(['PASS', 'SKIP']),
'baz/bar': set(['PASS', 'FAIL', 'SLOW']),
},
rules[''],
)
self.assertEqual(
{
'foo/': set(['SLOW', 'FAIL']),
},
prefix_rules[''],
)
self.assertEqual({}, rules['default'])
self.assertEqual({}, prefix_rules['default'])
def test_read_statusfile_section_false(self):
rules, prefix_rules = statusfile.ReadStatusFile(
TEST_STATUS_FILE % 'system==windows', make_variables())
self.assertEqual(
{
'foo/bar': set(['PASS', 'SKIP']),
'baz/bar': set(['PASS', 'FAIL']),
},
rules[''],
)
self.assertEqual(
{
'foo/': set(['PASS', 'SLOW']),
},
prefix_rules[''],
)
self.assertEqual({}, rules['default'])
self.assertEqual({}, prefix_rules['default'])
def test_read_statusfile_section_variant(self):
rules, prefix_rules = statusfile.ReadStatusFile(
TEST_STATUS_FILE % 'system==linux and variant==default',
make_variables(),
)
self.assertEqual(
{
'foo/bar': set(['PASS', 'SKIP']),
'baz/bar': set(['PASS', 'FAIL']),
},
rules[''],
)
self.assertEqual(
{
'foo/': set(['PASS', 'SLOW']),
},
prefix_rules[''],
)
self.assertEqual(
{
'baz/bar': set(['PASS', 'SLOW']),
},
rules['default'],
)
self.assertEqual(
{
'foo/': set(['FAIL']),
},
prefix_rules['default'],
)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,326 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import importlib.machinery
import itertools
import os
from contextlib import contextmanager
from pathlib import Path
from . import statusfile
from .variants import ALL_VARIANTS, ALL_VARIANT_FLAGS
STANDARD_VARIANT = set(["default"])
class VariantsGenerator(object):
def __init__(self, variants):
self._all_variants = [v for v in variants if v in ALL_VARIANTS]
self._standard_variant = [v for v in variants if v in STANDARD_VARIANT]
def gen(self, test):
"""Generator producing (variant, flags, procid suffix) tuples."""
flags_set = self._get_flags_set(test)
for n, variant in enumerate(self._get_variants(test)):
yield (variant, flags_set[variant][0], n)
def _get_flags_set(self, test):
return ALL_VARIANT_FLAGS
def _get_variants(self, test):
if test.only_standard_variant:
return self._standard_variant
return self._all_variants
class TestCombiner(object):
def get_group_key(self, test):
"""To indicate what tests can be combined with each other we define a group
key for each test. Tests with the same group key can be combined. Test
without a group key (None) is not combinable with any other test.
"""
raise NotImplementedError()
def combine(self, name, tests):
"""Returns test combined from `tests`. Since we identify tests by their
suite and name, `name` parameter should be unique within one suite.
"""
return self._combined_test_class()(name, tests)
def _combined_test_class(self):
raise NotImplementedError()
class TestLoader(object):
"""Base class for loading TestSuite tests after applying test suite
transformations."""
def __init__(self, ctx, suite, test_class, test_config, test_root):
self.ctx = ctx
self.suite = suite
self.test_class = test_class
self.test_config = test_config
self.test_root = test_root
self.test_count_estimation = len(list(self._list_test_filenames()))
def _list_test_filenames(self):
"""Implemented by the subclassed TestLoaders to list filenames.
Filenames are expected to be sorted and are deterministic."""
raise NotImplementedError
def _should_filter_by_name(self, name):
return False
def _should_filter_by_test(self, test):
return False
def _filename_to_testname(self, filename):
"""Hook for subclasses to write their own filename transformation
logic before the test creation."""
return filename
def _create_test(self, path, suite, **kwargs):
"""Converts paths into test objects using the given options"""
return self.test_class(suite, path, path.as_posix(), **kwargs)
def list_tests(self):
"""Loads and returns the test objects for a TestSuite"""
# TODO: detect duplicate tests.
for filename in map(Path, self._list_test_filenames()):
if self._should_filter_by_name(filename):
continue
testname = self._filename_to_testname(filename)
case = self._create_test(testname, self.suite)
if self._should_filter_by_test(case):
continue
yield case
class GenericTestLoader(TestLoader):
"""Generic TestLoader implementing the logic for listing filenames"""
@property
def excluded_files(self):
return set()
@property
def excluded_dirs(self):
return set()
@property
def excluded_suffixes(self):
return set()
@property
def test_dirs(self):
return [self.test_root]
@property
def extensions(self):
return []
def __find_extension(self, filename):
for extension in self.extensions:
if filename.name.endswith(extension):
return extension
return False
def _should_filter_by_name(self, filename):
if not self.__find_extension(filename):
return True
for suffix in self.excluded_suffixes:
if filename.name.endswith(suffix):
return True
if filename.name in self.excluded_files:
return True
return False
def _filename_to_testname(self, filename):
extension = self.__find_extension(filename)
if not extension:
return filename
return filename.parent / filename.name[:-len(extension)]
def _to_relpath(self, abspath, test_root):
return abspath.relative_to(test_root)
def _list_test_filenames(self):
for test_dir in sorted(self.test_dirs):
test_root = self.test_root / test_dir
for dirname, dirs, files in os.walk(test_root, followlinks=True):
dirs.sort()
for dir in dirs:
if dir in self.excluded_dirs or dir.startswith('.'):
dirs.remove(dir)
files.sort()
for filename in files:
abspath = Path(dirname) / filename
yield self._to_relpath(abspath, test_root)
class JSTestLoader(GenericTestLoader):
@property
def extensions(self):
return [".js", ".mjs"]
class TestGenerator(object):
def __init__(
self, test_count_estimate, heavy_tests, slow_tests, remaining_tests):
self.test_count_estimate = test_count_estimate
self.heavy_tests = heavy_tests
self.slow_tests = slow_tests
self.remaining_tests = remaining_tests
self._rebuild_iterator()
def _rebuild_iterator(self):
self._iterator = itertools.chain(
self.heavy_tests, self.slow_tests, self.remaining_tests)
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
return next(self._iterator)
def merge(self, test_generator):
self.test_count_estimate += test_generator.test_count_estimate
self.heavy_tests = itertools.chain(
self.heavy_tests, test_generator.heavy_tests)
self.slow_tests = itertools.chain(
self.slow_tests, test_generator.slow_tests)
self.remaining_tests = itertools.chain(
self.remaining_tests, test_generator.remaining_tests)
self._rebuild_iterator()
@contextmanager
def _load_testsuite_module(name, root):
f = None
try:
yield importlib.machinery.SourceFileLoader(
name + "_testcfg", f"{root}/testcfg.py").load_module()
finally:
if f:
f.close()
class TestSuite(object):
@staticmethod
def Load(ctx, root, test_config):
name = root.name
with _load_testsuite_module(name, root) as module:
return module.TestSuite(ctx, name, root, test_config)
def __init__(self, ctx, name, root, test_config):
self.name = name # string
self.root = root # pathlib path
self.test_config = test_config
self.tests = None # list of TestCase objects
self.statusfile = None
self._test_loader = self._test_loader_class()(ctx, self, self._test_class(),
self.test_config, self.root)
@property
def framework_name(self):
return self.test_config.framework_name
def status_file(self):
return "%s/%s.status" % (self.root, self.name)
def statusfile_outcomes(self, test_name, variant):
return self.statusfile.get_outcomes(test_name, variant)
@property
def _test_loader_class(self):
raise NotImplementedError
def ListTests(self):
return self._test_loader.list_tests()
def __initialize_test_count_estimation(self):
# Retrieves a single test to initialize the test generator.
next(iter(self.ListTests()), None)
def __calculate_test_count(self):
self.__initialize_test_count_estimation()
return self._test_loader.test_count_estimation
def load_tests_from_disk(self, statusfile_variables):
self.statusfile = statusfile.StatusFile(
self.status_file(), statusfile_variables)
test_count = self.__calculate_test_count()
heavy_tests = (
test for test in self.ListTests()
if test.is_heavy)
slow_tests = (
test for test in self.ListTests()
if not test.is_heavy and test.is_slow)
remaining_tests = (
test for test in self.ListTests()
if not test.is_heavy and not test.is_slow)
return TestGenerator(test_count, heavy_tests, slow_tests, remaining_tests)
def get_variants_gen(self, variants):
return self._variants_gen_class()(variants)
def _variants_gen_class(self):
return VariantsGenerator
def test_combiner_available(self):
return bool(self._test_combiner_class())
def get_test_combiner(self):
cls = self._test_combiner_class()
if cls:
return cls()
return None
def _test_combiner_class(self):
"""Returns Combiner subclass. None if suite doesn't support combining
tests.
"""
return None
def _test_class(self):
raise NotImplementedError

View File

@ -0,0 +1,98 @@
#!/usr/bin/env python3
# Copyright 2016 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 os
import sys
import unittest
from pathlib import Path
# Needed because the test runner contains relative imports.
TOOLS_PATH = Path(__file__).resolve().parents[2]
sys.path.append(str(TOOLS_PATH))
from testrunner.local.command import PosixCommand
from testrunner.local.context import DefaultOSContext
from testrunner.local.testsuite import TestSuite
from testrunner.test_config import TestConfig
class TestSuiteTest(unittest.TestCase):
def setUp(self):
test_dir = Path(__file__).resolve().parent
self.test_root = test_dir / "fake_testsuite"
self.test_config = TestConfig(
command_prefix=[],
extra_flags=[],
extra_d8_flags=[],
framework_name='standard_runner',
isolates=False,
log_process_stats=False,
mode_flags=[],
no_harness=False,
noi18n=False,
random_seed=0,
run_skipped=False,
shard_count=1,
shard_id=0,
shell_dir=Path('fake_testsuite/fake_d8'),
target_os='macos',
timeout=10,
verbose=False,
)
self.suite = TestSuite.Load(
DefaultOSContext(PosixCommand), self.test_root, self.test_config)
def testLoadingTestSuites(self):
self.assertEqual(self.suite.name, "fake_testsuite")
self.assertEqual(self.suite.test_config, self.test_config)
# Verify that the components of the TestSuite aren't loaded yet.
self.assertIsNone(self.suite.tests)
self.assertIsNone(self.suite.statusfile)
def testLoadingTestsFromDisk(self):
tests = self.suite.load_tests_from_disk(statusfile_variables={})
def is_generator(iterator):
return iterator == iter(iterator)
self.assertTrue(is_generator(tests))
self.assertEqual(tests.test_count_estimate, 3)
heavy_tests = list(tests.heavy_tests)
slow_tests = list(tests.slow_tests)
remaining_tests = list(tests.remaining_tests)
# Verify that the components of the TestSuite are loaded.
self.assertTrue(
len(heavy_tests) == len(slow_tests) == len(remaining_tests) == 1)
self.assertTrue(all(test.is_heavy for test in heavy_tests))
self.assertTrue(all(test.is_slow for test in slow_tests))
self.assertFalse(any(test.is_heavy for test in remaining_tests))
self.assertFalse(any(test.is_slow for test in remaining_tests))
self.assertIsNotNone(self.suite.statusfile)
def testMergingTestGenerators(self):
tests = self.suite.load_tests_from_disk(statusfile_variables={})
more_tests = self.suite.load_tests_from_disk(statusfile_variables={})
# Merge the test generators
tests.merge(more_tests)
self.assertEqual(tests.test_count_estimate, 6)
# Check the tests are sorted by memory and speed.
tests = list(tests)
heavy_tests = [test.is_heavy for test in tests]
slow_tests = [test.is_slow for test in tests]
self.assertEqual(heavy_tests, [True, True, False, False, False, False])
self.assertEqual(slow_tests, [False, False, True, True, False, False])
if __name__ == '__main__':
unittest.main()

225
deps/v8/tools/testrunner/local/utils.py vendored Normal file
View File

@ -0,0 +1,225 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from os.path import isdir
from os.path import join
import os
import platform
import re
### Exit codes and their meaning.
# Normal execution.
EXIT_CODE_PASS = 0
# Execution with test failures.
EXIT_CODE_FAILURES = 1
# Execution with no tests executed.
EXIT_CODE_NO_TESTS = 2
# Execution aborted with SIGINT (Ctrl-C).
EXIT_CODE_INTERRUPTED = 3
# Execution aborted with SIGTERM.
EXIT_CODE_TERMINATED = 4
# Internal error.
EXIT_CODE_INTERNAL_ERROR = 5
def GetSuitePaths(test_root):
return [ f for f in os.listdir(test_root) if isdir(join(test_root, f)) ]
# Reads a file into an array of strings
def ReadLinesFrom(name):
lines = []
with open(name) as f:
for line in f:
if line.startswith('#'): continue
if '#' in line:
line = line[:line.find('#')]
line = line.strip()
if not line: continue
lines.append(line)
return lines
def GuessOS():
system = platform.system()
if system == 'Linux':
return 'linux'
elif system == 'Darwin':
return 'macos'
elif system.find('CYGWIN') >= 0:
return 'cygwin'
elif system == 'Windows' or system == 'Microsoft':
# On Windows Vista platform.system() can return 'Microsoft' with some
# versions of Python, see http://bugs.python.org/issue1082
return 'windows'
elif system == 'FreeBSD':
return 'freebsd'
elif system == 'OpenBSD':
return 'openbsd'
elif system == 'SunOS':
return 'solaris'
elif system == 'NetBSD':
return 'netbsd'
elif system in ['AIX', 'OS400']:
# OS400 runs an AIX emulator called PASE
return 'aix'
elif system == "OS/390":
return 'zos'
else:
return None
# Check if Vector Enhancement Facility 1 is available on the
# host S390 machine. This facility is required for supporting Simd on V8.
def IsS390SimdSupported():
if GuessOS() == 'zos':
from ctypes import CDLL
libname = os.environ.get('ZOSLIB_LIBPATH') + '/libzoslib.so'
clib = CDLL(libname)
return clib.__is_vef1_available()
import subprocess
cpuinfo = subprocess.check_output("cat /proc/cpuinfo", shell=True)
cpuinfo_list = cpuinfo.strip().decode("utf-8").splitlines()
facilities = "".join(x for x in cpuinfo_list if x.startswith("facilities"))
facilities_list = facilities.split(" ")
# Having bit 135 set indicates VEF1 is available.
return "135" in facilities_list
# Returns power processor version, taking compatibility mode into account.
# (Power9 running in Power8 compatibility mode returns 8)
# Only useful if arch is ppc64
def GuessPowerProcessorVersion():
import ctypes, ctypes.util
os = GuessOS()
if os == 'linux':
AT_PLATFORM = 15 # from linux/auxvec.h
_LIBC = ctypes.CDLL(ctypes.util.find_library('c'))
_LIBC.getauxval.argtypes = [ctypes.c_ulong]
_LIBC.getauxval.restype = ctypes.c_char_p
at_platform = _LIBC.getauxval(AT_PLATFORM).decode('utf-8').lower()
if at_platform.startswith('power6'):
return 6
elif at_platform.startswith('power7'):
return 7
elif at_platform.startswith('power8'):
return 8
elif at_platform.startswith('power9'):
return 9
elif at_platform.startswith('power10'):
return 10
else:
raise Exception('Unable to guess power processor version')
elif os == 'aix':
# covers aix and os400
RTLD_MEMBER = 0x00040000
_LIBC = ctypes.CDLL(ctypes.util.find_library('c'),
ctypes.DEFAULT_MODE | RTLD_MEMBER)
class _system_configuration(ctypes.Structure):
_fields_ = [
('architecture', ctypes.c_int),
('implementation', ctypes.c_int),
]
cfg = _system_configuration.in_dll(_LIBC, '_system_configuration')
# Values found in sys/systemcfg.h
if cfg.implementation == 0x4000:
return 6
elif cfg.implementation == 0x8000:
return 7
elif cfg.implementation == 0x10000:
return 8
elif cfg.implementation == 0x20000:
return 9
elif cfg.implementation == 0x40000:
return 10
else:
raise Exception('Unable to guess power processor version')
else:
raise Exception('Unable to guess power processor version')
def UseSimulator(arch):
machine = platform.machine()
return (machine and (arch == "arm" or arch == "arm64") and
not arch.startswith(machine))
# This will default to building the 32 bit VM even on machines that are
# capable of running the 64 bit VM.
def DefaultArch():
machine = platform.machine()
machine = machine.lower() # Windows 7 capitalizes 'AMD64'.
if machine.startswith('arm'):
return 'arm'
elif (not machine) or (not re.match('(x|i[3-6])86$', machine) is None):
return 'ia32'
elif machine == 'i86pc':
return 'ia32'
elif machine == 'x86_64':
return 'ia32'
elif machine == 'amd64':
return 'ia32'
elif machine == 's390x':
return 's390'
elif machine == 'ppc64':
return 'ppc'
else:
return None
def GuessWordsize():
if '64' in platform.machine():
return '64'
else:
return '32'
def IsWindows():
return GuessOS() == 'windows'
class FrozenDict(dict):
def __setitem__(self, *args, **kwargs):
raise Exception('Tried to mutate a frozen dict')
def update(self, *args, **kwargs):
raise Exception('Tried to mutate a frozen dict')
def Freeze(obj):
if isinstance(obj, dict):
return FrozenDict((k, Freeze(v)) for k, v in list(obj.items()))
elif isinstance(obj, set):
return frozenset(obj)
elif isinstance(obj, list):
return tuple(Freeze(item) for item in obj)
else:
# Make sure object is hashable.
hash(obj)
return obj

View File

@ -0,0 +1,342 @@
# Copyright 2016 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.
# Use this to run several variants of the tests.
ALL_VARIANT_FLAGS = {
"assert_types": [["--assert-types"]],
"code_serializer": [["--cache=code"]],
"default": [[]],
"future": [["--future"]],
"gc_stats": [["--gc-stats=1"]],
# Alias of exhaustive variants, but triggering new test framework features.
"infra_staging": [[]],
"interpreted_regexp": [["--regexp-interpret-all"]],
"stress_regexp_jit": [["--regexp-tier-up-ticks=0"]],
"experimental_regexp": [["--default-to-experimental-regexp-engine"]],
"jitless": [["--jitless", "--wasm-jitless-if-available-for-testing"]],
# Jit-fuzzing variants pass --no-fail as most test conditions are violated.
# We only look for dchecks and crashes. As a result, negative tests like
# in the bugs/* directory are not supported.
"jit_fuzzing": [["--fuzzing", "--jit-fuzzing", "--no-fail"]],
"jit_fuzzing_maglev": [[
"--fuzzing",
"--jit-fuzzing",
"--optimize-on-next-call-optimizes-to-maglev",
"--no-fail",
]],
"sparkplug": [["--sparkplug"]],
"maglev": [["--maglev"]],
"maglev_future": [["--maglev", "--maglev-future"]],
"maglev_no_turbofan": [[
"--maglev", "--no-turbofan",
"--optimize-on-next-call-optimizes-to-maglev"
]],
"stress_maglev": [[
"--maglev", "--stress-maglev",
"--optimize-on-next-call-optimizes-to-maglev"
]],
"stress_maglev_future": [[
"--maglev", "--maglev-future", "--stress-maglev",
"--optimize-on-next-call-optimizes-to-maglev"
]],
"stress_maglev_no_turbofan": [[
"--maglev", "--no-turbofan", "--stress-maglev",
"--optimize-on-next-call-optimizes-to-maglev"
]],
"stress_maglev_non_eager_inlining": [[
"--maglev", "--stress-maglev", "--maglev-non-eager-inlining",
"--optimize-on-next-call-optimizes-to-maglev"
]],
"conservative_pinning": [[
"--scavenger-conservative-object-pinning",
"--stress-scavenger-conservative-object-pinning"
]],
"precise_pinning": [[
"--precise-object-pinning", "--scavenger-precise-object-pinning"
]],
# We test both the JS and Wasm Turboshaft pipelines under the same variant.
# For extended Wasm Turboshaft coverage, we add --no-liftoff to the options.
"turboshaft": [[
"--turboshaft",
"--no-wasm-generic-wrapper",
"--no-liftoff",
]],
# Turboshaft with Maglev as a frontend
"turbolev": [[
"--turboshaft",
"--turbolev",
]],
"concurrent_sparkplug": [["--concurrent-sparkplug", "--sparkplug"]],
"always_sparkplug": [["--always-sparkplug", "--sparkplug"]],
# This combines two orthogonal variants always_sparkplug and
# stress_regexp_jit to use bot resources more effectively.
"always_sparkplug_and_stress_regexp_jit": [[
"--always-sparkplug",
"--sparkplug",
"--regexp-tier-up-ticks=0",
]],
"minor_ms": [["--minor-ms"]],
"no_lfa": [["--no-lazy-feedback-allocation"]],
"no_memory_protection_keys": [["--no-memory-protection-keys"]],
# No optimization means disable all optimizations. OptimizeFunctionOnNextCall
# would not force optimization too. It turns into a Nop. Please see
# https://chromium-review.googlesource.com/c/452620/ for more discussion.
# For WebAssembly, we test "Liftoff-only" in the nooptimization variant and
# "TurboFan-only" in the stress variant. The WebAssembly configuration is
# independent of JS optimizations, so we can combine those configs. We
# disable lazy compilation to have one test variant that tests eager
# compilation. "Liftoff-only" and eager compilation is not a problem,
# because test functions do typically not get optimized to TurboFan anyways.
"nooptimization": [[
"--disable-optimizing-compilers", "--no-wasm-lazy-compilation"
]],
"rehash_snapshot": [["--rehash-snapshot"]],
"slow_path": [["--force-slow-path"]],
"stress": [[
"--no-liftoff",
"--stress-lazy-source-positions",
"--no-wasm-generic-wrapper",
"--no-wasm-lazy-compilation",
]],
"stress_concurrent_allocation": [["--stress-concurrent-allocation"]],
"stress_concurrent_inlining": [["--stress-concurrent-inlining"]],
"stress_js_bg_compile_wasm_code_gc": [[
"--stress-background-compile", "--stress-wasm-code-gc"
]],
"stress_maglev_tests_with_turbofan": [[
"--turbofan", "--optimize-maglev-optimizes-to-turbofan"
]],
"stress_wasm_stack_switching": [["--stress-wasm-stack-switching"]],
"stress_incremental_marking": [["--stress-incremental-marking"]],
"stress_snapshot": [["--stress-snapshot"]],
# Trigger stress sampling allocation profiler with sample interval = 2^14
"stress_sampling": [["--stress-sampling-allocation-profiler=16384"]],
"no_wasm_traps": [["--no-wasm-trap-handler"]],
"instruction_scheduling": [["--turbo-instruction-scheduling"]],
"stress_instruction_scheduling": [["--turbo-stress-instruction-scheduling"]
],
# Google3 variants.
"google3_icu": [[]],
"google3_noicu": [[]],
}
# Note these are specifically for the case when Turbofan is either fully
# disabled (i.e. not part of the binary), or when all codegen is disallowed (in
# jitless mode).
kIncompatibleFlagsForNoTurbofan = [
"--turbofan", "--always-turbofan", "--liftoff", "--validate-asm",
"--maglev", "--stress-concurrent-inlining"
]
# Flags that lead to a contradiction with the flags provided by the respective
# variant. This depends on the flags specified in ALL_VARIANT_FLAGS and on the
# implications defined in flag-definitions.h.
INCOMPATIBLE_FLAGS_PER_VARIANT = {
"jitless":
kIncompatibleFlagsForNoTurbofan + [
"--track-field-types",
"--sparkplug",
"--concurrent-sparkplug",
"--always-sparkplug",
"--regexp-tier-up",
"--no-regexp-interpret-all",
"--interpreted-frames-native-stack",
"--script-context-mutable-heap-number",
],
"nooptimization": [
"--turbofan",
"--always-turbofan",
"--turboshaft",
"--turboshaft-wasm-in-js-inlining",
"--maglev",
"--no-liftoff",
"--wasm-tier-up",
"--wasm-dynamic-tiering",
"--validate-asm",
"--track-field-types",
"--stress-concurrent-inlining",
"--script-context-mutable-heap-number",
"--experimental-wasm-shared",
],
"slow_path": ["--no-force-slow-path"],
"stress_concurrent_allocation": [
"--single-threaded", "--single-threaded-gc", "--predictable"
],
"stress_concurrent_inlining": [
"--single-threaded", "--predictable", "--lazy-feedback-allocation",
"--assert-types", "--turboshaft-assert-types",
"--no-concurrent-recompilation", "--no-turbofan", "--jitless"
],
# The fast API tests initialize an embedder object that never needs to be
# serialized to the snapshot, so we don't have a
# SerializeInternalFieldsCallback for it, so they are incompatible with
# stress_snapshot.
"stress_snapshot": ["--expose-fast-api"],
"stress": [
# 'stress' disables Liftoff, which conflicts with flags that require
# Liftoff support.
"--liftoff-only",
"--wasm-dynamic-tiering"
],
"sparkplug": ["--jitless", "--no-sparkplug"],
"turboshaft": [
# 'turboshaft' disables Liftoff, which conflicts with flags that require
# Liftoff support.
"--liftoff-only",
"--wasm-dynamic-tiering"
],
"concurrent_sparkplug": ["--jitless"],
"maglev": ["--jitless", "--no-maglev"],
"maglev_future": ["--jitless", "--no-maglev", "--no-maglev-future"],
"maglev_no_turbofan": [
"--jitless",
"--no-maglev",
"--turbofan",
"--always-turbofan",
"--stress-concurrent-inlining",
],
"stress_maglev": ["--jitless"],
"stress_maglev_non_eager_inlining": ["--jitless"],
"stress_maglev_future": ["--jitless", "--no-maglev", "--no-maglev-future"],
"stress_maglev_no_turbofan": [
"--jitless",
"--no-maglev",
"--turbofan",
"--always-turbofan",
"--stress-concurrent-inlining",
],
"stress_maglev_tests_with_turbofan": ["--jitless"],
"always_sparkplug": ["--jitless", "--no-sparkplug"],
"always_sparkplug_and_stress_regexp_jit": ["--jitless", "--no-sparkplug"],
"code_serializer": [
"--cache=after-execute", "--cache=full-code-cache", "--cache=none"
],
"interpreted_regexp": ["--regexp-tier-up"],
"stress_regexp_jit": ["--regexp-interpret-all"],
"experimental_regexp": ["--no-enable-experimental-regexp-engine"],
"assert_types": [
"--concurrent-recompilation", "--stress_concurrent_inlining",
"--no-assert-types"
],
"stress_wasm_stack_switching": ["--no-stress-wasm-stack-switching"],
"--turboshaft-assert-types": [
"--concurrent-recompilation", "--stress_concurrent_inlining",
"--no-turboshaft-assert-types"
],
}
# Flags that lead to a contradiction under certain build variables.
# This corresponds to the build variables usable in status files as generated
# in _get_statusfile_variables in base_runner.py.
# The conflicts might be directly contradictory flags or be caused by the
# implications defined in flag-definitions.h.
# The keys of the following map support negation through '!', e.g. rule
#
# "!code_comments": [...]
#
# applies when the code_comments build variable is NOT set.
INCOMPATIBLE_FLAGS_PER_BUILD_VARIABLE = {
"!code_comments": ["--code-comments"],
"!DEBUG_defined": [
"--check_handle_count",
"--code_stats",
"--dump_wasm_module",
"--enable_testing_opcode_in_wasm",
"--gc_verbose",
"--no-wasm-opt",
"--print_ast",
"--print_break_location",
"--print_global_handles",
"--print_handles",
"--print_scopes",
"--regexp_possessive_quantifier",
"--trace_backing_store",
"--trace_contexts",
"--trace_isolates",
"--trace_lazy",
"--trace_liftoff",
"--trace_module_status",
"--trace_normalization",
"--trace_turbo_escape",
"--trace_wasm_compiler",
"--trace_wasm_decoder",
"--trace_wasm_instances",
"--trace_wasm_lazy_compilation",
"--trace_wasm_native_heap",
"--trace_wasm_serialization",
"--trace_wasm_stack_switching",
"--trace_wasm_streaming",
],
"!verify_heap": ["--verify-heap"],
"!debug_code": ["--debug-code"],
"!disassembler": [
"--print_all_code", "--print_code", "--print_opt_code",
"--print_code_verbose", "--print_builtin_code", "--print_regexp_code"
],
"!slow_dchecks": ["--enable-slow-asserts"],
"!gdbjit": ["--gdbjit", "--gdbjit_full", "--gdbjit_dump"],
"!has_maglev": ["--maglev"],
"!has_turbofan": kIncompatibleFlagsForNoTurbofan,
"has_jitless": INCOMPATIBLE_FLAGS_PER_VARIANT["jitless"],
"lite_mode": INCOMPATIBLE_FLAGS_PER_VARIANT["jitless"],
"verify_predictable": [
"--parallel-compile-tasks-for-eager-toplevel",
"--parallel-compile-tasks-for-lazy", "--concurrent-recompilation",
"--stress-concurrent-allocation", "--stress-concurrent-inlining"
],
"dict_property_const_tracking": ["--stress-concurrent-inlining"],
}
# Flags that lead to a contradiction when a certain extra-flag is present.
# Such extra-flags are defined for example in infra/testing/builders.pyl or in
# standard_runner.py.
# The conflicts might be directly contradictory flags or be caused by the
# implications defined in flag-definitions.h.
INCOMPATIBLE_FLAGS_PER_EXTRA_FLAG = {
"--concurrent-recompilation": [
"--predictable", "--assert-types", "--turboshaft-assert-types",
"--single-threaded"
],
"--parallel-compile-tasks-for-eager-toplevel": ["--predictable"],
"--parallel-compile-tasks-for-lazy": ["--predictable"],
"--gc-interval=*": ["--gc-interval=*"],
"--stress_concurrent_allocation":
INCOMPATIBLE_FLAGS_PER_VARIANT["stress_concurrent_allocation"],
"--stress-concurrent-inlining":
INCOMPATIBLE_FLAGS_PER_VARIANT["stress_concurrent_inlining"],
"--turboshaft-assert-types": [
"--concurrent-recompilation", "--stress-concurrent-inlining"
],
}
SLOW_VARIANTS = set([
'stress',
'stress_snapshot',
'nooptimization',
])
FAST_VARIANTS = set([
'default'
])
def _variant_order_key(v):
if v in SLOW_VARIANTS:
return 0
if v in FAST_VARIANTS:
return 100
return 50
ALL_VARIANTS = sorted(ALL_VARIANT_FLAGS.keys(),
key=_variant_order_key)
# For internal integrity checking.
REQUIRED_BUILD_VARIABLES = [
var.lstrip('!') for var in INCOMPATIBLE_FLAGS_PER_BUILD_VARIABLE.keys()
]
# Check {SLOW,FAST}_VARIANTS entries
for variants in [SLOW_VARIANTS, FAST_VARIANTS]:
for v in variants:
assert v in ALL_VARIANT_FLAGS

View File

@ -0,0 +1,103 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
import time
from . import statusfile
REPORT_TEMPLATE = (
"""Total: %(total)i tests
* %(skipped)4d tests will be skipped
* %(nocrash)4d tests are expected to be flaky but not crash
* %(pass)4d tests are expected to pass
* %(fail_ok)4d tests are expected to fail that we won't fix
* %(fail)4d tests are expected to fail that we should fix
* %(crash)4d tests are expected to crash
""")
# TODO(majeski): Turn it into an observer.
def PrintReport(tests):
total = len(tests)
skipped = nocrash = passes = fail_ok = fail = crash = 0
for t in tests:
if t.do_skip:
skipped += 1
elif t.is_pass_or_fail:
nocrash += 1
elif t.is_fail_ok:
fail_ok += 1
elif t.expected_outcomes == [statusfile.PASS]:
passes += 1
elif t.expected_outcomes == [statusfile.FAIL]:
fail += 1
elif t.expected_outcomes == [statusfile.CRASH]:
crash += 1
else:
assert False # Unreachable # TODO: check this in outcomes parsing phase.
print(REPORT_TEMPLATE % {
"total": total,
"skipped": skipped,
"nocrash": nocrash,
"pass": passes,
"fail_ok": fail_ok,
"fail": fail,
"crash": crash,
})
def PrintTestSource(tests):
for test in tests:
print("--- begin source: %s ---" % test)
if test.is_source_available():
print(test.get_source())
else:
print('(no source available)')
print("--- end source: %s ---" % test)
def FormatTime(d):
millis = round(d * 1000) % 1000
return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
def PrintTestDurations(suites, outputs, overall_time):
# Write the times to stderr to make it easy to separate from the
# test output.
print()
sys.stderr.write("--- Total time: %s ---\n" % FormatTime(overall_time))
timed_tests = [(t, outputs[t].duration) for s in suites for t in s.tests
if t in outputs]
timed_tests.sort(key=lambda test_duration: test_duration[1], reverse=True)
index = 1
for test, duration in timed_tests[:20]:
t = FormatTime(duration)
sys.stderr.write("%4i (%s) %s\n" % (index, t, test))
index += 1

197
deps/v8/tools/testrunner/num_fuzzer.py vendored Executable file
View File

@ -0,0 +1,197 @@
#!/usr/bin/env python3
#
# 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 sys
# Adds testrunner to the path hence it has to be imported at the beggining.
from testrunner import base_runner
from testrunner.local import utils
from testrunner.testproc import fuzzer
from testrunner.testproc.combiner import CombinerProc
from testrunner.testproc.execution import ExecutionProc
from testrunner.testproc.expectation import ExpectationProc
from testrunner.testproc.filter import StatusFileFilterProc, NameFilterProc
from testrunner.testproc.loader import LoadProc
from testrunner.utils import random_utils
from testrunner.testproc.rerun import RerunProc
from testrunner.testproc.timeout import TimeoutProc
from testrunner.testproc.progress import ResultsTracker, ProgressProc
from testrunner.testproc.shard import ShardProc
DEFAULT_SUITES = ["mjsunit", "webkit", "benchmarks"]
class NumFuzzer(base_runner.BaseTestRunner):
def __init__(self, *args, **kwargs):
super(NumFuzzer, self).__init__(*args, **kwargs)
@property
def default_framework_name(self):
return 'num_fuzzer'
def _add_parser_options(self, parser):
parser.add_option("--fuzzer-random-seed", default=0,
help="Default seed for initializing fuzzer random "
"generator")
parser.add_option("--tests-count", default=5, type="int",
help="Number of tests to generate from each base test. "
"Can be combined with --total-timeout-sec with "
"value 0 to provide infinite number of subtests. "
"When --combine-tests is set it indicates how many "
"tests to create in total")
# Stress gc
parser.add_option("--stress-marking", default=0, type="int",
help="probability [0-10] of adding --stress-marking "
"flag to the test")
parser.add_option("--stress-scavenge", default=0, type="int",
help="probability [0-10] of adding --stress-scavenge "
"flag to the test")
parser.add_option("--stress-compaction", default=0, type="int",
help="probability [0-10] of adding --stress-compaction "
"flag to the test")
parser.add_option("--stress-gc", default=0, type="int",
help="probability [0-10] of adding --random-gc-interval "
"flag to the test")
# Stress stack size
parser.add_option("--stress-stack-size", default=0, type="int",
help="probability [0-10] of adding --stack-size "
"flag to the test")
# Stress tasks
parser.add_option("--stress-delay-tasks", default=0, type="int",
help="probability [0-10] of adding --stress-delay-tasks "
"flag to the test")
parser.add_option("--stress-thread-pool-size", default=0, type="int",
help="probability [0-10] of adding --thread-pool-size "
"flag to the test")
# Stress compiler
parser.add_option("--stress-deopt", default=0, type="int",
help="probability [0-10] of adding --deopt-every-n-times "
"flag to the test")
parser.add_option("--stress-interrupt-budget", default=0, type="int",
help="probability [0-10] of adding the --interrupt-budget "
"flag to the test")
# Combine multiple tests
parser.add_option("--combine-tests", default=False, action="store_true",
help="Combine multiple tests as one and run with "
"try-catch wrapper")
parser.add_option("--combine-max", default=100, type="int",
help="Maximum number of tests to combine")
parser.add_option("--combine-min", default=2, type="int",
help="Minimum number of tests to combine")
# Miscellaneous
parser.add_option("--variants", default='default',
help="Comma-separated list of testing variants")
return parser
def _process_options(self):
if not self.options.fuzzer_random_seed:
self.options.fuzzer_random_seed = random_utils.random_seed()
if self.options.total_timeout_sec:
self.options.tests_count = 0
if self.options.combine_tests:
if self.options.combine_min > self.options.combine_max:
print(('min_group_size (%d) cannot be larger than max_group_size (%d)' %
self.options.min_group_size, self.options.max_group_size))
raise base_runner.TestRunnerError()
if self.options.variants != 'default':
print ('Only default testing variant is supported with numfuzz')
raise base_runner.TestRunnerError()
return True
def _get_default_suite_names(self):
return DEFAULT_SUITES
def _get_statusfile_variables(self, context):
variables = (
super(NumFuzzer, self)._get_statusfile_variables(context))
variables.update({
'deopt_fuzzer':
bool(self.options.stress_deopt),
'interrupt_fuzzer':
bool(self.options.stress_interrupt_budget),
'endurance_fuzzer':
bool(self.options.combine_tests),
'gc_stress':
bool(self.options.stress_gc),
'gc_fuzzer':
bool(
max([
self.options.stress_marking, self.options.stress_scavenge,
self.options.stress_compaction, self.options.stress_gc,
self.options.stress_delay_tasks,
self.options.stress_stack_size,
self.options.stress_thread_pool_size
])),
})
return variables
def _do_execute(self, tests, args, ctx):
loader = LoadProc(tests)
combiner = CombinerProc.create(self.options)
results = ResultsTracker.create(self.options)
execproc = ExecutionProc(ctx, self.options.j)
sigproc = self._create_signal_proc()
progress = ProgressProc(ctx, self.options, tests.test_count_estimate)
procs = [
loader,
NameFilterProc(args) if args else None,
StatusFileFilterProc(None, None),
# TODO(majeski): Improve sharding when combiner is present. Maybe select
# different random seeds for shards instead of splitting tests.
ShardProc.create(self.options),
ExpectationProc(),
combiner,
fuzzer.FuzzerProc.create(self.options),
sigproc,
progress,
results,
TimeoutProc.create(self.options),
RerunProc.create(self.options),
execproc,
]
procs = [p for p in procs if p]
self._prepare_procs(procs)
loader.load_initial_tests()
# TODO(majeski): maybe some notification from loader would be better?
if combiner:
combiner.generate_initial_tests(self.options.j * 4)
# This starts up worker processes and blocks until all tests are
# processed.
execproc.run()
progress.finished()
print('>>> %d tests ran' % results.total)
if results.failed:
return utils.EXIT_CODE_FAILURES
# Indicate if a SIGINT or SIGTERM happened.
return sigproc.exit_code
def _is_testsuite_supported(self, suite):
return not self.options.combine_tests or suite.test_combiner_available()
if __name__ == '__main__':
sys.exit(NumFuzzer().execute()) # pragma: no cover

View File

@ -0,0 +1,26 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,88 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import signal
import copy
from ..local.process_utils import ProcessStats
from ..local import utils
class Output(object):
def __init__(self, exit_code=0, timed_out=False, stdout=None, stderr=None,
pid=None, start_time=0, end_time=0, stats=None):
self.exit_code = exit_code
self.timed_out = timed_out
self.stdout = stdout
self.stderr = stderr
self.pid = pid
self.start_time = start_time
self.end_time = end_time
self.stats = stats or ProcessStats()
@property
def duration(self):
return self.end_time - self.start_time
def without_text(self):
"""Returns copy of the output without stdout and stderr."""
other = copy.copy(self)
other.stdout = None
other.stderr = None
return other
def HasCrashed(self):
if utils.IsWindows():
return 0x80000000 & self.exit_code and not (0x3FFFFF00 & self.exit_code)
else:
# Timed out tests will have exit_code -signal.SIGTERM.
if self.timed_out:
return False
return (self.exit_code < 0 and
self.exit_code != -signal.SIGABRT)
def HasTimedOut(self):
return self.timed_out
def IsSuccess(self):
return not self.HasCrashed() and not self.HasTimedOut()
@property
def exit_code_string(self):
return "%d [%02X]" % (self.exit_code, self.exit_code & 0xffffffff)
class _NullOutput(Output):
"""Useful to signal that the binary has not been run."""
def __init__(self):
super(_NullOutput, self).__init__()
# Default instance of the _NullOutput class above.
NULL_OUTPUT = _NullOutput()

View File

@ -0,0 +1,46 @@
# 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.
from ..outproc import base as outproc_base
from ..testproc import base as testproc_base
# Only check the exit code of the predictable_wrapper in
# verify-predictable mode. Negative tests are not supported as they
# usually also don't print allocation hashes. There are two versions of
# negative tests: one specified by the test, the other specified through
# the status file (e.g. known bugs).
def get_outproc(test):
return OutProc(test.output_proc)
class OutProc(outproc_base.BaseOutProc):
"""Output processor wrapper for predictable mode. It has custom process and
has_unexpected_output implementation, but for all other methods it simply
calls wrapped output processor.
"""
def __init__(self, _outproc):
super(OutProc, self).__init__()
self._outproc = _outproc
def has_unexpected_output(self, output):
return output.exit_code != 0
def get_outcome(self, output):
return self._outproc.get_outcome(output)
@property
def negative(self):
return self._outproc.negative
@property
def expected_outcomes(self):
return self._outproc.expected_outcomes
class PredictableFilterProc(testproc_base.TestProcFilter):
def _filter(self, test):
return test.skip_predictable()

View File

@ -0,0 +1,649 @@
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import copy
import os
import re
import shlex
from pathlib import Path
from testrunner.outproc import base as outproc
from testrunner.local import command
from testrunner.local import statusfile
from testrunner.local import utils
from testrunner.local.variants import ALL_VARIANT_FLAGS
from testrunner.local.variants import INCOMPATIBLE_FLAGS_PER_VARIANT
from testrunner.local.variants import INCOMPATIBLE_FLAGS_PER_BUILD_VARIABLE
from testrunner.local.variants import INCOMPATIBLE_FLAGS_PER_EXTRA_FLAG
FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)")
# Patterns for additional resource files on Android. Files that are not covered
# by one of the other patterns below will be specified in the resources section.
RESOURCES_PATTERN = re.compile(r"//\s+Resources:(.*)")
# Pattern to auto-detect files to push on Android for statements like:
# load("path/to/file.js")
# d8.file.execute("path/to/file.js")
LOAD_PATTERN = re.compile(
r"(?:execute|load|readbuffer|read)\((?:'|\")([^'\"]+)(?:'|\")\)")
# Pattern to auto-detect files to push on Android for statements like:
# import foobar from "path/to/file.js"
# import {foo, bar} from "path/to/file.js"
# export {"foo" as "bar"} from "path/to/file.js"
MODULE_FROM_RESOURCES_PATTERN = re.compile(
r"(?:import|export).*?from\s*\(?['\"]([^'\"]+)['\"]",
re.MULTILINE | re.DOTALL)
# Pattern to detect files to push on Android for statements like:
# import "path/to/file.js"
# import("module.mjs").catch()...
# Require the matched path in one line. Note this might include some
# false matches, which is safe, since files are tested for existence.
MODULE_IMPORT_RESOURCES_PATTERN = re.compile(
r"import\s*\(?['\"]([^'\"\n]+)['\"]",
re.MULTILINE)
# Pattern to detect files to push on Android for statements like:
# import source x from "path/to/file.js"
# import.source("module.mjs").catch()...
# Require the matched path in one line. Note this might include some
# false matches, which is safe, since files are tested for existence.
MODULE_IMPORT_SOURCE_RESOURCES_PATTERN = re.compile(
r"import\s*\.?\s*source\s*\(?['\"]([^'\"\n]+)['\"]",
re.MULTILINE)
# Pattern to detect files to push on Android for expressions like:
# shadowRealm.importValue("path/to/file.js", "obj")
SHADOWREALM_IMPORTVALUE_RESOURCES_PATTERN = re.compile(
r"(?:importValue)\((?:'|\")([^'\"]+)(?:'|\")", re.MULTILINE | re.DOTALL)
# Pattern to detect and strip test262 frontmatter from tests to prevent false
# positives for MODULE_RESOURCES_PATTERN above.
TEST262_FRONTMATTER_PATTERN = re.compile(r"/\*---.*?---\*/", re.DOTALL)
TIMEOUT_LONG = "long"
def read_file(file):
with open(file, encoding='ISO-8859-1') as f:
return f.read()
class TestCase(object):
def __init__(self, suite, path, name):
self.suite = suite
# Path (pathlib) with the relative test path, e.g. 'test-api/foo'.
self.path = Path(path)
# Sting with a posix path to identify test in the status file and
# at the command line.
self.name = name
# String that identifies subtests.
self.subtest_id = None
# Name of the used testing variant.
self.variant = None
# List of strings, flags specific to this test.
self.variant_flags = []
# Fields used by the test processors.
self.origin = None # Test that this test is subtest of.
# Processor that created this subtest, initialised to a default value
self.processor = DuckProcessor()
self.procid = '%s/%s' % (self.suite.name, self.name) # unique id
self.keep_output = False # Can output of this test be dropped
self._random_seed = None # Overrides test config value if not None
# Outcomes
self._statusfile_outcomes = None
self._expected_outcomes = None
self._checked_flag_contradictions = False
self._statusfile_flags = None
self.expected_failure_reason = None
self._prepare_outcomes()
def create_subtest(self, processor, subtest_id, variant=None, flags=None,
keep_output=False, random_seed=None):
subtest = copy.copy(self)
subtest.origin = self
subtest.processor = processor
subtest.subtest_id = subtest_id
subtest.procid += f'.{subtest.processor_name}-{subtest_id}'
subtest.keep_output |= keep_output
if random_seed:
subtest._random_seed = random_seed
if flags:
subtest.variant_flags = subtest.variant_flags + flags
if variant is not None:
assert self.variant is None
subtest.variant = variant
subtest._prepare_outcomes()
return subtest
def _prepare_outcomes(self, force_update=True):
if force_update or self._statusfile_outcomes is None:
def is_flag(outcome):
return outcome.startswith('--')
def not_flag(outcome):
return not is_flag(outcome)
outcomes = self.suite.statusfile_outcomes(self.name, self.variant)
self._statusfile_outcomes = list(filter(not_flag, outcomes))
self._statusfile_flags = list(filter(is_flag, outcomes))
self._expected_outcomes = (
self._parse_status_file_outcomes(self._statusfile_outcomes))
def _parse_status_file_outcomes(self, outcomes):
if (statusfile.FAIL_SLOPPY in outcomes and
'--use-strict' not in self.variant_flags):
return outproc.OUTCOMES_FAIL
expected_outcomes = []
if (statusfile.FAIL in outcomes or
statusfile.FAIL_OK in outcomes):
expected_outcomes.append(statusfile.FAIL)
if statusfile.CRASH in outcomes:
expected_outcomes.append(statusfile.CRASH)
# Do not add PASS if there is nothing else. Empty outcomes are converted to
# the global [PASS].
if expected_outcomes and statusfile.PASS in outcomes:
expected_outcomes.append(statusfile.PASS)
# Avoid creating multiple instances of a list with a single FAIL.
if expected_outcomes == outproc.OUTCOMES_FAIL:
return outproc.OUTCOMES_FAIL
return expected_outcomes or outproc.OUTCOMES_PASS
def allow_timeouts(self):
if self.expected_outcomes == outproc.OUTCOMES_PASS:
self._expected_outcomes = outproc.OUTCOMES_PASS_OR_TIMEOUT
elif self.expected_outcomes == outproc.OUTCOMES_FAIL:
self._expected_outcomes = outproc.OUTCOMES_FAIL_OR_TIMEOUT
elif statusfile.TIMEOUT not in self.expected_outcomes:
self._expected_outcomes = (
self.expected_outcomes + [statusfile.TIMEOUT])
def allow_pass(self):
if self.expected_outcomes == outproc.OUTCOMES_TIMEOUT:
self._expected_outcomes = outproc.OUTCOMES_PASS_OR_TIMEOUT
elif self.expected_outcomes == outproc.OUTCOMES_FAIL:
self._expected_outcomes = outproc.OUTCOMES_FAIL_OR_PASS
elif statusfile.PASS not in self.expected_outcomes:
self._expected_outcomes = (
self.expected_outcomes + [statusfile.PASS])
# TODO(jgruber): Due to flag contradiction logic complexity, we will never
# fully match the v8 logic here. What we should do instead is simply ask v8
# whether given flags produce a flag contradiction or not, and use that to
# determine the expected outcome. E.g.: add a flag to d8 called
# --only-check-flag-contradictions, which exits with an appropriate code
# after running flag contradiction logic.
@property
def expected_outcomes(self):
def is_flag(maybe_flag):
return maybe_flag.startswith("--") # Best-effort heuristic.
# Filter to flags, e.g.: ["--foo", "3", "--bar"] -> ["--foo", "--bar"].
def filter_flags(normalized_flags):
return [f for f in normalized_flags if is_flag(f)];
def normalize_flag(flag):
return flag.replace("_", "-").replace("--no-", "--no")
def normalize_flags(flags):
return [normalize_flag(flag) for flag in filter_flags(flags)]
# Note this can get it wrong if the flag name starts with the characters
# "--no" where "no" is part of the flag name, e.g. "--nobodys-perfect".
# In that case the negation "--bodys-perfect" would be returned. This is
# a weakness we accept and hope to never run into.
def negate_flag(normalized_flag):
return ("--" + normalized_flag[4:] if normalized_flag.startswith("--no")
else "--no" + normalized_flag[2:])
def negate_flags(normalized_flags):
return [negate_flag(flag) for flag in normalized_flags]
def find_flag(conflicting_flag, flags):
conflicting_flag = normalize_flag(conflicting_flag)
if conflicting_flag in flags:
return conflicting_flag
if conflicting_flag.endswith("*"):
conflicting_flag = conflicting_flag[:-1]
for flag in flags:
if flag.startswith(conflicting_flag):
return flag
return None
def check_flags(incompatible_flags, actual_flags, rule, other_flag=None):
for incompatible_flag in incompatible_flags:
conflicting_flag = find_flag(incompatible_flag, actual_flags)
if conflicting_flag and (conflicting_flag != other_flag):
self._statusfile_outcomes = outproc.OUTCOMES_FAIL
self._expected_outcomes = outproc.OUTCOMES_FAIL
self.expected_failure_reason = (
"Rule " + rule + " in " +
"tools/testrunner/local/variants.py expected a flag " +
"contradiction error with " + incompatible_flag + ".")
def remove_flags_after(flags, flag):
try:
pos = flags.index(normalize_flag(flag))
except:
pass
else:
flags = flags[0:pos]
return flags
# Flags can be ignored with respect to contradictions by passing
# --fuzzing or --no-abort-on-contradictory-flags, which ignores all
# following flags; or by passing --allow-overwriting-for-next-flag,
# which ignores just the next flag. Remove these flags from the list.
# See Flag::ShouldCheckFlagContradictions.
def remove_ignored_flags(flags):
flags = remove_flags_after(flags, "--fuzzing")
flags = remove_flags_after(flags, "--no-abort-on-contradictory-flags")
flag_aofnf = normalize_flag("--allow-overwriting-for-next-flag")
while flag_aofnf in flags:
pos = flags.index(flag_aofnf)
flags.pop(pos)
flags.pop(pos)
return flags
if not self._checked_flag_contradictions:
self._checked_flag_contradictions = True
test_flags = remove_ignored_flags(normalize_flags(self.get_flags()))
test_flags_without_extra = remove_ignored_flags(
normalize_flags(self.get_flags_without_extra()))
extra_flags = remove_ignored_flags(
normalize_flags(self._get_extra_flags()))
# Contradiction: flags contains both a flag --foo and its negation --no-foo.
check_flags(negate_flags(test_flags), test_flags, "Flag negations")
# Contradiction: flags are incompatible with the variant.
if self.variant in INCOMPATIBLE_FLAGS_PER_VARIANT:
check_flags(INCOMPATIBLE_FLAGS_PER_VARIANT[self.variant], test_flags,
"INCOMPATIBLE_FLAGS_PER_VARIANT[\"" + self.variant + "\"]")
# Contradiction: flags are incompatible with the build.
for var, flags in INCOMPATIBLE_FLAGS_PER_BUILD_VARIABLE.items():
if var.startswith("!"):
# `var` is negated, apply the rule if the build variable is NOT set.
if not self.suite.statusfile.variables[var[1:]]:
check_flags(
flags, test_flags,
"INCOMPATIBLE_FLAGS_PER_BUILD_VARIABLE[\"" + var + "\"]")
else:
if self.suite.statusfile.variables[var]:
check_flags(
flags, test_flags,
"INCOMPATIBLE_FLAGS_PER_BUILD_VARIABLE[\"" + var + "\"]")
# Contradiction: flags passed through --extra-flags are incompatible with
# other test flags.
for extra_flag, flags in INCOMPATIBLE_FLAGS_PER_EXTRA_FLAG.items():
flag = find_flag(extra_flag, extra_flags)
if not flag:
continue
check_flags(flags, test_flags_without_extra,
"INCOMPATIBLE_FLAGS_PER_EXTRA_FLAG[\"" + extra_flag + "\"]",
flag)
return self._expected_outcomes
@property
def test_config(self):
return self.suite.test_config
@property
def framework_name(self):
return self.test_config.framework_name
@property
def shard_id(self):
return self.test_config.shard_id
@property
def shard_count(self):
return self.test_config.shard_count
@property
def do_skip(self):
return (statusfile.SKIP in self._statusfile_outcomes and
not self.test_config.run_skipped)
@property
def is_heavy(self):
return statusfile.HEAVY in self._statusfile_outcomes
@property
def is_slow(self):
return statusfile.SLOW in self._statusfile_outcomes
@property
def is_fail_ok(self):
return statusfile.FAIL_OK in self._statusfile_outcomes
@property
def is_pass_or_fail(self):
return (statusfile.PASS in self._statusfile_outcomes and
statusfile.FAIL in self._statusfile_outcomes and
statusfile.CRASH not in self._statusfile_outcomes)
@property
def is_fail(self):
return (statusfile.FAIL in self._statusfile_outcomes and
statusfile.PASS not in self._statusfile_outcomes)
@property
def only_standard_variant(self):
return statusfile.NO_VARIANTS in self._statusfile_outcomes
@property
def shell(self):
return self.get_shell()
def skip_rdb(self, result):
return False
def get_command(self, ctx):
params = self._get_cmd_params()
env = self._get_cmd_env()
shell_flags = self._get_shell_flags()
timeout = self._get_timeout(params)
return self._create_cmd(ctx, shell_flags + params, env, timeout)
def _get_cmd_params(self):
"""Gets all command parameters and combines them in the following order:
- files [empty by default]
- all flags
"""
return (self._get_files_params() + self.get_flags())
def get_flags(self):
"""Gets all flags and combines them in the following order:
- random seed
- mode flags (based on chosen mode)
- extra flags (from command line)
- user flags (variant/fuzzer flags)
- source flags (from source code) [empty by default]
- test-suite flags
- statusfile flags
The best way to modify how parameters are created is to only override
methods for getting partial parameters.
"""
return (
self._get_random_seed_flags() +
self._get_mode_flags() +
self._get_extra_flags() +
self._get_variant_flags() +
self._get_source_flags() +
self._get_suite_flags() +
self._get_statusfile_flags()
)
def get_flags_without_extra(self):
"""Gets all flags except extra, and combines them in the following order:
- random seed
- mode flags (based on chosen mode)
- user flags (variant/fuzzer flags)
- source flags (from source code) [empty by default]
- test-suite flags
- statusfile flags
"""
return (self._get_random_seed_flags() + self._get_mode_flags() +
self._get_variant_flags() + self._get_source_flags() +
self._get_suite_flags() + self._get_statusfile_flags())
def _get_cmd_env(self):
return {}
def _get_files_params(self):
return []
def _get_timeout_param(self):
return None
def _get_random_seed_flags(self):
return ['--random-seed=%d' % self.random_seed]
@property
def random_seed(self):
return self._random_seed or self.test_config.random_seed
def _get_extra_flags(self):
return self.test_config.extra_flags
def _get_variant_flags(self):
return self.variant_flags
def _get_statusfile_flags(self):
"""Gets runtime flags from a status file.
Every outcome that starts with "--" is a flag.
"""
return self._statusfile_flags
def _get_mode_flags(self):
return self.test_config.mode_flags
def _get_source_flags(self):
return []
def _get_suite_flags(self):
return []
def _get_shell_flags(self):
return []
def _get_timeout(self, params):
timeout = self.test_config.timeout
if "--jitless" in params:
timeout *= 2
if "--no-turbofan" in params:
timeout *= 2
if "--noenable-vfp3" in params:
timeout *= 2
if self._get_timeout_param() == TIMEOUT_LONG:
timeout *= 10
if self.is_slow:
timeout *= 4
return timeout
def get_shell(self):
raise NotImplementedError()
def path_and_suffix(self, suffix):
return self.path.with_name(self.path.name + suffix)
@property
def path_js(self):
return self.path_and_suffix('.js')
@property
def path_mjs(self):
return self.path_and_suffix('.mjs')
def _create_cmd(self, ctx, params, env, timeout):
return ctx.command(
cmd_prefix=self.test_config.command_prefix,
shell=ctx.platform_shell(self.get_shell(), params,
self.test_config.shell_dir),
args=params,
env=env,
timeout=timeout,
verbose=self.test_config.verbose,
test_case=self,
handle_sigterm=True,
log_process_stats=self.test_config.log_process_stats,
)
def _parse_source_flags(self, source=None):
source = source or self.get_source()
flags = []
for match in re.findall(FLAGS_PATTERN, source):
flags += shlex.split(match.strip())
return flags
def is_source_available(self):
return self._get_source_path() is not None
def get_source(self):
return read_file(self._get_source_path())
def _get_source_path(self):
return None
def skip_predictable(self):
"""Returns True if the test case is not suitable for predictable testing."""
return True
@property
def output_proc(self):
if self.expected_outcomes is outproc.OUTCOMES_PASS:
return outproc.DEFAULT
return outproc.OutProc(self.expected_outcomes)
def __cmp__(self, other):
# Make sure that test cases are sorted correctly if sorted without
# key function. But using a key function is preferred for speed.
def cmp(x, y):
return (x > y) - (x < y)
return cmp(
(self.suite.name, self.name, self.variant),
(other.suite.name, other.name, other.variant)
)
@property
def full_name(self):
return self.suite.name + '/' + self.name
def __str__(self):
return self.full_name
def test_suffixes(self):
suffixes = self.origin.test_suffixes() if self.origin else []
current_suffix = self.processor.test_suffix(self)
if current_suffix:
suffixes.append(str(current_suffix))
return suffixes
@property
def rdb_test_id(self):
suffixes = '/'.join(self.test_suffixes())
full_suffix = ('//' + suffixes) if suffixes else ''
return self.full_name + full_suffix
@property
def processor_name(self):
return self.processor.name
def _get_resources_for_file(self, file):
"""Returns for a given file a list of absolute paths of files needed by the
given file.
"""
source = read_file(file)
result = []
def add_path(path):
result.append(Path(path).resolve())
def add_import_path(import_path):
add_path(file.parent / import_path)
def strip_test262_frontmatter(input):
return TEST262_FRONTMATTER_PATTERN.sub('', input)
for match in RESOURCES_PATTERN.finditer(source):
# There are several resources per line. Relative to base dir.
for path in match.group(1).strip().split():
add_path(path)
# Strip test262 frontmatter before looking for load() and import/export
# statements.
source = strip_test262_frontmatter(source)
for match in LOAD_PATTERN.finditer(source):
# Files in load statements are relative to base dir.
add_path(match.group(1))
# Imported files are relative to the file importing them.
for match in MODULE_FROM_RESOURCES_PATTERN.finditer(source):
add_import_path(match.group(1))
for match in MODULE_IMPORT_RESOURCES_PATTERN.finditer(source):
add_import_path(match.group(1))
for match in MODULE_IMPORT_SOURCE_RESOURCES_PATTERN.finditer(source):
add_import_path(match.group(1))
for match in SHADOWREALM_IMPORTVALUE_RESOURCES_PATTERN.finditer(source):
add_import_path(match.group(1))
return result
def get_android_resources(self):
"""Returns a list of absolute paths with additional files needed by the
test case.
Used to push additional files to Android devices.
"""
if not self._get_source_path():
return []
result = set()
to_check = [self._get_source_path()]
# Recurse over all files until reaching a fixpoint.
while to_check:
next_resource = to_check.pop()
result.add(next_resource)
for resource in self._get_resources_for_file(next_resource):
# Only add files that exist on disc. The pattens we check for give some
# false positives otherwise.
if resource not in result and resource.exists():
to_check.append(resource)
return sorted(list(result))
class DuckProcessor:
"""Dummy default processor for original tests implemented by duck-typing."""
def test_suffix(self, test):
return None
@property
def name(self):
return None
class D8TestCase(TestCase):
def get_shell(self):
return "d8"
def _get_shell_flags(self):
return ['--test']
def _get_extra_flags(self):
return self.test_config.extra_flags + self.test_config.extra_d8_flags
def skip_predictable(self):
"""Returns True if the test case is not suitable for predictable testing."""
return (statusfile.FAIL in self.expected_outcomes or
self.output_proc.negative)

View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
# Copyright 2022 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 os
import sys
import unittest
from pathlib import Path
# Needed because the test runner contains relative imports.
TOOLS_PATH = Path(__file__).resolve().parent.parent.parent
sys.path.append(str(TOOLS_PATH))
from testrunner.objects.testcase import TestCase
class TestCaseTest(unittest.TestCase):
def testSubtestsProperties(self):
test = TestCase(
suite=FakeSuite(),
path='far/away',
name='parent')
self.assertEqual(test.rdb_test_id, 'fakeSuite/parent')
# provide by DuckProcessor
self.assertEqual(test.processor.name, None)
self.assertEqual(test.procid, 'fakeSuite/parent')
self.assertEqual(test.keep_output, False)
subtest = test.create_subtest(FakeProcessor(), 0, keep_output=True)
self.assertEqual(subtest.rdb_test_id, 'fakeSuite/parent//fakep/0')
# provide by FakeProcessor
self.assertEqual(subtest.processor.name, 'fake_processor1')
self.assertEqual(subtest.procid, 'fakeSuite/parent.fake_processor1-0')
self.assertEqual(subtest.keep_output, True)
subsubtest = subtest.create_subtest(FakeProcessor(), 1)
self.assertEqual(subsubtest.rdb_test_id,
'fakeSuite/parent//fakep/0/fakep/1')
# provide by FakeProcessor
self.assertEqual(subsubtest.processor.name, 'fake_processor2')
self.assertEqual(subsubtest.procid,
'fakeSuite/parent.fake_processor1-0.fake_processor2-1')
self.assertEqual(subsubtest.keep_output, True)
class FakeSuite:
@property
def name(self):
return 'fakeSuite'
def statusfile_outcomes(self, name, variant):
return []
class FakeProcessor:
instance_count = 0
def __init__(self):
FakeProcessor.instance_count += 1
self.idx = FakeProcessor.instance_count
@property
def name(self):
return f'fake_processor{self.idx}'
def test_suffix(self, test):
return f'fakep/{test.subtest_id}'
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,3 @@
# Copyright 2018 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.

232
deps/v8/tools/testrunner/outproc/base.py vendored Normal file
View File

@ -0,0 +1,232 @@
# 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.
from itertools import zip_longest
from ..testproc.base import (
DROP_RESULT, DROP_OUTPUT, DROP_PASS_OUTPUT, DROP_PASS_STDOUT)
from ..local import statusfile
from ..testproc.result import Result
import difflib
OUTCOMES_PASS = [statusfile.PASS]
OUTCOMES_FAIL = [statusfile.FAIL]
OUTCOMES_TIMEOUT = [statusfile.TIMEOUT]
OUTCOMES_PASS_OR_TIMEOUT = [statusfile.PASS, statusfile.TIMEOUT]
OUTCOMES_FAIL_OR_TIMEOUT = [statusfile.FAIL, statusfile.TIMEOUT]
OUTCOMES_FAIL_OR_PASS = [statusfile.FAIL, statusfile.PASS]
class BaseOutProc(object):
def process(self, output, reduction=None):
has_unexpected_output = self.has_unexpected_output(output)
if has_unexpected_output:
self.regenerate_expected_files(output)
return self._create_result(has_unexpected_output, output, reduction)
def regenerate_expected_files(self, output):
return
def has_unexpected_output(self, output):
return self.get_outcome(output) not in self.expected_outcomes
def _create_result(self, has_unexpected_output, output, reduction):
"""Creates Result instance. When reduction is passed it tries to drop some
parts of the result to save memory and time needed to send the result
across process boundary. None disables reduction and full result is created.
"""
if reduction == DROP_RESULT:
return None
error_details = \
self._get_error_details(output) if has_unexpected_output else None
if reduction == DROP_OUTPUT:
return Result(has_unexpected_output, None, error_details=error_details)
if not has_unexpected_output:
if reduction == DROP_PASS_OUTPUT:
return Result(has_unexpected_output, None)
if reduction == DROP_PASS_STDOUT:
return Result(has_unexpected_output, output.without_text())
return Result(has_unexpected_output, output, error_details=error_details)
def get_outcome(self, output):
if output.HasCrashed():
return statusfile.CRASH
elif output.HasTimedOut():
return statusfile.TIMEOUT
elif self._has_failed(output):
return statusfile.FAIL
else:
return statusfile.PASS
def _has_failed(self, output):
execution_failed = self._is_failure_output(output)
if self.negative:
return not execution_failed
return execution_failed
def _is_failure_output(self, output):
return output.exit_code != 0
def _get_error_details(self, output):
return None
@property
def negative(self):
return False
@property
def expected_outcomes(self):
raise NotImplementedError()
class Negative(object):
@property
def negative(self):
return True
class PassOutProc(BaseOutProc):
"""Output processor optimized for positive tests expected to PASS."""
def has_unexpected_output(self, output):
return self.get_outcome(output) != statusfile.PASS
@property
def expected_outcomes(self):
return OUTCOMES_PASS
class NegPassOutProc(Negative, PassOutProc):
"""Output processor optimized for negative tests expected to PASS"""
pass
class OutProc(BaseOutProc):
"""Output processor optimized for positive tests with expected outcomes
different than a single PASS.
"""
def __init__(self, expected_outcomes):
self._expected_outcomes = expected_outcomes
@property
def expected_outcomes(self):
return self._expected_outcomes
# TODO(majeski): Inherit from PassOutProc in case of OUTCOMES_PASS and remove
# custom get/set state.
def __getstate__(self):
d = self.__dict__
if self._expected_outcomes is OUTCOMES_PASS:
d = d.copy()
del d['_expected_outcomes']
return d
def __setstate__(self, d):
if '_expected_outcomes' not in d:
d['_expected_outcomes'] = OUTCOMES_PASS
self.__dict__.update(d)
# TODO(majeski): Override __reduce__ to make it deserialize as one instance.
DEFAULT = PassOutProc()
DEFAULT_NEGATIVE = NegPassOutProc()
class ExpectedOutProc(OutProc):
"""Output processor that has is_failure_output depending on comparing the
output with the expected output.
"""
def __init__(self, expected_outcomes, expected_filename,
regenerate_expected_files=False):
super(ExpectedOutProc, self).__init__(expected_outcomes)
self._expected_filename = expected_filename
self._regenerate_expected_files = regenerate_expected_files
def _is_failure_output(self, output):
if output.exit_code != 0:
return True
with open(self._expected_filename, 'r', encoding='utf-8') as f:
expected_lines = f.readlines()
for act_iterator in self._act_block_iterator(output):
for expected, actual in zip_longest(
self._expected_iterator(expected_lines),
act_iterator,
fillvalue=''
):
if expected != actual:
return True
return False
def regenerate_expected_files(self, output):
if not self._regenerate_expected_files:
return
lines = output.stdout.splitlines()
with open(self._expected_filename, 'w') as f:
for _, line in enumerate(lines):
f.write(line+'\n')
def _get_error_details(self, output):
"""Return diff between expected and actual output."""
expected = open(self._expected_filename, 'r', encoding='utf-8').readlines()
actual = output.stdout.splitlines(True)
if expected == actual:
return None
lines = difflib.unified_diff(
expected, actual, fromfile=str(self._expected_filename),
tofile='<actual>')
return 'Output does not match expectation:\n' + ''.join(lines)
def _act_block_iterator(self, output):
"""Iterates over blocks of actual output lines."""
lines = output.stdout.splitlines()
start_index = 0
found_eqeq = False
for index, line in enumerate(lines):
# If a stress test separator is found:
if line.startswith('=='):
# Iterate over all lines before a separator except the first.
if not found_eqeq:
found_eqeq = True
else:
yield self._actual_iterator(lines[start_index:index])
# The next block of output lines starts after the separator.
start_index = index + 1
# Iterate over complete output if no separator was found.
if not found_eqeq:
yield self._actual_iterator(lines)
def _actual_iterator(self, lines):
return self._iterator(lines, self._ignore_actual_line)
def _expected_iterator(self, lines):
return self._iterator(lines, self._ignore_expected_line)
def _ignore_actual_line(self, line):
"""Ignore empty lines, valgrind output, Android output and trace
incremental marking output.
"""
if not line:
return True
return (line.startswith('==') or
line.startswith('**') or
line.startswith('ANDROID') or
line.startswith('###') or
# Android linker warning.
line.startswith('WARNING: linker:') or
# FIXME(machenbach): The test driver shouldn't try to use slow
# asserts if they weren't compiled. This fails in optdebug=2.
line == 'Warning: unknown flag --enable-slow-asserts.' or
line == 'Try --help for options')
def _ignore_expected_line(self, line):
return not line
def _iterator(self, lines, ignore_predicate):
for line in lines:
line = line.strip()
if not ignore_predicate(line):
yield line

View File

@ -0,0 +1,19 @@
# Copyright 2025 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 platform
from . import base
# -1 gets converted to 0xff on POSIX and 0xffffffff on Windows.
MINUS_ONE_EXIT_CODE = 0xffffffff if platform.system() == 'Windows' else 0xff
class OutProc(base.OutProc):
"""
Fuzzers are allowed to return -1 or 0 (see
https://llvm.org/docs/LibFuzzer.html#rejecting-unwanted-inputs).
"""
def _is_failure_output(self, output):
return output.exit_code != 0 and output.exit_code != MINUS_ONE_EXIT_CODE

View File

@ -0,0 +1,74 @@
# Copyright 2018 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 re
from itertools import zip_longest
from . import base
class OutProc(base.ExpectedOutProc):
def __init__(self, expected_outcomes, basepath, expected_fail,
expected_filename, regenerate_expected_files):
super(OutProc, self).__init__(expected_outcomes, expected_filename,
regenerate_expected_files)
self._basepath = basepath
self._expected_fail = expected_fail
def _is_failure_output(self, output):
fail = output.exit_code != 0
if fail != self._expected_fail:
return True
expected_lines = []
# Can't use utils.ReadLinesFrom() here because it strips whitespace.
with open(self._basepath.with_suffix('.out')) as f:
for line in f:
if line.startswith("#") or not line.strip():
continue
expected_lines.append(line)
raw_lines = output.stdout.splitlines()
actual_lines = [ s for s in raw_lines if not self._ignore_line(s) ]
if len(expected_lines) != len(actual_lines):
return True
# Try .js first, and fall back to .mjs.
# TODO(v8:9406): clean this up by never separating the path from
# the extension in the first place.
base_path = self._basepath.with_suffix('.js')
if not base_path.exists():
base_path = self._basepath.with_suffix('.mjs')
env = {
'basename': base_path.name,
}
for (expected, actual) in zip_longest(
expected_lines, actual_lines, fillvalue=''):
pattern = re.escape(expected.rstrip() % env)
pattern = pattern.replace('\\*', '.*')
pattern = pattern.replace('\\{NUMBER\\}', r'-?\d+(?:\.\d*)?')
# Note: The character sequence for printing an address in C++ is
# implementation defined.
pattern = pattern.replace('\\{ADDRESS\\}', r'(0x)?[0-9A-Fa-f]+')
pattern = '^%s$' % pattern
if not re.match(pattern, actual):
return True
return False
def _ignore_line(self, string):
"""Ignore empty lines, valgrind output, Android output."""
return (
not string or #
not string.strip() or #
string.startswith("==") or #
string.startswith("**") or #
string.startswith("ANDROID") or #
# Android linker warning.
string.startswith('WARNING: linker:') or #
# Testing on Android devices mixes stderr into stdout.
string ==
"V8 is running with experimental features enabled. Stability and security will suffer."
or #
string == "Concurrent maglev has been disabled for tracing.")

View File

@ -0,0 +1,39 @@
# Copyright 2018 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 difflib
from . import base
class OutProc(base.OutProc):
def __init__(self, expected_outcomes):
super(OutProc, self).__init__(expected_outcomes)
def _is_failure_output(self, output):
MIN_EXPECTED_LINE_COUNT = 300 # Currently the actual count is 652.
if len(output.stdout.splitlines()) < MIN_EXPECTED_LINE_COUNT:
output.stdout = ("Plausibility check failed. Output too short.\n\n" +
output.stdout)
return True
PLAUSIBILITY_CHECK_EXPECTED_STRINGS = [
"INSTANCE_TYPES = {",
"HEAP_NUMBER_TYPE",
"KNOWN_MAPS = {",
"HeapNumberMap",
"KNOWN_OBJECTS = {",
"TrueValue",
"HEAP_FIRST_PAGES = {",
"FRAME_MARKERS = (",
"INTERPRETED",
]
for expected_string in PLAUSIBILITY_CHECK_EXPECTED_STRINGS:
if not expected_string in output.stdout:
output.stdout = (
"Plausibility check failed. Expected string "
f"'{expected_string}' not found in output.\n") + output.stdout
return True
return False

View File

@ -0,0 +1,33 @@
# Copyright 2018 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.
from . import base
def _is_failure_output(self, output):
return (
output.exit_code != 0 or
'FAILED!' in output.stdout
)
class OutProc(base.OutProc):
"""Optimized for positive tests."""
OutProc._is_failure_output = _is_failure_output
class PassOutProc(base.PassOutProc):
"""Optimized for positive tests expected to PASS."""
PassOutProc._is_failure_output = _is_failure_output
class NegOutProc(base.Negative, OutProc):
pass
class NegPassOutProc(base.Negative, PassOutProc):
pass
MOZILLA_PASS_DEFAULT = PassOutProc()
MOZILLA_PASS_NEGATIVE = NegPassOutProc()

View File

@ -0,0 +1,70 @@
# Copyright 2018 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 re
from . import base
def _is_failure_output(output, is_async):
return (output.exit_code != 0 or 'FAILED!' in output.stdout or
(is_async and 'Test262:AsyncTestComplete' not in output.stdout))
class ExceptionOutProc(base.OutProc):
"""Output processor for tests with expected exception."""
def __init__(self,
expected_outcomes,
expected_exception=None,
negative=False,
is_async=False):
super(ExceptionOutProc, self).__init__(expected_outcomes)
self._expected_exception = expected_exception
self._negative = negative
self._async = is_async
@property
def negative(self):
return self._negative
def _is_failure_output(self, output):
if self._expected_exception != self._parse_exception(output.stdout):
return True
return _is_failure_output(output, self._async)
def _parse_exception(self, string):
# somefile:somelinenumber: someerror[: sometext]
# somefile might include an optional drive letter on windows e.g. "e:".
match = re.search(
'^(?:\w:)?[^:]*:[0-9]+: ([^: ]+?)($|: )', string, re.MULTILINE)
if match:
return match.group(1).strip()
else:
return None
class NoExceptionOutProc(base.OutProc):
"""Output processor optimized for tests without expected exception."""
def __init__(self, expected_outcomes, is_async=False):
super(NoExceptionOutProc, self).__init__(expected_outcomes)
self._async = is_async
def _is_failure_output(self, output):
return _is_failure_output(output, self._async)
class PassNoExceptionOutProc(base.PassOutProc):
"""
Output processor optimized for tests expected to PASS without expected
exception.
"""
def __init__(self, is_async=False):
super(PassNoExceptionOutProc, self).__init__()
self._async = is_async
def _is_failure_output(self, output):
return _is_failure_output(output, self._async)

View File

@ -0,0 +1,22 @@
# Copyright 2018 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.
from . import base
class OutProc(base.ExpectedOutProc):
def _is_failure_output(self, output):
if output.exit_code != 0:
return True
if output.exit_code == 0 and not output.stdout.strip():
# Special case for --exit-on-contradictory-flags. It lets tests exit
# fast with exit code 0. Normally passing webkit tests all have output.
return False
return super(OutProc, self)._is_failure_output(output)
def _ignore_expected_line(self, line):
return (
line.startswith('#') or
super(OutProc, self)._ignore_expected_line(line)
)

380
deps/v8/tools/testrunner/standard_runner.py vendored Executable file
View File

@ -0,0 +1,380 @@
#!/usr/bin/env python3
#
# 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.
from functools import reduce
from pathlib import Path
import datetime
import json
import os
import sys
import tempfile
import testrunner.base_runner as base_runner
from testrunner.local.variants import ALL_VARIANTS
from testrunner.objects import predictable
from testrunner.testproc.execution import ExecutionProc
from testrunner.testproc.expectation import ExpectationProc
from testrunner.testproc.filter import NameFilterProc, StatusFileFilterProc
from testrunner.testproc.loader import LoadProc
from testrunner.testproc.progress import ResultsTracker, ProgressProc
from testrunner.testproc.rerun import RerunProc
from testrunner.testproc.seed import SeedProc
from testrunner.testproc.sequence import SequenceProc
from testrunner.testproc.shard import ShardProc
from testrunner.testproc.timeout import TimeoutProc
from testrunner.testproc.variant import VariantProc
VARIANTS = ['default']
MORE_VARIANTS = [
'stress',
'stress_js_bg_compile_wasm_code_gc',
'stress_incremental_marking',
'future',
]
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': [
'jitless', 'nooptimization', 'no_wasm_traps', 'instruction_scheduling',
'always_sparkplug_and_stress_regexp_jit', 'turboshaft'
],
}
GC_STRESS_FLAGS = ['--gc-interval=500', '--stress-compaction',
'--concurrent-recompilation-queue-length=64',
'--concurrent-recompilation-delay=500',
'--concurrent-recompilation',
'--stress-flush-code', '--flush-bytecode',
'--wasm-code-gc', '--stress-wasm-code-gc']
RANDOM_GC_STRESS_FLAGS = ['--random-gc-interval=5000',
'--stress-compaction-random']
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 default_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(ALL_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('--max-heavy-tests', default=1, type='int',
help='Maximum number of heavy tests run in parallel')
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('--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('--flakiness-results',
help='Path to a file for storing flakiness json.')
def _predictable_wrapper(self):
return self.v8_root / 'tools' / 'predictable_wrapper.py'
def _process_options(self):
if self.options.sancov_dir:
self.sancov_dir = Path(self.options.sancov_dir)
if not self.sancov_dir.exists():
print('sancov-dir %s doesn\'t exist' % self.sancov_dir)
raise base_runner.TestRunnerError()
if self.options.gc_stress:
self.options.extra_flags += GC_STRESS_FLAGS
if self.options.random_gc_stress:
self.options.extra_flags += RANDOM_GC_STRESS_FLAGS
if self.build_config.asan:
self.options.extra_d8_flags.append('--invoke-weak-callbacks')
if self.options.novfp3:
self.options.extra_flags.append('--noenable-vfp3')
if self.options.no_variants: # pragma: no cover
print ('Option --no-variants is deprecated. '
'Pass --variants=default instead.')
assert not self.options.variants
self.options.variants = 'default'
if self.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 self.options.variants
self.options.variants = 'exhaustive'
if self.options.quickcheck:
assert not self.options.variants
self.options.variants = 'stress,default'
self.options.slow_tests = 'skip'
self.options.pass_fail_tests = 'skip'
if self.build_config.verify_predictable:
self.options.variants = 'default'
self.options.extra_flags.append('--predictable')
self.options.extra_flags.append('--verify-predictable')
self.options.extra_flags.append('--no-inline-new')
self.options.extra_flags.append('--omit-quit')
# Add predictable wrapper to command prefix.
self.options.command_prefix = (
[sys.executable, self._predictable_wrapper()] + self.options.command_prefix)
# TODO(machenbach): Figure out how to test a bigger subset of variants on
# msan.
if self.build_config.msan:
self.options.variants = 'default'
if self.options.variants == 'infra_staging':
self.options.variants = 'exhaustive'
self._variants = self._parse_variants(self.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', self.options.slow_tests)
CheckTestMode('pass|fail test', self.options.pass_fail_tests)
if not self.build_config.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 self.options.time and not self.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-")
self.options.json_test_results = self._temporary_json_output_file.name
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' # pragma: no cover
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, context):
variables = super(
StandardTestRunner, self)._get_statusfile_variables(context)
variables.update({
'gc_stress': self.options.gc_stress or self.options.random_gc_stress,
'gc_fuzzer': self.options.random_gc_stress,
'novfp3': self.options.novfp3,
})
return variables
def _create_sequence_proc(self):
"""Create processor for sequencing heavy tests on swarming."""
return SequenceProc(self.options.max_heavy_tests) if self.options.swarming else None
def _create_expectation_proc(self):
"""Create a processor with more forgiving expectations when using the
num-fuzzer framework flavor.
"""
return ExpectationProc() if self.framework_name == 'num_fuzzer' else None
def _do_execute(self, tests, args, ctx):
jobs = self.options.j
print('>>> Running with test processors')
loader = LoadProc(tests, initial_batch_size=self.options.j * 2)
results = ResultsTracker.create(self.options)
outproc_factory = None
if self.build_config.verify_predictable:
outproc_factory = predictable.get_outproc
execproc = ExecutionProc(ctx, jobs, outproc_factory)
sigproc = self._create_signal_proc()
progress = ProgressProc(ctx, self.options, tests.test_count_estimate)
procs = [
loader,
NameFilterProc(args) if args else None,
VariantProc(self._variants),
StatusFileFilterProc(self.options.slow_tests,
self.options.pass_fail_tests),
self._create_predictable_filter(),
ShardProc.create(self.options),
self._create_expectation_proc(),
self._create_seed_proc(),
self._create_sequence_proc(),
sigproc,
progress,
results,
TimeoutProc.create(self.options),
RerunProc.create(self.options),
execproc,
]
procs = [p for p in procs if p]
self._prepare_procs(procs)
loader.load_initial_tests()
# This starts up worker processes and blocks until all tests are
# processed.
requirement = max(p._requirement for p in procs)
execproc.run(requirement)
progress.finished()
results.standard_show(tests)
if self.options.time:
self._print_durations()
return sigproc.worst_exit_code(results)
def _print_durations(self):
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 Path(self.options.json_test_results).exists()
with open(self.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.verify_predictable:
return None
return predictable.PredictableFilterProc()
def _create_seed_proc(self):
if self.options.random_seed_stress_count == 1:
return None
return SeedProc(self.options.random_seed_stress_count, self.options.random_seed,
self.options.j * 4)
if __name__ == '__main__':
sys.exit(StandardTestRunner().execute()) # pragma: no cover

View File

@ -0,0 +1,663 @@
#!/usr/bin/env python3
# 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.
"""
Global system tests for V8 test runners and fuzzers.
This hooks up the framework under tools/testrunner testing high-level scenarios
with different test suite extensions and build configurations.
"""
# TODO(machenbach): Mock out util.GuessOS to make these tests really platform
# independent.
# TODO(machenbach): Move coverage recording to a global test entry point to
# include other unittest suites in the coverage report.
# TODO(machenbach): Coverage data from multiprocessing doesn't work.
# TODO(majeski): Add some tests for the fuzzers.
from collections import deque
from pathlib import Path
import re
import sys
import unittest
from mock import patch
TOOLS_ROOT = Path(__file__).resolve().parent.parent
sys.path.append(str(TOOLS_ROOT))
from testrunner import standard_runner
from testrunner import num_fuzzer
from testrunner.testproc import base
from testrunner.testproc import fuzzer
from testrunner.testproc import resultdb
from testrunner.testproc.resultdb_server_mock import RDBMockServer
from testrunner.utils.test_utils import (
temp_base,
TestRunnerTest,
with_json_output,
FakeOSContext,
)
class StandardRunnerTest(TestRunnerTest):
def setUp(self):
self.mock_rdb_server = RDBMockServer()
resultdb.TESTING_SINK = dict(
auth_token='none', address=self.mock_rdb_server.address)
def tearDown(self):
resultdb.TESTING_SINK = None
def get_runner_class(self):
return standard_runner.StandardTestRunner
def testPass(self):
"""Test running only passing tests in two variants.
Also test printing durations.
"""
result = self.run_tests(
'--progress=verbose',
'--variants=default,stress',
'--time',
'sweet/bananas',
'sweet/raspberries',
)
result.stdout_includes('sweet/bananas default: PASS')
# TODO(majeski): Implement for test processors
# self.assertIn('Total time:', result.stderr, result)
# self.assertIn('sweet/bananas', result.stderr, result)
result.has_returncode(0)
def testPassHeavy(self):
"""Test running with some tests marked heavy."""
result = self.run_tests(
'--progress=verbose',
'--variants=nooptimization',
'-j2',
'sweet',
baseroot='testroot3',
)
result.stdout_includes('7 tests ran')
result.has_returncode(0)
def testShardedProc(self):
for shard in [1, 2]:
result = self.run_tests(
'--progress=verbose',
'--variants=default,stress',
'--shard-count=2',
'--shard-run=%d' % shard,
'sweet/blackberries',
'sweet/raspberries',
infra_staging=False,
)
# One of the shards gets one variant of each test.
result.stdout_includes('2 tests ran')
if shard == 1:
result.stdout_includes('sweet/raspberries default')
result.stdout_includes('sweet/raspberries stress')
result.has_returncode(0)
else:
result.stdout_includes(
'sweet/blackberries default: FAIL')
result.stdout_includes(
'sweet/blackberries stress: FAIL')
result.has_returncode(1)
@unittest.skip("incompatible with test processors")
def testSharded(self):
"""Test running a particular shard."""
for shard in [1, 2]:
result = self.run_tests(
'--progress=verbose',
'--variants=default,stress',
'--shard-count=2',
'--shard-run=%d' % shard,
'sweet/bananas',
'sweet/raspberries',
)
# One of the shards gets one variant of each test.
result.stdout_includes('Running 2 tests')
result.stdout_includes('sweet/bananas')
result.stdout_includes('sweet/raspberries')
result.has_returncode(0)
def testFail(self):
"""Test running only failing tests in two variants."""
result = self.run_tests(
'--progress=verbose',
'--variants=default,stress',
'sweet/strawberries',
infra_staging=False,
)
result.stdout_includes('sweet/strawberries default: FAIL')
result.has_returncode(1)
def testGN(self):
"""Test setup with legacy GN out dir."""
result = self.run_tests('--gn', baseroot="testroot5", outdir='out.gn')
result.stdout_includes('>>> Latest GN build found: build')
result.stdout_includes('Build found: ')
result.stdout_includes('_v8_test/out.gn/build')
result.has_returncode(2)
def testMalformedJsonConfig(self):
"""Test running only failing tests in two variants."""
result = self.run_tests(baseroot="testroot4")
result.stdout_includes('contains invalid json')
result.stdout_includes('Failed to load build config')
result.stderr_includes('testrunner.base_runner.TestRunnerError')
result.has_returncode(5)
def testFailWithRerunAndJSON(self):
"""Test re-running a failing test and output to json."""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'--rerun-failures-count=2',
'--random-seed=123',
'--json-test-results', with_json_output,
'sweet/strawberries',
infra_staging=False,
)
result.stdout_includes('sweet/strawberries default: FAIL')
# With test processors we don't count reruns as separated failures.
# TODO(majeski): fix it?
result.stdout_includes('1 tests failed')
result.has_returncode(1)
# TODO(majeski): Previously we only reported the variant flags in the
# flags field of the test result.
# After recent changes we report all flags, including the file names.
# This is redundant to the command. Needs investigation.
result.json_content_equals('expected_test_results1.json')
def testRDB(self):
with self.with_fake_rdb() as records:
# sweet/bananaflakes fails first time on stress but passes on default
def tag_dict(tags):
return {t['key']: t['value'] for t in tags}
self.run_tests(
'--variants=default,stress',
'--rerun-failures-count=2',
'--time',
'sweet',
baseroot='testroot2',
infra_staging=False,
)
self.assertEqual(len(records), 3)
self.assertEqual(records[0]['testId'], 'sweet/bananaflakes//stress')
self.assertEqual(tag_dict(records[0]['tags'])['run'], '1')
self.assertFalse(records[0]['expected'])
self.assertEqual(records[1]['testId'], 'sweet/bananaflakes//stress')
self.assertEqual(tag_dict(records[1]['tags'])['run'], '2')
self.assertTrue(records[1]['expected'])
self.assertEqual(records[2]['testId'], 'sweet/bananaflakes//default')
self.assertEqual(tag_dict(records[2]['tags'])['run'], '1')
self.assertTrue(records[2]['expected'])
def testFlakeWithRerunAndJSON(self):
"""Test re-running a failing test and output to json."""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'--rerun-failures-count=2',
'--random-seed=123',
'--json-test-results', with_json_output,
'sweet',
baseroot='testroot2',
infra_staging=False,
)
result.stdout_includes('sweet/bananaflakes default: FAIL PASS')
result.stdout_includes('=== sweet/bananaflakes (flaky) ===')
result.stdout_includes('1 tests failed')
result.stdout_includes('1 tests were flaky')
result.has_returncode(1)
result.json_content_equals('expected_test_results2.json')
self.assertTrue(re.search(
r'sweet/bananaflakes default: FAIL \(\d+\.\d+:\d+\.\d+\)',
result.test_schedule))
self.assertTrue(re.search(
r'sweet/bananaflakes default: PASS \(\d+\.\d+:\d+\.\d+\)',
result.test_schedule))
def testAutoDetect(self):
"""Fake a build with several auto-detected options.
Using all those options at once doesn't really make much sense. This is
merely for getting coverage.
"""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'sweet/bananas',
config_overrides=dict(
arch="ia32",
asan=True,
cfi=True,
dcheck_always_on=True,
has_webassembly=True,
msan=True,
target_cpu='x86',
tsan=True,
ubsan=True,
use_sanitizer=True,
v8_target_cpu='x86',
))
result.stdout_includes('>>> Statusfile variables:')
result.stdout_includes(
"DEBUG_defined=False, arch=ia32, asan=True, byteorder=little, "
"cfi=True, code_comments=False, component_build=False, "
"dcheck_always_on=True, debug_code=False, debugging_features=False, "
"deopt_fuzzer=False, device_type=None, "
"dict_property_const_tracking=False, disassembler=False, "
"endurance_fuzzer=False, full_debug=False, gc_fuzzer=False, "
"gc_stress=False, gdbjit=False, has_jitless=False, has_maglev=False, "
"has_turbofan=False, has_webassembly=True, i18n=True, "
"interrupt_fuzzer=False, is_android=False, is_ios=False, "
"isolates=False, lite_mode=False, mode=debug, msan=True, "
"no_harness=False, no_simd_hardware=False, novfp3=False, "
"optimize_for_size=False, simulator_run=False, slow_dchecks=False, "
"system=linux, target_cpu=x86, tsan=True, ubsan=True, "
"use_sanitizer=True, v8_target_cpu=x86, verify_heap=False, "
"verify_predictable=False")
result.stdout_includes('>>> Running tests for ia32.release')
result.has_returncode(0)
# TODO(machenbach): Test some more implications of the auto-detected
# options, e.g. that the right env variables are set.
def testLimitedPreloading(self):
result = self.run_tests('--progress=verbose', '--variants=default', '-j1',
'sweet/*')
result.stdout_includes("sweet/mangoes default: PASS")
result.stdout_includes("sweet/cherries default: FAIL")
result.stdout_includes("sweet/apples default: FAIL")
result.stdout_includes("sweet/strawberries default: FAIL")
result.stdout_includes("sweet/bananas default: PASS")
result.stdout_includes("sweet/blackberries default: FAIL")
result.stdout_includes("sweet/raspberries default: PASS")
def testWithFakeContext(self):
with patch(
'testrunner.local.context.find_os_context_factory',
return_value=FakeOSContext):
result = self.run_tests(
'--progress=verbose',
'sweet',
)
result.stdout_includes('===>Starting stuff')
result.stdout_includes('>>> Running tests for x64.release\n'
'>>> Running with test processors\n')
result.stdout_includes('--- stdout ---\nfake stdout 1')
result.stdout_includes('--- stderr ---\nfake stderr 1')
result.stdout_includes('=== sweet/raspberries ===')
result.stdout_includes('=== sweet/cherries ===')
result.stdout_includes('=== sweet/apples ===')
result.stdout_includes('Command: fake_wrapper ')
result.stdout_includes(
'===\n'
'=== 4 tests failed\n'
'===\n'
'>>> 7 base tests produced 7 (100%) non-filtered tests\n'
'>>> 7 tests ran\n'
'<===Stopping stuff\n')
def testSkips(self):
"""Test skipping tests in status file for a specific variant."""
result = self.run_tests(
'--progress=verbose',
'--variants=nooptimization',
'sweet/strawberries',
infra_staging=False,
)
result.stdout_includes('0 tests ran')
result.has_returncode(2)
def testRunSkips(self):
"""Inverse the above. Test parameter to keep running skipped tests."""
result = self.run_tests(
'--progress=verbose',
'--variants=nooptimization',
'--run-skipped',
'sweet/strawberries',
)
result.stdout_includes('1 tests failed')
result.stdout_includes('1 tests ran')
result.has_returncode(1)
def testDefault(self):
"""Test using default test suites, though no tests are run since they don't
exist in a test setting.
"""
result = self.run_tests(
infra_staging=False,
)
result.stdout_includes('0 tests ran')
result.has_returncode(2)
def testNoBuildConfig(self):
"""Test failing run when build config is not found."""
result = self.run_tests(baseroot='wrong_path', with_build_config=False)
result.stdout_includes('Failed to load build config')
result.has_returncode(5)
def testInconsistentArch(self):
"""Test failing run when attempting to wrongly override the arch."""
result = self.run_tests('--arch=ia32')
result.stdout_includes(
'--arch value (ia32) inconsistent with build config (x64).')
result.has_returncode(5)
def testWrongVariant(self):
"""Test using a bogus variant."""
result = self.run_tests('--variants=meh')
result.has_returncode(5)
def testModeFromBuildConfig(self):
"""Test auto-detection of mode from build config."""
result = self.run_tests('--outdir=out/build', 'sweet/bananas')
result.stdout_includes('Running tests for x64.release')
result.has_returncode(0)
def testPredictable(self):
"""Test running a test in verify-predictable mode.
The test will fail because of missing allocation output. We verify that and
that the predictable flags are passed and printed after failure.
"""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'sweet/bananas',
infra_staging=False,
config_overrides=dict(verify_predictable=True),
)
result.stdout_includes('1 tests ran')
result.stdout_includes('sweet/bananas default: FAIL')
result.stdout_includes('Test had no allocation output')
result.stdout_includes('--predictable --verify-predictable')
result.has_returncode(1)
def testSlowArch(self):
"""Test timeout factor manipulation on slow architecture."""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'sweet/bananas',
config_overrides=dict(v8_target_cpu='arm64'),
)
# TODO(machenbach): We don't have a way for testing if the correct
# timeout was used.
result.has_returncode(0)
def testRandomSeedStressWithDefault(self):
"""Test using random-seed-stress feature has the right number of tests."""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'--random-seed-stress-count=2',
'sweet/bananas',
infra_staging=False,
)
result.stdout_includes('2 tests ran')
result.has_returncode(0)
def testRandomSeedStressWithSeed(self):
"""Test using random-seed-stress feature passing a random seed."""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'--random-seed-stress-count=2',
'--random-seed=123',
'sweet/strawberries',
)
result.stdout_includes('2 tests ran')
# We use a failing test so that the command is printed and we can verify
# that the right random seed was passed.
result.stdout_includes('--random-seed=123')
result.has_returncode(1)
def testRandomSeedStressWithNumfuzz(self):
"""Test using random-seed-stress feature with numfuzz flavor as used by
flake bisect for flakes on numfuzz.
"""
result = self.run_tests(
'--progress=verbose',
'--framework=num_fuzzer',
'--variants=default',
'--random-seed-stress-count=2',
'sweet/bananas',
'sweet/apples',
infra_staging=False,
baseroot='testroot7'
)
# The bananas test is expected to pass when --fuzzing, one of the numfuzz
# default flags is present. The apples test is expected to fail with this
# flag.
result.stdout_includes('sweet/bananas default: PASS')
result.stdout_includes('sweet/apples default: FAIL')
# We get everything twice due to the stress count above set to 2.
result.stdout_includes('2 tests failed')
result.stdout_includes('4 tests ran')
result.has_returncode(1)
def testSpecificVariants(self):
"""Test using NO_VARIANTS modifiers in status files skips the desire tests.
The test runner cmd line configures 4 tests to run (2 tests * 2 variants).
But the status file applies a modifier to each skipping one of the
variants.
"""
result = self.run_tests(
'--progress=verbose',
'--variants=default,stress',
'sweet/bananas',
'sweet/raspberries',
config_overrides=dict(asan=True),
)
# Both tests are either marked as running in only default or only
# slow variant.
result.stdout_includes('2 tests ran')
result.has_returncode(0)
def testDotsProgress(self):
result = self.run_tests(
'--progress=dots',
'sweet/cherries',
'sweet/bananas',
'--no-sorting', '-j1', # make results order deterministic
infra_staging=False,
)
result.stdout_includes('2 tests ran')
result.stdout_includes('F.')
result.has_returncode(1)
def testMonoProgress(self):
self._testCompactProgress('mono')
def testColorProgress(self):
self._testCompactProgress('color')
def _testCompactProgress(self, name):
result = self.run_tests(
'--progress=%s' % name,
'sweet/cherries',
'sweet/bananas',
infra_staging=False,
)
if name == 'color':
expected = ('\033[34m% 28\033[0m|'
'\033[32m+ 1\033[0m|'
'\033[31m- 1\033[0m]: Done')
else:
expected = '% 28|+ 1|- 1]: Done'
result.stdout_includes(expected)
result.stdout_includes('sweet/cherries')
result.stdout_includes('sweet/bananas')
result.has_returncode(1)
def testCompactErrorDetails(self):
result = self.run_tests(
'--progress=mono',
'sweet/cherries',
'sweet/strawberries',
infra_staging=False,
)
result.stdout_includes('sweet/cherries')
result.stdout_includes('sweet/strawberries')
result.stdout_includes('+Mock diff')
result.has_returncode(1)
def testExitAfterNFailures(self):
result = self.run_tests(
'--progress=verbose',
'--exit-after-n-failures=2',
'-j1',
'sweet/mangoes', # PASS
'sweet/strawberries', # FAIL
'sweet/blackberries', # FAIL
'sweet/raspberries', # should not run
)
result.stdout_includes('sweet/mangoes default: PASS')
result.stdout_includes('sweet/strawberries default: FAIL')
result.stdout_includes('Too many failures, exiting...')
result.stdout_includes('sweet/blackberries default: FAIL')
result.stdout_excludes('sweet/raspberries')
result.stdout_includes('2 tests failed')
result.stdout_includes('3 tests ran')
result.has_returncode(1)
def testHeavySequence(self):
"""Test a configuration with 2 heavy tests.
One heavy test will get buffered before being run.
"""
appended = 0
popped = 0
def mock_deque():
class MockQ():
def __init__(self):
self.buffer = deque()
def append(self, test):
nonlocal appended
self.buffer.append(test)
appended += 1
def popleft(self):
nonlocal popped
popped += 1
return self.buffer.popleft()
def __bool__(self):
return bool(self.buffer)
return MockQ()
# Swarming option will trigger a cleanup routine on the bot
def mock_kill():
pass
with patch('testrunner.testproc.sequence.deque', side_effect=mock_deque), \
patch('testrunner.testproc.util.kill_processes_linux', side_effect=mock_kill):
result = self.run_tests(
'--variants=default', '--swarming', 'fat', baseroot="testroot6")
result.has_returncode(1)
self.assertEqual(1, appended)
self.assertEqual(1, popped)
def testRunnerFlags(self):
"""Test that runner-specific flags are passed to tests."""
result = self.run_tests(
'--progress=verbose',
'--variants=default',
'--random-seed=42',
'sweet/bananas',
'-v',
)
result.stdout_includes(
'--test bananas --random-seed=42 --nohard-abort --testing-d8-test-runner')
result.has_returncode(0)
class FakeTimeoutProc(base.TestProcObserver):
"""Fake of the total-timeout observer that just stops after counting
"count" number of test or result events.
"""
def __init__(self, count):
super(FakeTimeoutProc, self).__init__()
self._n = 0
self._count = count
def _on_next_test(self, test):
self.__on_event()
def _on_result_for(self, test, result):
self.__on_event()
def __on_event(self):
if self._n >= self._count:
self.stop()
self._n += 1
class NumFuzzerTest(TestRunnerTest):
def get_runner_class(self):
return num_fuzzer.NumFuzzer
def testNumFuzzer(self):
fuzz_flags = [
f'{flag}=1' for flag in self.get_runner_options()
if flag.startswith('--stress-')
]
self.assertEqual(len(fuzz_flags), len(fuzzer.FUZZERS))
for fuzz_flag in fuzz_flags:
# The fake timeout observer above will stop after proessing the 10th
# test. This still executes an 11th. Each test causes a test- and a
# result event internally. We test both paths here.
for event_count in (19, 20):
with self.subTest(f'fuzz_flag={fuzz_flag} event_count={event_count}'):
with patch(
'testrunner.testproc.timeout.TimeoutProc.create',
lambda x: FakeTimeoutProc(event_count)):
result = self.run_tests(
'--command-prefix', sys.executable,
'--outdir', 'out/build',
'--variants=default',
'--fuzzer-random-seed=12345',
'--total-timeout-sec=60',
fuzz_flag,
'--progress=verbose',
'sweet/bananas',
)
result.has_returncode(0)
result.stdout_includes('>>> Statusfile variables:')
result.stdout_includes('11 tests ran')
class OtherTest(TestRunnerTest):
def testStatusFilePresubmit(self):
"""Test that the fake status file is well-formed."""
with temp_base() as basedir:
from testrunner.local import statusfile
self.assertTrue(statusfile.PresubmitCheck(
basedir / 'test' / 'sweet' / 'sweet.status'))
if __name__ == '__main__':
unittest.main()

46
deps/v8/tools/testrunner/test_config.py vendored Normal file
View File

@ -0,0 +1,46 @@
# Copyright 2018 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.
from .utils import random_utils
class TestConfig(object):
def __init__(self,
command_prefix,
extra_flags,
extra_d8_flags,
framework_name,
isolates,
log_process_stats,
mode_flags,
no_harness,
noi18n,
random_seed,
run_skipped,
shard_count,
shard_id,
shell_dir,
target_os,
timeout,
verbose,
regenerate_expected_files=False):
self.command_prefix = command_prefix
self.extra_flags = extra_flags
self.extra_d8_flags = extra_d8_flags
self.framework_name = framework_name
self.isolates = isolates
self.log_process_stats = log_process_stats
self.mode_flags = mode_flags
self.no_harness = no_harness
self.noi18n = noi18n
# random_seed is always not None.
self.random_seed = random_seed or random_utils.random_seed()
self.run_skipped = run_skipped
self.shard_count = shard_count
self.shard_id = shard_id
self.shell_dir = shell_dir
self.target_os = target_os
self.timeout = timeout
self.verbose = verbose
self.regenerate_expected_files = regenerate_expected_files

View File

@ -0,0 +1,369 @@
{
"duration_mean": 1,
"max_rss_tests": [
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 3,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"max_vms_tests": [
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 3,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"results": [
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"crash_state": "",
"crash_type": "",
"duration": 1,
"error_details": "+Mock diff",
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"stderr": "",
"stdout": "--test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"crash_state": "",
"crash_type": "",
"duration": 1,
"error_details": "+Mock diff",
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"stderr": "",
"stdout": "--test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"crash_state": "",
"crash_type": "",
"duration": 1,
"error_details": "+Mock diff",
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 3,
"shard_count": 1,
"shard_id": 0,
"stderr": "",
"stdout": "--test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"slowest_tests": [
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py --test strawberries --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"--test",
"strawberries",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": true,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/strawberries",
"random_seed": 123,
"result": "FAIL",
"run": 3,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"test_total": 3
}

View File

@ -0,0 +1,242 @@
{
"duration_mean": 1,
"max_rss_tests": [
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 0,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": false,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": false,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"max_vms_tests": [
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 0,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": false,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": false,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"results": [
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"crash_state": "",
"crash_type": "",
"duration": 1,
"error_details": null,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"stderr": "",
"stdout": "bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"crash_state": "",
"crash_type": "",
"duration": 1,
"error_details": null,
"exit_code": 0,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "PASS",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"stderr": "",
"stdout": "bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner\n",
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"slowest_tests": [
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 0,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": false,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "",
"run": 2,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
},
{
"command": "/usr/bin/python out/build/d8_mocked.py bananaflakes --random-seed=123 --nohard-abort --testing-d8-test-runner",
"duration": 1,
"exit_code": 1,
"expected": [
"PASS"
],
"flags": [
"bananaflakes",
"--random-seed=123",
"--nohard-abort",
"--testing-d8-test-runner"
],
"framework_name": "standard_runner",
"marked_heavy": false,
"marked_slow": false,
"max_rss": 1,
"max_vms": 1,
"name": "sweet/bananaflakes",
"random_seed": 123,
"result": "FAIL",
"run": 1,
"shard_count": 1,
"shard_id": 0,
"target_name": "d8_mocked.py",
"variant": "default",
"variant_flags": []
}
],
"test_total": 2
}

View File

@ -0,0 +1,30 @@
# 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.
"""
Dummy d8 replacement. Just passes all test, except if 'berries' is in args.
"""
# for py2/py3 compatibility
from __future__ import print_function
import sys
args = ' '.join(sys.argv[1:])
print(args)
# Let all berries fail.
if 'berries' in args:
sys.exit(1)
# Dummy results if some analysis flags for numfuzz are present.
if '--print-deopt-stress' in args:
print('=== Stress deopt counter: 12345')
if '--fuzzer-gc-analysis' in args:
print('### Maximum marking limit reached = 3.70')
print('### Maximum new space size reached = 7.30')
print('### Allocations = 9107, hash = 0x0000001b')
sys.exit(0)

View File

@ -0,0 +1,36 @@
# 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.
[
[ALWAYS, {
'raspberries': FAIL,
'strawberries': [PASS, ['mode == release', SLOW], ['mode == debug', NO_VARIANTS]],
'mangoes': [PASS, SLOW],
# Both cherries and apples are to test how PASS an FAIL from different
# sections are merged.
'cherries': [PASS, SLOW],
'apples': [FAIL],
# Unused rule.
'carrots': [PASS, FAIL],
}],
['variant == nooptimization', {
'strawberries': [SKIP],
}],
['arch == x64', {
'cherries': [FAIL],
'apples': [PASS, SLOW],
# Unused rule.
'regress/*': [CRASH],
}],
['asan', {
'bananas': [PASS, NO_VARIANTS],
'raspberries': [FAIL, NO_VARIANTS],
}],
]

View File

@ -0,0 +1,51 @@
# 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.
"""
Dummy test suite extension with some fruity tests.
"""
from testrunner.local import testsuite
from testrunner.objects import testcase
from testrunner.outproc.base import OutProc
class TestLoader(testsuite.TestLoader):
def _list_test_filenames(self):
return [
'bananas', 'apples', 'cherries', 'mangoes', 'strawberries',
'blackberries', 'raspberries',
]
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase
class MockOutProc(OutProc):
def __init__(self, expected_outcomes):
OutProc.__init__(self, expected_outcomes)
def _get_error_details(self, output):
return "+Mock diff"
class TestCase(testcase.D8TestCase):
def get_shell(self):
return 'd8_mocked.py'
def _get_files_params(self):
return [self.name]
@property
def output_proc(self):
if self.name == 'strawberries':
return MockOutProc([])
else:
return super(TestCase, self).output_proc

View File

@ -0,0 +1,32 @@
# Copyright 2018 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.
"""
Dummy d8 replacement for flaky tests.
"""
# for py2/py3 compatibility
from __future__ import print_function
import os
import sys
PATH = os.path.dirname(os.path.abspath(__file__))
print(' '.join(sys.argv[1:]))
# Test files ending in 'flakes' should first fail then pass. We store state in
# a file side by side with the executable. No clean-up required as all tests
# run in a temp test root. Restriction: Only one variant is supported for now.
for arg in sys.argv[1:]:
if arg.endswith('flakes'):
flake_state = os.path.join(PATH, arg)
if os.path.exists(flake_state):
sys.exit(0)
else:
with open(flake_state, 'w') as f:
f.write('something')
sys.exit(1)
sys.exit(0)

View File

@ -0,0 +1,6 @@
# Copyright 2018 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.
[
]

View File

@ -0,0 +1,31 @@
# Copyright 2018 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.
"""
Dummy test suite extension with some flaky fruity tests.
"""
from testrunner.local import testsuite
from testrunner.objects import testcase
class TestLoader(testsuite.TestLoader):
def _list_test_filenames(self):
return ['bananaflakes']
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase
class TestCase(testcase.TestCase):
def get_shell(self):
return 'd8_mocked.py'
def _get_files_params(self):
return [self.name]

View File

@ -0,0 +1,16 @@
# Copyright 2021 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.
"""
Dummy d8 replacement. Just passes all test.
"""
# for py2/py3 compatibility
from __future__ import print_function
import sys
args = ' '.join(sys.argv[1:])
print(args)
sys.exit(0)

View File

@ -0,0 +1,15 @@
# Copyright 2021 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.
[
[ALWAYS, {
'raspberries': [PASS, HEAVY],
'strawberries': [PASS, HEAVY],
'blackberries': [PASS, HEAVY],
}],
['variant == nooptimization', {
'cherries': [PASS, HEAVY],
}],
]

View File

@ -0,0 +1,33 @@
# Copyright 2021 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.
"""
Dummy test suite extension with some fruity tests.
"""
from testrunner.local import testsuite
from testrunner.objects import testcase
class TestLoader(testsuite.TestLoader):
def _list_test_filenames(self):
return [
'bananas', 'apples', 'cherries', 'mangoes', 'strawberries',
'blackberries', 'raspberries',
]
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase
class TestCase(testcase.D8TestCase):
def get_shell(self):
return 'd8_mocked.py'
def _get_files_params(self):
return [self.name]

View File

@ -0,0 +1,2 @@
{
malformed json

View File

@ -0,0 +1,36 @@
# Copyright 2022 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.
[
[ALWAYS, {
'raspberries': FAIL,
'strawberries': [PASS, ['mode == release', SLOW], ['mode == debug', NO_VARIANTS]],
'mangoes': [PASS, SLOW],
# Both cherries and apples are to test how PASS an FAIL from different
# sections are merged.
'cherries': [PASS, SLOW],
'apples': [FAIL],
# Unused rule.
'carrots': [PASS, FAIL],
}],
['variant == nooptimization', {
'strawberries': [SKIP],
}],
['arch == x64', {
'cherries': [FAIL],
'apples': [PASS, SLOW],
# Unused rule.
'regress/*': [CRASH],
}],
['asan', {
'bananas': [PASS, NO_VARIANTS],
'raspberries': [FAIL, NO_VARIANTS],
}],
]

View File

@ -0,0 +1,33 @@
# Copyright 2022 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.
"""
Dummy test suite extension with some fruity tests.
"""
from testrunner.local import testsuite
from testrunner.objects import testcase
class TestLoader(testsuite.TestLoader):
def _list_test_filenames(self):
return [
'bananas', 'apples', 'cherries', 'mangoes', 'strawberries',
'blackberries', 'raspberries',
]
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase
class TestCase(testcase.D8TestCase):
def get_shell(self):
return 'd8_mocked.py'
def _get_files_params(self):
return [self.name]

View File

@ -0,0 +1,18 @@
# 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.
"""
Dummy d8 replacement. Just passes all test, except if 'berries' is in args.
"""
# for py2/py3 compatibility
from __future__ import print_function
import sys
args = ' '.join(sys.argv[1:])
print(args)
# Let all berries fail.
if 'berries' in args:
sys.exit(1)
sys.exit(0)

View File

@ -0,0 +1,19 @@
# 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.
[
[ALWAYS, {
'raspberries': [FAIL, HEAVY],
'strawberries': [PASS, ['mode == release', SLOW], ['mode == debug', NO_VARIANTS]],
'mangoes': [PASS, SLOW, HEAVY],
# Both cherries and apples are to test how PASS an FAIL from different
# sections are merged.
'cherries': [PASS, SLOW],
'apples': [FAIL],
# Unused rule.
'carrots': [PASS, FAIL],
}],
]

View File

@ -0,0 +1,41 @@
# 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.
"""
Dummy test suite extension with some fruity tests.
"""
from testrunner.local import testsuite
from testrunner.objects import testcase
class TestLoader(testsuite.TestLoader):
def _list_test_filenames(self):
return [
'bananas',
'apples',
'cherries',
'mangoes',
'strawberries',
'blackberries',
'raspberries',
]
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase
class TestCase(testcase.D8TestCase):
def get_shell(self):
return 'd8_mocked.py'
def _get_files_params(self):
return [self.name]

View File

@ -0,0 +1,22 @@
# Copyright 2024 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.
"""
Dummy d8 replacement.
"""
import sys
args = ' '.join(sys.argv[1:])
print(args)
# Test numfuzz behavior: the bananas test requires --fuzzing.
if 'bananas' in args and '--fuzzing' not in args:
sys.exit(1)
# Test numfuzz behavior: the apples test fails with --fuzzing.
if 'apples' in args and '--fuzzing' in args:
sys.exit(1)
sys.exit(0)

View File

@ -0,0 +1,6 @@
# Copyright 2024 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.
[
]

View File

@ -0,0 +1,32 @@
# Copyright 2024 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.
"""
Dummy test suite extension with some fruity tests expecting numfuzz behavior.
"""
from testrunner.local import testsuite
from testrunner.objects import testcase
class TestLoader(testsuite.TestLoader):
def _list_test_filenames(self):
return ['bananas', 'apples']
class TestSuite(testsuite.TestSuite):
def _test_loader_class(self):
return TestLoader
def _test_class(self):
return TestCase
class TestCase(testcase.D8TestCase):
def get_shell(self):
return 'd8_mocked.py'
def _get_files_params(self):
assert self.suite.framework_name == 'num_fuzzer'
return [self.name]

View File

@ -0,0 +1,19 @@
{
"arch": "x64",
"asan": false,
"cfi": false,
"component_build": false,
"dcheck_always_on": false,
"debugging_features": false,
"full_debug": false,
"i18n": true,
"is_android": false,
"is_ios": false,
"lite_mode": false,
"msan": false,
"simulator_run": false,
"tsan": false,
"ubsan": false,
"use_sanitizer": false,
"verify_predictable": false
}

View File

@ -0,0 +1,3 @@
# Copyright 2018 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.

View File

@ -0,0 +1,183 @@
# Copyright 2018 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.
"""
Pipeline
Test processors are chained together and communicate with each other by
calling previous/next processor in the chain.
----next_test()----> ----next_test()---->
Proc1 Proc2 Proc3
<---result_for()---- <---result_for()----
For every next_test there is exactly one result_for call.
If processor ignores the test it has to return SkippedResult.
If it created multiple subtests for one test and wants to pass all of them to
the previous processor it can enclose them in GroupedResult.
Subtests
When test processor needs to modify the test or create some variants of the
test it creates subtests and sends them to the next processor.
Each subtest has:
- procid - globally unique id that should contain id of the parent test and
some suffix given by test processor, e.g. its name + subtest type.
- processor - which created it
- origin - pointer to the parent (sub)test
"""
DROP_RESULT = 0
DROP_OUTPUT = 1
DROP_PASS_OUTPUT = 2
DROP_PASS_STDOUT = 3
class TestProc(object):
def __init__(self):
self._prev_proc = None
self._next_proc = None
self._stopped = False
self._requirement = DROP_RESULT
def connect_to(self, next_proc):
"""Puts `next_proc` after itself in the chain."""
next_proc._prev_proc = self
self._next_proc = next_proc
def next_test(self, test):
"""
Method called by previous processor whenever it produces new test.
This method shouldn't be called by anyone except previous processor.
Returns a boolean value to signal whether the test was loaded into the
execution queue successfully or not.
"""
raise NotImplementedError()
def result_for(self, test, result):
"""
Method called by next processor whenever it has result for some test.
This method shouldn't be called by anyone except next processor.
"""
raise NotImplementedError()
def heartbeat(self):
if self._prev_proc:
self._prev_proc.heartbeat()
def stop(self):
if not self._stopped:
self._stopped = True
if self._prev_proc:
self._prev_proc.stop()
if self._next_proc:
self._next_proc.stop()
@property
def is_stopped(self):
return self._stopped
### Communication
def notify_previous(self, event):
self._on_event(event)
if self._prev_proc:
self._prev_proc.notify_previous(event)
def _on_event(self, event):
"""Called when processors to the right signal events, e.g. termination.
Args:
event: A text describing the signalled event.
"""
pass
def _send_test(self, test):
"""Helper method for sending test to the next processor."""
return self._next_proc.next_test(test)
def _send_result(self, test, result):
"""Helper method for sending result to the previous processor."""
self._prev_proc.result_for(test, result)
class TestProcObserver(TestProc):
"""Processor used for observing the data."""
def __init__(self):
super(TestProcObserver, self).__init__()
def next_test(self, test):
self._on_next_test(test)
return self._send_test(test)
def result_for(self, test, result):
self._on_result_for(test, result)
self._send_result(test, result)
def heartbeat(self):
self._on_heartbeat()
super(TestProcObserver, self).heartbeat()
def _on_next_test(self, test):
"""Method called after receiving test from previous processor but before
sending it to the next one."""
pass
def _on_result_for(self, test, result):
"""Method called after receiving result from next processor but before
sending it to the previous one."""
pass
def _on_heartbeat(self):
pass
class TestProcProducer(TestProc):
"""Processor for creating subtests."""
def __init__(self, name):
super(TestProcProducer, self).__init__()
self.name = name
def next_test(self, test):
return self._next_test(test)
def result_for(self, subtest, result):
self._result_for(subtest.origin, subtest, result)
### Implementation
def _next_test(self, test):
raise NotImplementedError()
def _result_for(self, test, subtest, result):
"""
result_for method extended with `subtest` parameter.
Args
test: test used by current processor to create the subtest.
subtest: test for which the `result` is.
result: subtest execution result created by the output processor.
"""
raise NotImplementedError()
def test_suffix(self, test):
"""Default implementation of rdb test id suffix generated by a producer"""
return None
class TestProcFilter(TestProc):
"""Processor for filtering tests."""
def next_test(self, test):
if self._filter(test):
return False
return self._send_test(test)
def result_for(self, test, result):
self._send_result(test, result)
def _filter(self, test):
"""Returns whether test should be filtered out."""
raise NotImplementedError()

View File

@ -0,0 +1,122 @@
# Copyright 2018 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.
from collections import defaultdict
from . import base
class CombinerProc(base.TestProc):
@staticmethod
def create(options):
if not options.combine_tests:
return None
return CombinerProc(options.fuzzer_rng(), options.combine_min, options.combine_max,
options.tests_count)
def __init__(self, rng, min_group_size, max_group_size, count):
"""
Args:
rng: random number generator
min_group_size: minimum number of tests to combine
max_group_size: maximum number of tests to combine
count: how many tests to generate. 0 means infinite running
"""
super(CombinerProc, self).__init__()
self._requirement = base.DROP_RESULT
self._rng = rng
self._min_size = min_group_size
self._max_size = max_group_size
self._count = count
# Index of the last generated test
self._current_num = 0
# {suite name: instance of TestGroups}
self._groups = defaultdict(TestGroups)
# {suite name: instance of TestCombiner}
self._combiners = {}
def next_test(self, test):
group_key = self._get_group_key(test)
if not group_key:
# Test not suitable for combining
return False
self._groups[test.suite.name].add_test(group_key, test)
return True
def _get_group_key(self, test):
combiner = self._get_combiner(test.suite)
if not combiner:
print ('>>> Warning: There is no combiner for %s testsuite' %
test.suite.name)
return None
return combiner.get_group_key(test)
def result_for(self, test, result):
self._send_next_test()
def generate_initial_tests(self, num=1):
for _ in range(0, num):
self._send_next_test()
def _send_next_test(self):
if self.is_stopped:
return False
if self._count and self._current_num >= self._count:
return False
combined_test = self._create_new_test()
if not combined_test:
# Not enough tests
return False
return self._send_test(combined_test)
def _create_new_test(self):
suite, combiner = self._select_suite()
groups = self._groups[suite]
max_size = self._rng.randint(self._min_size, self._max_size)
sample = groups.sample(self._rng, max_size)
if not sample:
return None
self._current_num += 1
return combiner.combine('%s-%d' % (suite, self._current_num), sample)
def _select_suite(self):
"""Returns pair (suite name, combiner)."""
selected = self._rng.randint(0, len(self._groups) - 1)
for n, suite in enumerate(self._groups):
if n == selected:
return suite, self._combiners[suite]
def _get_combiner(self, suite):
combiner = self._combiners.get(suite.name)
if not combiner:
combiner = suite.get_test_combiner()
self._combiners[suite.name] = combiner
return combiner
class TestGroups(object):
def __init__(self):
self._groups = defaultdict(list)
self._keys = []
def add_test(self, key, test):
self._groups[key].append(test)
self._keys.append(key)
def sample(self, rng, max_size):
# Not enough tests
if not self._groups:
return None
group_key = rng.choice(self._keys)
tests = self._groups[group_key]
return [rng.choice(tests) for _ in range(0, max_size)]

View File

@ -0,0 +1,77 @@
# Copyright 2018 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 collections
from . import base
JobResult = collections.namedtuple('JobResult', ['id', 'result'])
class Job(object):
def __init__(self, test_id, cmd, outproc, keep_output):
self.test_id = test_id
self.cmd = cmd
self.outproc = outproc
self.keep_output = keep_output
def run(self, process_ctx):
output = self.cmd.execute()
reduction = process_ctx.result_reduction if not self.keep_output else None
result = self.outproc.process(output, reduction)
return JobResult(self.test_id, result)
class ExecutionProc(base.TestProc):
"""Last processor in the chain. Instead of passing tests further it creates
commands and output processors, executes them in multiple worker processes and
sends results to the previous processor.
"""
def __init__(self, ctx, jobs, outproc_factory=None):
super(ExecutionProc, self).__init__()
self.ctx = ctx
self.ctx.pool.init(jobs, notify_function=self.notify_previous)
self._outproc_factory = outproc_factory or (lambda t: t.output_proc)
self._tests = {}
def connect_to(self, next_proc):
assert False, \
'ExecutionProc cannot be connected to anything' # pragma: no cover
def run(self, requirement=None):
for pool_result in self.ctx.pool.results(requirement):
self._unpack_result(pool_result)
def next_test(self, test):
if self.is_stopped:
return False
test_id = test.procid
cmd = test.get_command(self.ctx)
self._tests[test_id] = test, cmd
outproc = self._outproc_factory(test)
self.ctx.pool.add_jobs([Job(test_id, cmd, outproc, test.keep_output)])
return True
def result_for(self, test, result):
assert False, \
'ExecutionProc cannot receive results' # pragma: no cover
def stop(self):
super(ExecutionProc, self).stop()
self.ctx.pool.abort()
def _unpack_result(self, pool_result):
if pool_result.heartbeat:
self.heartbeat()
return
job_result = pool_result.value
test_id, result = job_result
test, result.cmd = self._tests[test_id]
del self._tests[test_id]
self._send_result(test, result)

View File

@ -0,0 +1,19 @@
# Copyright 2018 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.
from . import base
class ExpectationProc(base.TestProcProducer):
"""Test processor passing tests and results through and forgiving timeouts."""
def __init__(self):
super(ExpectationProc, self).__init__('no-timeout')
def _next_test(self, test):
subtest = test.create_subtest(self, 'no_timeout')
subtest.allow_timeouts()
subtest.allow_pass()
return self._send_test(subtest)
def _result_for(self, test, subtest, result):
self._send_result(test, result)

View File

@ -0,0 +1,85 @@
# Copyright 2018 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.
from collections import defaultdict
import fnmatch
import os
from . import base
class StatusFileFilterProc(base.TestProcFilter):
"""Filters tests by outcomes from status file.
Status file has to be loaded before using this function.
Args:
slow_tests_mode: What to do with slow tests.
pass_fail_tests_mode: What to do with pass or fail tests.
Mode options:
None (default): don't skip
"skip": skip if slow/pass_fail
"run": skip if not slow/pass_fail
"""
def __init__(self, slow_tests_mode, pass_fail_tests_mode):
super(StatusFileFilterProc, self).__init__()
self._slow_tests_mode = slow_tests_mode
self._pass_fail_tests_mode = pass_fail_tests_mode
def _filter(self, test):
return (
test.do_skip or
self._skip_slow(test.is_slow) or
self._skip_pass_fail(test.is_pass_or_fail)
)
def _skip_slow(self, is_slow):
return (
(self._slow_tests_mode == 'run' and not is_slow) or
(self._slow_tests_mode == 'skip' and is_slow)
)
def _skip_pass_fail(self, is_pass_fail):
return (
(self._pass_fail_tests_mode == 'run' and not is_pass_fail) or
(self._pass_fail_tests_mode == 'skip' and is_pass_fail)
)
class NameFilterProc(base.TestProcFilter):
"""Filters tests based on command-line arguments.
args can be a glob: asterisks in any position of the name
represent zero or more characters. Without asterisks, only exact matches
will be used with the exeption of the test-suite name as argument.
"""
def __init__(self, args):
super(NameFilterProc, self).__init__()
self._globs = defaultdict(list)
self._exact_matches = defaultdict(dict)
for a in args:
argpath = a.split('/')
suitename = argpath[0]
path = '/'.join(argpath[1:]) or '*'
if '*' in path:
self._globs[suitename].append(path)
else:
self._exact_matches[suitename][path] = True
for s, globs in list(self._globs.items()):
if not globs or '*' in globs:
self._globs[s] = ['*']
def _filter(self, test):
globs = self._globs.get(test.suite.name, [])
for g in globs:
if g == '*': return False
if fnmatch.fnmatch(test.name, g):
return False
exact_matches = self._exact_matches.get(test.suite.name, {})
return test.name not in exact_matches

View File

@ -0,0 +1,421 @@
# Copyright 2018 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.
from . import base
# Extra flags randomly added to all fuzz tests with numfuzz. List of tuples
# (probability, flag).
EXTRA_FLAGS = [
(0.1, '--always-turbofan'),
(0.1, '--assert-types'),
(0.1, '--cache=code'),
(0.1, '--force-slow-path'),
(0.2, '--future'),
(0.5, '--harmony'),
# TODO(v8:13524): Enable when issue is fixed
# TODO(v8:13528): Enable when issue is fixed
# (0.1, '--harmony-struct'),
(0.1, '--jit-fuzzing'),
(0.5, '--js-staging'),
(0.1, '--liftoff'),
(0.1, '--maglev'),
(0.1, '--maglev-future'),
(0.1, '--minor-ms'),
(0.2, '--no-analyze-environment-liveness'),
# TODO(machenbach): Enable when it doesn't collide with crashing on missing
# simd features.
#(0.1, '--no-enable-sse3'),
#(0.1, '--no-enable-ssse3'),
#(0.1, '--no-enable-sse4_1'),
(0.1, '--no-enable-sse4_2'),
(0.1, '--no-enable-sahf'),
(0.1, '--no-enable-avx'),
(0.1, '--no-enable-fma3'),
(0.1, '--no-enable-bmi1'),
(0.1, '--no-enable-bmi2'),
(0.1, '--no-enable-lzcnt'),
(0.1, '--no-enable-popcnt'),
(0.3, '--no-lazy-feedback-allocation'),
(0.1, '--no-liftoff'),
(0.1, '--no-turbofan'),
(0.1, '--no-wasm-tier-up'),
(0.5, '--optimize-on-next-call-optimizes-to-maglev'),
(0.1, '--regexp-interpret-all'),
(0.1, '--regexp-tier-up-ticks=0'),
(0.1, '--regexp-tier-up-ticks=10'),
(0.1, '--regexp-tier-up-ticks=100'),
(0.1, '--shared-string-table'),
(0.1, '--shared-heap'),
(0.1, '--stress-background-compile'),
(0.1, '--stress-flush-code'),
(0.1, '--stress-lazy-source-positions'),
(0.1, '--stress-maglev'),
(0.1, '--stress-wasm-code-gc'),
(0.2, '--turboshaft'),
(0.1, '--turbolev'),
(0.1, '--turbo-instruction-scheduling'),
(0.1, '--turbo-stress-instruction-scheduling'),
(0.1, '--stress-wasm-memory-moving'),
(0.1, '--stress-scavenger-conservative-object-pinning-random'),
(0.1, '--precise-object-pinning'),
(0.25, '--wasm-staging'),
(0.1, '--ephemeron-fixpoint-iterations=0'),
]
MIN_DEOPT = 1
MAX_DEOPT = 10**9
ANALYSIS_SUFFIX = 'analysis'
def random_extra_flags(rng):
"""Returns a random list of flags chosen from the configurations in
EXTRA_FLAGS.
"""
return [flag for prob, flag in EXTRA_FLAGS if rng.random() < prob]
def _flag_prefix(flag):
"""Returns the flag part before an equal sign."""
if '=' not in flag:
return flag
else:
return flag[0:flag.index('=')]
def _invert_flag(flag):
"""Flips a --flag and its --no-flag counterpart."""
assert flag.startswith('--')
if flag.startswith('--no-'):
return '--' + flag[len('--no-'):]
else:
return '--no-' + flag[2:]
def _drop_contradictory_flags(new_flags, existing_flags):
"""Drops flags that have a simple contradiction with an existing flag.
Contradictions checked for:
- Repetition: --flag --flag
- Repetition with param: --flag=foo --flag=bar
- Negation: --flag --no-flag
- Inverse negation: --no-flag --flag
- For simplicity also drops combinations of negation and param, which don't
occur in practice.
Args:
new_flags: new flags to filter from
existing_flags: existing flags checked against
Returns: A list of flags without contradictions.
"""
existing_flag_prefixes = set(_flag_prefix(flag) for flag in existing_flags)
def contradictory_flag(flag):
flag_prefix = _flag_prefix(flag)
return (flag_prefix in existing_flag_prefixes or
_invert_flag(flag_prefix) in existing_flag_prefixes)
return [flag for flag in new_flags if not contradictory_flag(flag)]
class FuzzerConfig(object):
def __init__(self, probability, analyzer, fuzzer):
"""
Args:
probability: of choosing this fuzzer (0; 10]
analyzer: instance of Analyzer class, can be None if no analysis is needed
fuzzer: instance of Fuzzer class
"""
assert probability > 0 and probability <= 10
self.probability = probability
self.analyzer = analyzer
self.fuzzer = fuzzer
class Analyzer(object):
def get_analysis_flags(self):
raise NotImplementedError()
def do_analysis(self, result):
raise NotImplementedError()
class Fuzzer(object):
def create_flags_generator(self, rng, test, analysis_value):
"""
Args:
rng: random number generator
test: test for which to create flags
analysis_value: value returned by the analyzer. None if there is no
corresponding analyzer to this fuzzer or the analysis phase is disabled
"""
raise NotImplementedError()
# TODO(majeski): Allow multiple subtests to run at once.
class FuzzerProc(base.TestProcProducer):
@staticmethod
def create(options):
return FuzzerProc(
options.fuzzer_rng(),
options.fuzzer_tests_count(),
options.fuzzer_configs(),
options.combine_tests,
)
def __init__(self, rng, count, fuzzers, disable_analysis=False):
"""
Args:
rng: random number generator used to select flags and values for them
count: number of tests to generate based on each base test
fuzzers: list of FuzzerConfig instances
disable_analysis: disable analysis phase and filtering base on it. When
set, processor passes None as analysis result to fuzzers
"""
super(FuzzerProc, self).__init__('Fuzzer')
self._rng = rng
self._count = count
self._fuzzer_configs = fuzzers
self._disable_analysis = disable_analysis
self._gens = {}
def test_suffix(self, test):
return test.subtest_id
def _next_test(self, test):
if self.is_stopped:
return False
analysis_subtest = self._create_analysis_subtest(test)
if analysis_subtest:
return self._send_test(analysis_subtest)
self._gens[test.procid] = self._create_gen(test)
return self._try_send_next_test(test)
def _create_analysis_subtest(self, test):
if self._disable_analysis:
return None
analysis_flags = []
for fuzzer_config in self._fuzzer_configs:
if fuzzer_config.analyzer:
analysis_flags += fuzzer_config.analyzer.get_analysis_flags()
if analysis_flags:
analysis_flags = list(set(analysis_flags))
return test.create_subtest(
self, ANALYSIS_SUFFIX, flags=analysis_flags, keep_output=True)
def _result_for(self, test, subtest, result):
if not self._disable_analysis:
if result is not None and subtest.procid.endswith(
f'{self.name}-{ANALYSIS_SUFFIX}'):
# Analysis phase, for fuzzing we drop the result.
if result.has_unexpected_output:
self._send_result(test, None)
return
self._gens[test.procid] = self._create_gen(test, result)
self._try_send_next_test(test)
def _create_gen(self, test, analysis_result=None):
# It will be called with analysis_result==None only when there is no
# analysis phase at all, so no fuzzer has it's own analyzer.
gens = []
indexes = []
for fuzzer_config in self._fuzzer_configs:
analysis_value = None
if analysis_result and fuzzer_config.analyzer:
analysis_value = fuzzer_config.analyzer.do_analysis(analysis_result)
if not analysis_value:
# Skip fuzzer for this test since it doesn't have analysis data
continue
p = fuzzer_config.probability
flag_gen = fuzzer_config.fuzzer.create_flags_generator(self._rng, test,
analysis_value)
indexes += [len(gens)] * p
gens.append((p, flag_gen))
if not gens:
# No fuzzers for this test, skip it
return
i = 0
while not self._count or i < self._count:
main_index = self._rng.choice(indexes)
_, main_gen = gens[main_index]
flags = random_extra_flags(self._rng) + next(main_gen)
for index, (p, gen) in enumerate(gens):
if index == main_index:
continue
if self._rng.randint(1, 10) <= p:
flags += next(gen)
flags.append('--fuzzer-random-seed=%s' % self._next_seed())
flags = _drop_contradictory_flags(flags, test.get_flags())
yield test.create_subtest(self, str(i), flags=flags)
i += 1
def _try_send_next_test(self, test):
for subtest in self._gens[test.procid]:
if self._send_test(subtest):
return True
elif self.is_stopped:
return False
del self._gens[test.procid]
return False
def _next_seed(self):
seed = None
while not seed:
seed = self._rng.randint(-2147483648, 2147483647)
return seed
class ScavengeAnalyzer(Analyzer):
def get_analysis_flags(self):
return ['--fuzzer-gc-analysis']
def do_analysis(self, result):
for line in reversed(result.output.stdout.splitlines()):
if line.startswith('### Maximum new space size reached = '):
return int(float(line.split()[7]))
class ScavengeFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
yield ['--stress-scavenge=%d' % (analysis_value or 100)]
class MarkingAnalyzer(Analyzer):
def get_analysis_flags(self):
return ['--fuzzer-gc-analysis']
def do_analysis(self, result):
for line in reversed(result.output.stdout.splitlines()):
if line.startswith('### Maximum marking limit reached = '):
return int(float(line.split()[6]))
class MarkingFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
yield ['--stress-marking=%d' % (analysis_value or 100)]
class GcIntervalAnalyzer(Analyzer):
def get_analysis_flags(self):
return ['--fuzzer-gc-analysis']
def do_analysis(self, result):
for line in reversed(result.output.stdout.splitlines()):
if line.startswith('### Allocations = '):
return int(float(line.split()[3][:-1]))
class GcIntervalFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
if analysis_value:
value = analysis_value // 10
else:
value = 10000
while True:
yield ['--random-gc-interval=%d' % value]
class CompactionFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
yield ['--stress-compaction-random']
class InterruptBudgetFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
# Half with, half without lazy feedback allocation. The first flag
# overwrites potential flag negations from the extra flags list.
flag1 = rng.choice(
['--lazy-feedback-allocation', '--no-lazy-feedback-allocation'])
flag2 = '--invocation-count-for-turbofan=%d' % rng.randint(0, 240)
flag3 = '--invocation-count-for-maglev=%d' % rng.randint(0, 120)
flag4 = '--invocation-count-for-feedback-allocation=%d' % rng.randint(
0, 8)
yield [flag1, flag2, flag3, flag4]
class StackSizeFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
yield ['--stack-size=%d' % rng.randint(54, 983)]
class TaskDelayFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
yield ['--stress-delay-tasks']
class ThreadPoolSizeFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
yield ['--thread-pool-size=%d' % rng.randint(1, 8)]
class DeoptAnalyzer(Analyzer):
def get_analysis_flags(self):
return ['--deopt-every-n-times=%d' % MAX_DEOPT,
'--print-deopt-stress']
def do_analysis(self, result):
for line in reversed(result.output.stdout.splitlines()):
if line.startswith('=== Stress deopt counter: '):
counter = MAX_DEOPT - int(line.split(' ')[-1])
if counter < MIN_DEOPT:
# Skip this test since we won't generate any meaningful interval with
# given minimum.
return None
return counter
class DeoptFuzzer(Fuzzer):
def create_flags_generator(self, rng, test, analysis_value):
while True:
if analysis_value:
value = analysis_value // 2
else:
value = 10000
interval = rng.randint(MIN_DEOPT, max(value, MIN_DEOPT))
yield ['--deopt-every-n-times=%d' % interval]
FUZZERS = {
'compaction': (None, CompactionFuzzer),
'delay': (None, TaskDelayFuzzer),
'deopt': (DeoptAnalyzer, DeoptFuzzer),
'gc_interval': (GcIntervalAnalyzer, GcIntervalFuzzer),
'interrupt': (None, InterruptBudgetFuzzer),
'marking': (MarkingAnalyzer, MarkingFuzzer),
'scavenge': (ScavengeAnalyzer, ScavengeFuzzer),
'stack': (None, StackSizeFuzzer),
'threads': (None, ThreadPoolSizeFuzzer),
}
def create_fuzzer_config(name, probability, *args, **kwargs):
analyzer_class, fuzzer_class = FUZZERS[name]
return FuzzerConfig(
probability,
analyzer_class(*args, **kwargs) if analyzer_class else None,
fuzzer_class(*args, **kwargs),
)

View File

@ -0,0 +1,51 @@
#!/usr/bin/env python3
# Copyright 2022 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.
"""
Test functionality used by the fuzzer test processor.
"""
import os
import sys
import unittest
# Needed because the test runner contains relative imports.
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
from testrunner.testproc import fuzzer
class TestFuzzerProcHelpers(unittest.TestCase):
def test_invert_flag(self):
self.assertEqual(fuzzer._invert_flag('--no-flag'), '--flag')
self.assertEqual(fuzzer._invert_flag('--flag'), '--no-flag')
def test_flag_prefix(self):
self.assertEqual(fuzzer._flag_prefix('--flag'), '--flag')
self.assertEqual(fuzzer._flag_prefix('--flag=42'), '--flag')
def test_contradictory_flags(self):
def check(new_flags, old_flags, expected_flags):
actual = fuzzer._drop_contradictory_flags(new_flags, old_flags)
self.assertEqual(actual, expected_flags)
old_flags = ['--foo-bar', '--no-bar', '--baz', '--doom-melon=42', '--flag']
check([], old_flags, [])
check(['--flag', '--no-baz', '--bar'], old_flags, [])
check(['--foo-bar=42', '--doom-melon=0'], old_flags, [])
check(['--flag', '--something', '--bar'], old_flags, ['--something'])
check(['--something', '--doom-melon=0'], old_flags, ['--something'])
check(old_flags, old_flags, [])
check(['--a', '--b'], old_flags, ['--a', '--b'])
old_flags = []
check(['--flag', '--no-baz'], old_flags, ['--flag', '--no-baz'])
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,479 @@
# Copyright 2022 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 datetime
import json
import logging
import platform
import sys
import time
from . import base
from . import util
from .stack_utils import stack_analyzer_util
TRACK_N_HEAVY_TESTS = 20
def print_failure_header(test, is_flaky=False):
text = [test.full_name]
if test.output_proc.negative:
text.append('[negative]')
if is_flaky:
text.append('(flaky)')
output = '=== %s ===' % ' '.join(text)
encoding = sys.stdout.encoding or 'utf-8'
print(output.encode(encoding, errors='replace').decode(encoding))
def formatted_result_output(result, relative=False):
lines = []
if result.output.stderr:
lines.append("--- stderr ---")
lines.append(result.output.stderr.strip())
if result.output.stdout:
lines.append("--- stdout ---")
lines.append(result.output.stdout.strip())
lines.append("Command: %s" % result.cmd.to_string(relative))
if result.output.HasCrashed():
lines.append("exit code: %s" % result.output.exit_code_string)
lines.append("--- CRASHED ---")
if result.output.HasTimedOut():
lines.append("--- TIMEOUT ---")
return '\n'.join(lines)
class ProgressIndicator():
def __init__(self, context, options, test_count):
self.options = None
self.options = options
self._total = test_count
self.context = context
def on_test_result(self, test, result):
pass
def finished(self):
pass
def on_heartbeat(self):
pass
def on_event(self, event):
pass
class SimpleProgressIndicator(ProgressIndicator):
def __init__(self, context, options, test_count):
super(SimpleProgressIndicator, self).__init__(context, options, test_count)
self._requirement = base.DROP_PASS_OUTPUT
self._failed = []
def on_test_result(self, test, result):
# TODO(majeski): Support for dummy/grouped results
if result.has_unexpected_output:
self._failed.append((test, result, False))
elif result.is_rerun:
# Print only the first result of a flaky failure that was rerun.
self._failed.append((test, result.results[0], True))
def finished(self):
crashed = 0
flaky = 0
print()
for test, result, is_flaky in self._failed:
flaky += int(is_flaky)
print_failure_header(test, is_flaky=is_flaky)
print(formatted_result_output(result))
if len(self._failed) == 0:
print("===")
print("=== All tests succeeded")
print("===")
else:
print()
print("===")
print("=== %d tests failed" % len(self._failed))
if flaky > 0:
print("=== %d tests were flaky" % flaky)
if crashed > 0:
print("=== %d tests CRASHED" % crashed)
print("===")
class StreamProgressIndicator(ProgressIndicator):
def __init__(self, context, options, test_count):
super(StreamProgressIndicator, self).__init__(context, options, test_count)
self._requirement = base.DROP_PASS_OUTPUT
def on_test_result(self, test, result):
if not result.has_unexpected_output:
self.print('PASS', test)
elif result.output.HasCrashed():
self.print("CRASH", test)
elif result.output.HasTimedOut():
self.print("TIMEOUT", test)
else:
if test.is_fail:
self.print("UNEXPECTED PASS", test)
else:
self.print("FAIL", test)
def print(self, prefix, test):
print('%s: %ss' % (prefix, test))
sys.stdout.flush()
class VerboseProgressIndicator(SimpleProgressIndicator):
def __init__(self, context, options, test_count):
super(VerboseProgressIndicator, self).__init__(context, options, test_count)
self._last_printed_time = time.time()
def _print(self, text):
encoding = sys.stdout.encoding or 'utf-8'
print(text.encode(encoding, errors='replace').decode(encoding))
sys.stdout.flush()
self._last_printed_time = time.time()
def _message(self, test, result):
return '%s %s: %s' % (test, test.variant or 'default', result.status())
def on_test_result(self, test, result):
super(VerboseProgressIndicator, self).on_test_result(test, result)
self._print(self._message(test, result))
# TODO(machenbach): Remove this platform specific hack and implement a proper
# feedback channel from the workers, providing which tests are currently run.
def _log_processes(self):
procs = self.context.list_processes()
if procs:
logging.info('List of processes:')
for pid, cmd in self.context.list_processes():
# Show command with pid, but other process info cut off.
logging.info('pid: %d cmd: %s', pid, cmd)
def _ensure_delay(self, delay):
return time.time() - self._last_printed_time > delay
def on_heartbeat(self):
if self._ensure_delay(30):
# Print something every 30 seconds to not get killed by an output
# timeout.
self._print('Still working...')
self._log_processes()
def on_event(self, event):
logging.info(event)
self._log_processes()
class CIProgressIndicator(VerboseProgressIndicator):
def on_test_result(self, context, test, result):
super(VerboseProgressIndicator, self).on_test_result(context, test, result)
if self.options.ci_test_completion:
with open(self.options.ci_test_completion, "a") as f:
f.write(self._message(test, result) + "\n")
self._output_feedback()
def _output_feedback(self):
"""Reduced the verbosity leads to getting killed by an ouput timeout.
We ensure output every minute.
"""
if self._ensure_delay(60):
dt = time.time()
st = datetime.datetime.fromtimestamp(dt).strftime('%Y-%m-%d %H:%M:%S')
self._print(st)
class DotsProgressIndicator(SimpleProgressIndicator):
def __init__(self, context, options, test_count):
super(DotsProgressIndicator, self).__init__(context, options, test_count)
self._count = 0
def on_test_result(self, test, result):
super(DotsProgressIndicator, self).on_test_result(test, result)
# TODO(majeski): Support for dummy/grouped results
self._count += 1
if self._count > 1 and self._count % 50 == 1:
sys.stdout.write('\n')
if result.has_unexpected_output:
if result.output.HasCrashed():
sys.stdout.write('C')
sys.stdout.flush()
elif result.output.HasTimedOut():
sys.stdout.write('T')
sys.stdout.flush()
else:
sys.stdout.write('F')
sys.stdout.flush()
else:
sys.stdout.write('.')
sys.stdout.flush()
class CompactProgressIndicator(ProgressIndicator):
def __init__(self, context, options, test_count, templates):
super(CompactProgressIndicator, self).__init__(context, options, test_count)
self._requirement = base.DROP_PASS_OUTPUT
self._templates = templates
self._last_status_length = 0
self._start_time = time.time()
self._passed = 0
self._failed = 0
def on_test_result(self, test, result):
# TODO(majeski): Support for dummy/grouped results
if result.has_unexpected_output:
self._failed += 1
else:
self._passed += 1
self._print_progress(test.full_name)
if result.has_unexpected_output:
output = result.output
stdout = output.stdout.strip()
stderr = output.stderr.strip()
self._clear_line(self._last_status_length)
print_failure_header(test)
if len(stdout):
self.printFormatted('stdout', stdout)
if len(stderr):
self.printFormatted('stderr', stderr)
if result.error_details:
self.printFormatted('failure', result.error_details)
self.printFormatted('command',
"Command: %s" % result.cmd.to_string(relative=True))
if output.HasCrashed():
self.printFormatted('failure',
"exit code: %s" % output.exit_code_string)
self.printFormatted('failure', "--- CRASHED ---")
elif output.HasTimedOut():
self.printFormatted('failure', "--- TIMEOUT ---")
else:
if test.is_fail:
self.printFormatted('failure', "--- UNEXPECTED PASS ---")
if test.expected_failure_reason is not None:
self.printFormatted('failure', test.expected_failure_reason)
else:
self.printFormatted('failure', "--- FAILED ---")
def finished(self):
self._print_progress('Done')
print()
def _print_progress(self, name):
self._clear_line(self._last_status_length)
elapsed = time.time() - self._start_time
if self._total:
progress = (self._passed + self._failed) * 100 // self._total
else:
progress = 0
status = self._templates['status_line'] % {
'passed': self._passed,
'progress': progress,
'failed': self._failed,
'test': name,
'mins': int(elapsed) // 60,
'secs': int(elapsed) % 60
}
status = self._truncateStatusLine(status, 78)
self._last_status_length = len(status)
print(status, end='')
sys.stdout.flush()
def _truncateStatusLine(self, string, length):
if length and len(string) > (length - 3):
return string[:(length - 3)] + "..."
else:
return string
def _clear_line(self, last_length):
raise NotImplementedError()
class ColorProgressIndicator(CompactProgressIndicator):
def __init__(self, context, options, test_count):
templates = {
'status_line': ("[%(mins)02i:%(secs)02i|"
"\033[34m%%%(progress) 4d\033[0m|"
"\033[32m+%(passed) 4d\033[0m|"
"\033[31m-%(failed) 4d\033[0m]: %(test)s"),
'stdout': "\033[1m%s\033[0m",
'stderr': "\033[31m%s\033[0m",
'failure': "\033[1;31m%s\033[0m",
'command': "\033[33m%s\033[0m",
}
super(ColorProgressIndicator, self).__init__(context, options, test_count,
templates)
def printFormatted(self, format, string):
print(self._templates[format] % string)
def _truncateStatusLine(self, string, length):
# Add some slack for the color control chars
return super(ColorProgressIndicator,
self)._truncateStatusLine(string, length + 3 * 9)
def _clear_line(self, last_length):
print("\033[1K\r", end='')
class MonochromeProgressIndicator(CompactProgressIndicator):
def __init__(self, context, options, test_count):
templates = {
'status_line': ("[%(mins)02i:%(secs)02i|%%%(progress) 4d|"
"+%(passed) 4d|-%(failed) 4d]: %(test)s"),
}
super(MonochromeProgressIndicator, self).__init__(context, options,
test_count, templates)
def printFormatted(self, format, string):
print(string)
def _clear_line(self, last_length):
print(("\r" + (" " * last_length) + "\r"), end='')
class JsonTestProgressIndicator(ProgressIndicator):
def __init__(self, context, options, test_count):
super(JsonTestProgressIndicator, self).__init__(context, options,
test_count)
self.slowest_tests = util.FixedSizeTopList(
self.options.slow_tests_cutoff, key=lambda rec: rec['duration'])
self.max_rss_tests = util.FixedSizeTopList(
TRACK_N_HEAVY_TESTS, key=lambda rec: rec['max_rss'])
self.max_vms_tests = util.FixedSizeTopList(
TRACK_N_HEAVY_TESTS, key=lambda rec: rec['max_vms'])
# We want to drop stdout/err for all passed tests on the first try, but we
# need to get outputs for all runs after the first one. To accommodate that,
# reruns are set to keep the result no matter what requirement says, i.e.
# keep_output set to True in the RerunProc.
self._requirement = base.DROP_PASS_STDOUT
self.results = []
self.duration_sum = 0
self.test_count = 0
self.stack_parser = stack_analyzer_util.create_stack_parser()
def on_test_result(self, test, result):
self.process_results(test, result.as_list)
def process_results(self, test, results):
for run, result in enumerate(results):
# TODO(majeski): Support for dummy/grouped results
output = result.output
self._buffer_top_tests(test, result, output, run)
# Omit tests that run as expected on the first try.
# Everything that happens after the first run is included in the output
# even if it flakily passes.
if not result.has_unexpected_output and run == 0:
continue
record = self._test_record(test, result, run)
record.update({
"result": test.output_proc.get_outcome(output),
"stdout": output.stdout,
"stderr": output.stderr,
"error_details": result.error_details,
})
record.update(self.stack_parser.analyze_crash(output.stderr))
self.results.append(record)
def _buffer_top_tests(self, test, result, output, run):
def result_value(test, result, output):
if not result.has_unexpected_output:
return ""
return test.output_proc.get_outcome(output)
record = self._test_record(test, result, run)
record.update(
result=result_value(test, result, output),
marked_slow=test.is_slow,
marked_heavy=test.is_heavy,
)
self.slowest_tests.add(record)
self.max_rss_tests.add(record)
self.max_vms_tests.add(record)
self.duration_sum += record['duration']
self.test_count += 1
def _test_record(self, test, result, run):
record = util.base_test_record(test, result, run)
record.update(
command=result.cmd.to_string(relative=True),
)
return record
def finished(self):
duration_mean = None
if self.test_count:
duration_mean = self.duration_sum / self.test_count
result = {
'results': self.results,
'max_rss_tests': self.max_rss_tests.as_list(),
'max_vms_tests': self.max_vms_tests.as_list(),
'slowest_tests': self.slowest_tests.as_list(),
'duration_mean': duration_mean,
'test_total': self.test_count,
}
with open(self.options.json_test_results, "w") as f:
json.dump(result, f)
class TestScheduleIndicator(ProgressIndicator):
"""Indicator the logs the start:end interval as timestamps for each
executed test. Reruns and variants are accounted for separately.
"""
def __init__(self, context, options, test_count):
super(TestScheduleIndicator, self).__init__(
context, options, test_count)
self._requirement = base.DROP_PASS_STDOUT
self.handle = open(self.options.log_test_schedule, 'w')
def on_test_result(self, test, result):
encoding = sys.stdout.encoding or 'utf-8'
for r in result.as_list:
# TODO(v8-infra): Ideally the full_name should already be encoded
# correctly. This takes care of one test with unicode characters in
# the file name.
name = test.full_name.encode(encoding, errors='replace').decode(encoding)
print(f'{name} {test.variant or "default"}: {r.status()} '
f'({r.output.start_time}:{r.output.end_time})',
file=self.handle)
def finished(self):
self.handle.close()
PROGRESS_INDICATORS = {
'verbose': VerboseProgressIndicator,
'ci': CIProgressIndicator,
'dots': DotsProgressIndicator,
'color': ColorProgressIndicator,
'mono': MonochromeProgressIndicator,
'stream': StreamProgressIndicator,
}

View File

@ -0,0 +1,36 @@
# Copyright 2018 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.
from . import base
class LoadProc(base.TestProc):
"""First processor in the chain that passes all tests to the next processor.
"""
def __init__(self, tests, initial_batch_size=float('inf')):
super(LoadProc, self).__init__()
self.tests = tests
self.initial_batch_size = initial_batch_size
def load_initial_tests(self):
"""Send an initial batch of tests down to executor"""
if not self.initial_batch_size:
return
to_load = self.initial_batch_size
for t in self.tests:
if self._send_test(t):
to_load -= 1
if not to_load:
break
def next_test(self, test):
assert False, \
'Nothing can be connected to the LoadProc' # pragma: no cover
def result_for(self, test, result):
for t in self.tests:
if self._send_test(t):
break

View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
# Copyright 2022 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 os
import sys
import unittest
from unittest.mock import patch
from contextlib import contextmanager
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
from testrunner.testproc.loader import LoadProc
class LoadProcTest(unittest.TestCase):
# TODO(liviurau): test interaction between load_initialtests and results_for.
def setUp(self):
self.loader = LoadProc(iter(range(4)), 2)
@contextmanager
def send_test_return_values(self, l):
def do_pop(*args):
return l.pop()
with patch(
'testrunner.testproc.loader.LoadProc._send_test', side_effect=do_pop):
yield
def test_react_to_2_results(self):
with self.send_test_return_values([True] * 2):
self.loader.result_for(None, None)
self.loader.result_for(None, None)
self.assertEqual(2, next(self.loader.tests))
def test_react_to_result_but_fail_to_send(self):
with self.send_test_return_values([False] * 4):
self.loader.result_for(None, None)
self.assertEqual("empty", next(self.loader.tests, "empty"))
def test_init(self):
with self.send_test_return_values([True] * 4):
self.loader.load_initial_tests()
self.assertEqual(2, next(self.loader.tests))
def test_init_fully_filtered(self):
with self.send_test_return_values([False] * 4):
self.loader.load_initial_tests()
self.assertEqual("empty", next(self.loader.tests, "empty"))
def test_init_filter_1(self):
with self.send_test_return_values([True, False, True]):
self.loader.load_initial_tests()
self.assertEqual(3, next(self.loader.tests))
def test_init_infinity(self):
self.loader = LoadProc(iter(range(500)))
with self.send_test_return_values(([False] * 100) + ([True] * 400)):
self.loader.load_initial_tests()
self.assertEqual("empty", next(self.loader.tests, "empty"))
def test_init_0(self):
self.loader = LoadProc(iter(range(10)), 0)
with self.send_test_return_values([]): # _send_test never gets called
self.loader.load_initial_tests()
self.assertEqual(0, next(self.loader.tests))
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,94 @@
# Copyright 2018 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.
from . import base
from testrunner.local import utils
from testrunner.testproc.indicators import (
JsonTestProgressIndicator, TestScheduleIndicator, PROGRESS_INDICATORS)
from testrunner.testproc.resultdb import rdb_sink, ResultDBIndicator
class ResultsTracker(base.TestProcObserver):
@staticmethod
def create(options):
return ResultsTracker(options.exit_after_n_failures)
"""Tracks number of results and stops to run tests if max_failures reached."""
def __init__(self, max_failures):
super(ResultsTracker, self).__init__()
self._requirement = base.DROP_OUTPUT
self.failed = 0
self.remaining = 0
self.total = 0
self.max_failures = max_failures
def _on_next_test(self, test):
self.total += 1
self.remaining += 1
def _on_result_for(self, test, result):
self.remaining -= 1
# Count failures - treat flakes as failures, too.
if result.has_unexpected_output or result.is_rerun:
self.failed += 1
if self.max_failures and self.failed >= self.max_failures:
print('>>> Too many failures, exiting...')
self.stop()
def standard_show(self, tests):
if tests.test_count_estimate:
percentage = float(self.total) / tests.test_count_estimate * 100
else:
percentage = 0
print(('>>> %d base tests produced %d (%d%s)'
' non-filtered tests') %
(tests.test_count_estimate, self.total, percentage, '%'))
print('>>> %d tests ran' % (self.total - self.remaining))
def exit_code(self):
exit_code = utils.EXIT_CODE_PASS
if self.failed:
exit_code = utils.EXIT_CODE_FAILURES
if not self.total:
exit_code = utils.EXIT_CODE_NO_TESTS
return exit_code
class ProgressProc(base.TestProcObserver):
def __init__(self, context, options, test_count):
super(ProgressProc, self).__init__()
self.procs = [
PROGRESS_INDICATORS[options.progress](context, options, test_count)
]
if options.log_test_schedule:
self.procs.insert(
0,
TestScheduleIndicator(context, options, test_count))
if options.json_test_results:
self.procs.insert(
0,
JsonTestProgressIndicator(context, options, test_count))
sink = rdb_sink()
if sink:
self.procs.append(ResultDBIndicator(context, options, test_count, sink))
self._requirement = max(proc._requirement for proc in self.procs)
def _on_result_for(self, test, result):
for proc in self.procs:
proc.on_test_result(test, result)
def finished(self):
for proc in self.procs:
proc.finished()
def _on_heartbeat(self):
for proc in self.procs:
proc.on_heartbeat()
def _on_event(self, event):
for proc in self.procs:
proc.on_event(event)

View File

@ -0,0 +1,66 @@
# Copyright 2018 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 collections
from . import base
from .result import RerunResult
class RerunProc(base.TestProcProducer):
@staticmethod
def create(options):
if not options.rerun_failures_count:
return None
return RerunProc(options.rerun_failures_count,
options.rerun_failures_max)
def __init__(self, rerun_max, rerun_max_total=None):
super(RerunProc, self).__init__('Rerun')
self._requirement = base.DROP_OUTPUT
self._rerun = {}
self._results = collections.defaultdict(list)
self._rerun_max = rerun_max
self._rerun_total_left = rerun_max_total
def _next_test(self, test):
return self._send_next_subtest(test)
def _result_for(self, test, subtest, result):
# First result
if subtest.procid[-2:] == '-1':
# Passed, no reruns
if not result.has_unexpected_output:
self._send_result(test, result)
return
self._rerun[test.procid] = 0
results = self._results[test.procid]
results.append(result)
if not self.is_stopped and self._needs_rerun(test, result):
self._rerun[test.procid] += 1
if self._rerun_total_left is not None:
self._rerun_total_left -= 1
self._send_next_subtest(test, self._rerun[test.procid])
else:
result = RerunResult.create(results)
self._finalize_test(test)
self._send_result(test, result)
def _needs_rerun(self, test, result):
# TODO(majeski): Limit reruns count for slow tests.
return ((self._rerun_total_left is None or self._rerun_total_left > 0) and
self._rerun[test.procid] < self._rerun_max and
result.has_unexpected_output)
def _send_next_subtest(self, test, run=0):
subtest = test.create_subtest(self, str(run + 1), keep_output=(run != 0))
return self._send_test(subtest)
def _finalize_test(self, test):
del self._rerun[test.procid]
del self._results[test.procid]

View File

@ -0,0 +1,124 @@
# Copyright 2018 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.
class ResultBase(object):
@property
def is_skipped(self):
return False
@property
def is_grouped(self):
return False
@property
def is_rerun(self):
return False
@property
def as_list(self):
return [self]
class Result(ResultBase):
"""Result created by the output processor."""
def __init__(self,
has_unexpected_output,
output,
cmd=None,
error_details=None):
self.has_unexpected_output = has_unexpected_output
self.output = output
self.cmd = cmd
self.error_details = error_details
def status(self):
if self.has_unexpected_output:
if not hasattr(self.output, "HasCrashed"):
raise Exception(type(self))
if self.output.HasCrashed():
return 'CRASH'
else:
return 'FAIL'
else:
return 'PASS'
class GroupedResult(ResultBase):
"""Result consisting of multiple results. It can be used by processors that
create multiple subtests for each test and want to pass all results back.
"""
@staticmethod
def create(results):
"""Create grouped result from the list of results. It filters out skipped
results. If all results are skipped results it returns skipped result.
Args:
results: list of pairs (test, result)
"""
results = [(t, r) for (t, r) in results if not r.is_skipped]
if not results:
return SKIPPED
return GroupedResult(results)
def __init__(self, results):
self.results = results
@property
def is_grouped(self):
return True
class SkippedResult(ResultBase):
"""Result without any meaningful value. Used primarily to inform the test
processor that it's test wasn't executed.
"""
@property
def is_skipped(self):
return True
SKIPPED = SkippedResult()
class RerunResult(Result):
"""Result generated from several reruns of the same test. It's a subclass of
Result since the result of rerun is result of the last run. In addition to
normal result it contains results of all reruns.
"""
@staticmethod
def create(results):
"""Create RerunResult based on list of results. List cannot be empty. If it
has only one element it's returned as a result.
"""
assert results
if len(results) == 1:
return results[0]
return RerunResult(results)
def __init__(self, results):
"""Has unexpected output and the output itself of the RerunResult equals to
the last result in the passed list.
"""
assert results
last = results[-1]
super(RerunResult, self).__init__(last.has_unexpected_output, last.output,
last.cmd)
self.results = results
@property
def is_rerun(self):
return True
@property
def as_list(self):
return self.results
def status(self):
return ' '.join(r.status() for r in self.results)

View File

@ -0,0 +1,143 @@
# Copyright 2022 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 logging
import os
import re
import tempfile
from . import base
from .indicators import (
formatted_result_output,
ProgressIndicator,
)
from .util import base_test_record
class ResultDBIndicator(ProgressIndicator):
def __init__(self, context, options, test_count, sink):
super(ResultDBIndicator, self).__init__(context, options, test_count)
self._requirement = base.DROP_PASS_OUTPUT
self.rpc = ResultDB_RPC(sink)
def on_test_result(self, test, result):
for run, sub_result in enumerate(result.as_list):
self.send_result(test, sub_result, run)
def send_result(self, test, result, run):
# We need to recalculate the observed (but lost) test behaviour.
# `result.has_unexpected_output` indicates that the run behaviour of the
# test matches the expected behaviour irrespective of passing or failing.
if test.skip_rdb(result):
return
result_expected = not result.has_unexpected_output
test_should_pass = not test.is_fail
run_passed = (result_expected == test_should_pass)
rdb_result = {
'testId': strip_ascii_control_characters(test.rdb_test_id),
'status': 'PASS' if run_passed else 'FAIL',
'expected': result_expected,
}
if result.output and result.output.duration:
rdb_result.update(duration=f'{result.output.duration:f}s')
if result.has_unexpected_output:
formated_output = formatted_result_output(result, relative=True)
relative_cmd = result.cmd.to_string(relative=True)
artifacts = {
'output' : write_artifact(formated_output),
'cmd' : write_artifact(relative_cmd)
}
rdb_result.update(artifacts=artifacts)
summary = '<p><text-artifact artifact-id="output"></p>'
summary += '<p><text-artifact artifact-id="cmd"></p>'
rdb_result.update(summary_html=summary)
record = base_test_record(test, result, run)
record.update(
processor=test.processor_name,
subtest_id=test.subtest_id,
path=test.path)
rdb_result.update(tags=extract_tags(record))
self.rpc.send(rdb_result)
def write_artifact(value):
with tempfile.NamedTemporaryFile(
mode='w', delete=False, encoding='utf-8') as tmp:
tmp.write(value)
return { 'filePath': tmp.name }
def extract_tags(record):
tags = []
for k, v in record.items():
if not v:
continue
if type(v) == list:
tags += [sanitized_kv_dict(k, e) for e in v]
else:
tags.append(sanitized_kv_dict(k, v))
return tags
def sanitized_kv_dict(k, v):
return dict(key=k, value=strip_ascii_control_characters(v))
def strip_ascii_control_characters(unicode_string):
return re.sub(r'[^\x20-\x7E]', '?', str(unicode_string))
TESTING_SINK = None
def rdb_sink():
try:
import requests
except:
log_instantiation_failure('Failed to import requests module.')
return None
if TESTING_SINK:
return TESTING_SINK
luci_context = os.environ.get('LUCI_CONTEXT')
if not luci_context:
log_instantiation_failure('No LUCI_CONTEXT found.')
return None
with open(luci_context, mode="r", encoding="utf-8") as f:
config = json.load(f)
sink = config.get('result_sink', None)
if not sink:
log_instantiation_failure('No ResultDB sink found.')
return None
return sink
def log_instantiation_failure(error_message):
logging.info(f'{error_message} No results will be sent to ResultDB.')
class ResultDB_RPC:
def __init__(self, sink):
import requests
self.session = requests.Session()
self.session.headers = {
'Authorization': f'ResultSink {sink.get("auth_token")}',
}
self.url = f'http://{sink.get("address")}/prpc/luci.resultsink.v1.Sink/ReportTestResults'
def send(self, result):
payload = dict(testResults=[result])
try:
self.session.post(self.url, json=payload).raise_for_status()
except Exception as e:
logging.error(f'Request failed: {payload}')
raise e
def __del__(self):
self.session.close()

View File

@ -0,0 +1,46 @@
# Copyright 2023 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.
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Thread
import requests
import socket
class RDBRequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
self.server.register_post_data(post_data)
self.send_response(requests.codes.ok)
self.end_headers()
return
def get_free_port():
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
s.bind(('localhost', 0))
_, port = s.getsockname()
s.close()
return port
class RDBMockServer(HTTPServer):
def __init__(self) -> None:
self.port = get_free_port()
super().__init__(('localhost', self.port), RDBRequestHandler)
self.thread = Thread(target=self.serve_forever)
self.thread.daemon = True
self.thread.start()
self.post_data = []
def register_post_data(self, data):
self.post_data.append(data)
@property
def address(self):
return f'localhost:{self.port}'

View File

@ -0,0 +1,105 @@
# Copyright 2023 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.
from collections import namedtuple
from pyfakefs import fake_filesystem_unittest
import json
import os
import sys
import unittest
# Needed because the test runner contains relative imports.
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
# Standard library imports...
from testrunner.testproc.resultdb import ResultDBIndicator, write_artifact
from testrunner.testproc.resultdb_server_mock import RDBMockServer
from testrunner.local.command import BaseCommand
from testrunner.objects.output import Output
def mock_test262(is_fail, rdb_test_id):
Test = namedtuple('Test', [
'is_fail', 'rdb_test_id', 'expected_outcomes', 'framework_name',
'full_name', 'random_seed', 'shard_id', 'shard_count', 'shell', 'variant',
'variant_flags', 'processor_name', 'subtest_id', 'path', 'skip_rdb'
])
def skip_if_expected(result):
return not result.has_unexpected_output
return Test(is_fail, rdb_test_id, [], 'unittest', 'test-123', 0, 1, 2, '',
'default', [], 'none', None, '/tmp/test/some_test.js',
skip_if_expected)
def mock_result(is_fail, duration=0):
Result = namedtuple('Result', ['has_unexpected_output', 'output', 'cmd'])
return Result(
is_fail,
Output(start_time=100.0, end_time=100.0 + duration),
BaseCommand('echo'))
class TestResultDBIndicator(unittest.TestCase):
def test_send(self):
def assert_sent_count(count):
self.assertEqual(len(server.post_data), count)
server = RDBMockServer()
self.rdb = ResultDBIndicator(
context=None,
options=None,
test_count=0,
sink=dict(auth_token='none', address=server.address))
self.rdb.send_result(
mock_test262(True, 'expected2fail-fail'), mock_result(False), 0)
assert_sent_count(0)
self.rdb.send_result(
mock_test262(True, 'expected2fail-pass'), mock_result(True), 0)
assert_sent_count(1)
self.rdb.send_result(
mock_test262(False, 'expected2pass-fail'), mock_result(True), 0)
assert_sent_count(2)
self.rdb.send_result(
mock_test262(False, 'expected2pass-pass'), mock_result(False), 0)
assert_sent_count(2)
self.rdb.send_result(
mock_test262(True, 'fast'), mock_result(True, duration=0.00001), 0)
assert_sent_count(3)
def assert_testid_at_index(testid, idx):
data = json.loads(server.post_data[idx])
self.assertEqual(data['testResults'][0]['testId'], testid)
def assert_duration_at_index(duration_str, idx):
data = json.loads(server.post_data[idx])
self.assertEqual(data['testResults'][0]['duration'], duration_str)
assert_testid_at_index('expected2fail-pass', 0)
assert_testid_at_index('expected2pass-fail', 1)
assert_duration_at_index('0.000010s', 2)
class TestFileOutput(fake_filesystem_unittest.TestCase):
def test_write_artifact_encoded(self):
"""Test that characters mapping to utf-8 encoding work."""
self.setUpPyfakefs(allow_root_user=True)
with open(write_artifact('\u0394')['filePath']) as f:
self.assertEqual('\u0394', f.read())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,57 @@
# Copyright 2018 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.
from collections import defaultdict
from . import base
from ..utils import random_utils
class SeedProc(base.TestProcProducer):
def __init__(self, count, seed=None, parallel_subtests=1):
"""
Args:
count: How many subtests with different seeds to create for each test.
0 means infinite.
seed: seed to use. None means random seed for each subtest.
parallel_subtests: How many subtest of each test to run at the same time.
"""
super(SeedProc, self).__init__('Seed')
self._requirement = base.DROP_RESULT
self._count = count
self._seed = seed
self._last_idx = defaultdict(int)
self._todo = defaultdict(int)
self._parallel_subtests = parallel_subtests
if count:
self._parallel_subtests = min(self._parallel_subtests, count)
def _next_test(self, test):
is_loaded = False
for _ in range(0, self._parallel_subtests):
is_loaded |= self._try_send_next_test(test)
return is_loaded
def _result_for(self, test, subtest, result):
self._todo[test.procid] -= 1
if not self._try_send_next_test(test):
if not self._todo.get(test.procid):
del self._last_idx[test.procid]
del self._todo[test.procid]
self._send_result(test, None)
def _try_send_next_test(self, test):
def create_subtest(idx):
seed = self._seed or random_utils.random_seed()
return test.create_subtest(self, idx, random_seed=seed)
num = self._last_idx[test.procid]
if not self._count or num < self._count:
num += 1
self._todo[test.procid] += 1
self._last_idx[test.procid] = num
return self._send_test(create_subtest(num))
return False

View File

@ -0,0 +1,59 @@
# Copyright 2021 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.
from collections import deque
from . import base
class SequenceProc(base.TestProc):
"""Processor ensuring heavy tests are sent sequentially into the execution
pipeline.
The class keeps track of the number of tests in the pipeline marked heavy
and permits only a configurable amount. An excess amount is queued and sent
as soon as other heavy tests return.
"""
def __init__(self, max_heavy):
"""Initialize the processor.
Args:
max_heavy: The maximum number of heavy tests that will be sent further
down the pipeline simultaneously.
"""
super(SequenceProc, self).__init__()
assert max_heavy > 0
self.max_heavy = max_heavy
self.n_heavy = 0
self.buffer = deque()
def next_test(self, test):
if test.is_heavy:
if self.n_heavy < self.max_heavy:
# Enough space to send more heavy tests. Check if the test is not
# filtered otherwise.
used = self._send_test(test)
if used:
self.n_heavy += 1
return used
else:
# Too many tests in the pipeline. Buffer the test and indicate that
# this test didn't end up in the execution queue (i.e. test loader
# will try to send more tests).
self.buffer.append(test)
return False
else:
return self._send_test(test)
def result_for(self, test, result):
if test.is_heavy:
# A heavy test finished computing. Try to send one from the buffer.
self.n_heavy -= 1
while self.buffer:
next_test = self.buffer.popleft()
if self._send_test(next_test):
self.n_heavy += 1
break
self._send_result(test, result)

View File

@ -0,0 +1,167 @@
#!/usr/bin/env python3
# Copyright 2021 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.
"""
Test integrating the sequence processor into a simple test pipeline.
"""
import os
import sys
import unittest
# Needed because the test runner contains relative imports.
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
from testrunner.testproc import base
from testrunner.testproc.loader import LoadProc
from testrunner.testproc.sequence import SequenceProc
class FakeExecutionProc(base.TestProc):
"""Simulates the pipeline sink consuming and running the tests.
Test execution is simulated for each test by calling run().
"""
def __init__(self):
super(FakeExecutionProc, self).__init__()
self.tests = []
def next_test(self, test):
self.tests.append(test)
return True
def run(self):
test = self.tests.pop()
self._send_result(test, test.n)
class FakeResultObserver(base.TestProcObserver):
"""Observer to track all results sent back through the pipeline."""
def __init__(self):
super(FakeResultObserver, self).__init__()
self.tests = set([])
def _on_result_for(self, test, result):
self.tests.add(test.n)
class FakeTest(object):
"""Simple test representation to differentiate light/heavy tests."""
def __init__(self, n, is_heavy):
self.n = n
self.is_heavy = is_heavy
self.keep_output = False
class TestSequenceProc(unittest.TestCase):
def _test(self, tests, batch_size, max_heavy):
# Set up a simple processing pipeline:
# Loader -> observe results -> sequencer -> execution.
loader = LoadProc(iter(tests), initial_batch_size=batch_size)
results = FakeResultObserver()
sequence_proc = SequenceProc(max_heavy)
execution = FakeExecutionProc()
loader.connect_to(results)
results.connect_to(sequence_proc)
sequence_proc.connect_to(execution)
# Fill the execution queue (with the number of tests potentially
# executed in parallel).
loader.load_initial_tests()
# Simulate the execution test by test.
while execution.tests:
# Assert the invariant of maximum heavy tests executed simultaneously.
self.assertLessEqual(
sum(int(test.is_heavy) for test in execution.tests), max_heavy)
# As in the real pipeline, running a test and returning its result
# will add another test into the pipeline.
execution.run()
# Ensure that all tests are processed and deliver results.
self.assertEqual(set(test.n for test in tests), results.tests)
def test_wrong_usage(self):
with self.assertRaises(Exception):
SequenceProc(0)
def test_no_tests(self):
self._test([], 1, 1)
def test_large_batch_light(self):
self._test([
FakeTest(0, False),
FakeTest(1, False),
FakeTest(2, False),
], 4, 1)
def test_small_batch_light(self):
self._test([
FakeTest(0, False),
FakeTest(1, False),
FakeTest(2, False),
], 2, 1)
def test_large_batch_heavy(self):
self._test([
FakeTest(0, True),
FakeTest(1, True),
FakeTest(2, True),
], 4, 1)
def test_small_batch_heavy(self):
self._test([
FakeTest(0, True),
FakeTest(1, True),
FakeTest(2, True),
], 2, 1)
def test_large_batch_mixed(self):
self._test([
FakeTest(0, True),
FakeTest(1, False),
FakeTest(2, True),
FakeTest(3, False),
], 4, 1)
def test_small_batch_mixed(self):
self._test([
FakeTest(0, True),
FakeTest(1, False),
FakeTest(2, True),
FakeTest(3, False),
], 2, 1)
def test_large_batch_more_heavy(self):
self._test([
FakeTest(0, True),
FakeTest(1, True),
FakeTest(2, True),
FakeTest(3, False),
FakeTest(4, True),
FakeTest(5, True),
FakeTest(6, False),
], 4, 2)
def test_small_batch_more_heavy(self):
self._test([
FakeTest(0, True),
FakeTest(1, True),
FakeTest(2, True),
FakeTest(3, False),
FakeTest(4, True),
FakeTest(5, True),
FakeTest(6, False),
], 2, 2)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,45 @@
# Copyright 2018 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.
from . import base
# Alphabet size determines the hashing radix. Choosing a prime number prevents
# clustering of the hashes.
HASHING_ALPHABET_SIZE = 2 ** 7 -1
def radix_hash(capacity, key):
h = 0
for character in key:
h = (h * HASHING_ALPHABET_SIZE + ord(character)) % capacity
return h
class ShardProc(base.TestProcFilter):
@staticmethod
def create(options):
myid, count = options.shard_info
if count == 1:
return None
return ShardProc(myid, count)
"""Processor distributing tests between shards.
It hashes the unique test identifiers uses the hash to shard tests.
"""
def __init__(self, myid, shards_count):
"""
Args:
myid: id of the shard within [0; shards_count - 1]
shards_count: number of shards
"""
super(ShardProc, self).__init__()
assert myid >= 0 and myid < shards_count
self._myid = myid
self._shards_count = shards_count
def _filter(self, test):
return self._myid != radix_hash(self._shards_count, test.procid)

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
# Copyright 2019 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 os
import sys
import unittest
# Needed because the test runner contains relative imports.
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
from testrunner.testproc.shard import radix_hash
class TestRadixHashing(unittest.TestCase):
def test_hash_character_by_radix(self):
self.assertEqual(97, radix_hash(capacity=2**32, key="a"))
def test_hash_character_by_radix_with_capacity(self):
self.assertEqual(6, radix_hash(capacity=7, key="a"))
def test_hash_string(self):
self.assertEqual(6, radix_hash(capacity=7, key="ab"))
def test_hash_test_id(self):
self.assertEqual(
5,
radix_hash(
capacity=7, key="test262/Map/class-private-method-Variant-0-1"))
def test_hash_boundaries(self):
total_variants = 5
cases = []
for case in [
"test262/Map/class-private-method",
"test262/Map/class-public-method",
"test262/Map/object-retrieval",
"test262/Map/object-deletion",
"test262/Map/object-creation",
"test262/Map/garbage-collection",
]:
for variant_index in range(total_variants):
cases.append("%s-Variant-%d" % (case, variant_index))
for case in cases:
self.assertTrue(0 <= radix_hash(capacity=7, key=case) < 7)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,37 @@
# Copyright 2018 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 faulthandler
import logging
import signal
from . import base
from testrunner.local import utils
class SignalProc(base.TestProcObserver):
def __init__(self):
super(SignalProc, self).__init__()
self.exit_code = utils.EXIT_CODE_PASS
signal.signal(signal.SIGINT, self._on_ctrlc)
signal.signal(signal.SIGTERM, self._on_sigterm)
def _on_ctrlc(self, _signum, _stack_frame):
logging.warning('Ctrl-C detected, early abort...')
self.exit_code = utils.EXIT_CODE_INTERRUPTED
self.stop()
if logging.getLogger().isEnabledFor(logging.INFO):
faulthandler.dump_traceback()
def _on_sigterm(self, _signum, _stack_frame):
logging.warning('SIGTERM received, early abort...')
self.exit_code = utils.EXIT_CODE_TERMINATED
self.stop()
if logging.getLogger().isEnabledFor(logging.INFO):
faulthandler.dump_traceback()
def worst_exit_code(self, results):
exit_code = results.exit_code()
# Indicate if a SIGINT or SIGTERM happened.
return max(exit_code, self.exit_code)

View File

@ -0,0 +1,3 @@
# Copyright 2022 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.

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
# Copyright 2022 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.
# This file exists to be able to mock some of the imports in ClusterFuzz. Since
# we only need a specific functionality in ClusterFuzz (Stack Analysis) we do
# not need most of the imports that it uses, so we fake them here in order to
# not crash when we run.
def empty_fn(*args, **kwargs):
raise 'empty function was used'
kernel_utils, \
storage, \
fetch_artifact, \
settings, \
symbols_downloader, \
= [empty_fn] * 5
class ProjectConfig:
def get(self):
"""Return empty config properties when ClusterFuzz tries to find a project
config.
"""
return None

View File

@ -0,0 +1,107 @@
#!/usr/bin/env python3
# Copyright 2022 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 logging
import os
import re
import sys
ANALYSIS_FAILURE_STATES = ['NULL', 'Unknown', '', None]
STACKFRAME_REGEX = r'^#\d+.*'
FILE_LOCATION_REGEX = r'.*\:\d+\:\d+$'
def fake_clusterfuzz_imports(modules):
for module in modules:
sys.modules[module] = __import__('clusterfuzz_fakes')
def pre_clusterfuzz_import():
local_path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(local_path)
fake_clusterfuzz_imports([
'clusterfuzz._internal.platforms.android',
'clusterfuzz._internal.google_cloud_utils',
'clusterfuzz._internal.config.local_config',
])
class CustomStackParser:
def __init__(self):
pre_clusterfuzz_import()
from clusterfuzz.stacktraces import StackParser, MAX_CRASH_STATE_FRAMES
from clusterfuzz.stacktraces import llvm_test_one_input_override
from clusterfuzz.stacktraces import constants
self.MAX_CRASH_STATE_FRAMES = MAX_CRASH_STATE_FRAMES
self.stack_parser = StackParser()
self.constants = constants
self.llvm_test_one_input_override = llvm_test_one_input_override
def analyze_crash(self, stderr):
try:
stderr_analysis = self.stack_parser.parse(stderr)
self.custom_analyzer(stderr_analysis)
return {
"crash_state": stderr_analysis.crash_state.strip(),
"crash_type": stderr_analysis.crash_type.strip(),
}
except Exception as e:
logging.info(e)
return {
"crash_state": "Unknown",
"crash_type": "Unknown",
}
def _extract_function_name(self, frame):
split_frame = frame.split()
start_loc = 1
end_loc = -1
if len(split_frame) > 2 and split_frame[2] == 'in':
start_loc = 3
split_frame = split_frame[start_loc:]
for idx, item in enumerate(split_frame):
# exclude file name and everything after it
if re.match(FILE_LOCATION_REGEX, item):
end_loc = idx
break
if end_loc != -1:
return ' '.join(split_frame[:end_loc])
return None
def _fallback_crash_state(self, stacktrace):
status = []
for line in [l.strip() for l in stacktrace.splitlines()]:
if re.match(STACKFRAME_REGEX, line):
frame = self._extract_function_name(line)
if not self.stack_parser.ignore_stack_frame(frame):
status.append(frame)
if len(status) >= self.MAX_CRASH_STATE_FRAMES:
break
return '\n'.join(status)
def custom_analyzer(self, crash_info):
if crash_info.crash_state in ANALYSIS_FAILURE_STATES:
fallback_state = self._fallback_crash_state(crash_info.crash_stacktrace)
crash_info.crash_state = fallback_state or crash_info.crash_state
class EmptyStackParser:
def analyze_crash(self, stderr):
return {}
def create_stack_parser():
try:
return CustomStackParser()
except ImportError as e:
logging.info(e)
return EmptyStackParser()

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
# Copyright 2022 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 os
import sys
import unittest
TOOLS_PATH = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(TOOLS_PATH)
TEST_DATA_ROOT = os.path.join(TOOLS_PATH, 'testproc', 'stack_utils', 'testdata')
TEST_DATA_GENERAL = os.path.join(TEST_DATA_ROOT, 'analyze_crash')
TEST_DATA_CUSTOM = os.path.join(TEST_DATA_ROOT, 'custom_analyzer')
from testproc.stack_utils.stack_analyzer_util import create_stack_parser
class TestScript(unittest.TestCase):
# TODO(almuthanna): find out why these test cases are not analyzed and add
# logic to make them pass.
skipped_tests = [
'type_assertion_1.txt',
'type_assertion_2.txt',
'static_assertion_2.txt',
'static_assertion_1.txt',
]
def test_analyze_crash(self):
stack_parser = create_stack_parser()
for file in [
f for f in os.listdir(TEST_DATA_GENERAL) if f.endswith('.txt')
]:
if file in self.skipped_tests:
continue
filepath = os.path.join(TEST_DATA_GENERAL, file)
exp_filepath = os.path.join(TEST_DATA_GENERAL,
file.replace('.txt', '.expected.json'))
with self.subTest(test_name=file[:-4]):
with open(filepath) as f:
result = stack_parser.analyze_crash(f.read())
with open(exp_filepath, 'r') as exp_f:
expectation = json.load(exp_f)
self.assertDictEqual(result, expectation)
def test_fallback_crash_state(self):
self.maxDiff = None
stack_parser = create_stack_parser()
for file in [f for f in os.listdir(TEST_DATA_CUSTOM) if f.endswith('.txt')]:
filepath = os.path.join(TEST_DATA_CUSTOM, file)
exp_filepath = os.path.join(TEST_DATA_CUSTOM,
file.replace('.txt', '.expected'))
with self.subTest(test_name=file[:-4]):
with open(filepath) as f:
result = stack_parser._fallback_crash_state(f.read())
with open(exp_filepath, 'r') as exp_f:
expectation = exp_f.read()
self.assertEqual(result, expectation)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,4 @@
{
"crash_type": "CHECK failure",
"crash_state": "kind() == CodeKind::BASELINE"
}

View File

@ -0,0 +1,20 @@
Warning: disabling flag --regexp_tier_up due to conflicting flags
#
# Fatal error in , line 0
# Check failed: kind() == CodeKind::BASELINE.
#
#
#
#FailureMessage Object: 0x7fff55c98300
==== C stack trace ===============================
/b/s/w/ir/out/build/d8(+0xec56a3) [0x55fbef6236a3]
/b/s/w/ir/out/build/d8(+0xec4f4b) [0x55fbef622f4b]
/b/s/w/ir/out/build/d8(+0xebb4d5) [0x55fbef6194d5]
/b/s/w/ir/out/build/d8(+0x42cd8c) [0x55fbeeb8ad8c]
/b/s/w/ir/out/build/d8(+0x42bd45) [0x55fbeeb89d45]
[0x2c4d0000c1ca]
Received signal 6
Aborted (core dumped)

View File

@ -0,0 +1,4 @@
{
"crash_type": "Data race\nWRITE 8",
"crash_state": "SetFlag\nMarkWasUsedForAllocation\nv8::internal::PagedSpaceBase::SetLinearAllocationArea(unsigned long, unsigned long)"
}

View File

@ -0,0 +1,91 @@
==================
WARNING: ThreadSanitizer: data race (pid=24781)
Write of size 8 at 0x7e9b00080008 by main thread (mutexes: write M0):
#0 operator|= src/base/flags.h:47:11 (d8+0xa03eff) (BuildId: 2d1896fcafe8220c)
#1 operator|= src/base/flags.h:66:46 (d8+0xa03eff)
#2 SetFlag src/heap/basic-memory-chunk.h:198:48 (d8+0xa03eff)
#3 MarkWasUsedForAllocation src/heap/memory-chunk.h:223:37 (d8+0xa03eff)
#4 v8::internal::PagedSpaceBase::SetLinearAllocationArea(unsigned long, unsigned long) src/heap/paged-spaces.cc:435:13 (d8+0xa03eff)
#5 v8::internal::PagedSpaceBase::TryAllocationFromFreeListMain(unsigned long, v8::internal::AllocationOrigin) src/heap/paged-spaces.cc:653:3 (d8+0xa04b8e) (BuildId: 2d1896fcafe8220c)
#6 v8::internal::PagedSpaceBase::RawRefillLabMain(int, v8::internal::AllocationOrigin) src/heap/paged-spaces.cc:991:7 (d8+0xa066b4) (BuildId: 2d1896fcafe8220c)
#7 v8::internal::PagedSpaceBase::RefillLabMain(int, v8::internal::AllocationOrigin) src/heap/paged-spaces.cc:960:10 (d8+0xa065f7) (BuildId: 2d1896fcafe8220c)
#8 EnsureAllocation src/heap/paged-spaces-inl.h:89:10 (d8+0x9f33a8) (BuildId: 2d1896fcafe8220c)
#9 EnsureAllocation src/heap/new-spaces-inl.h:109:24 (d8+0x9f33a8)
#10 v8::internal::PagedNewSpace::EnsureAllocation(int, v8::internal::AllocationAlignment, v8::internal::AllocationOrigin, int*) src/heap/new-spaces.h:800:25 (d8+0x9f33a8)
#11 AllocateRawUnaligned src/heap/spaces-inl.h:277:8 (d8+0x8b41ea) (BuildId: 2d1896fcafe8220c)
#12 AllocateRawSlow src/heap/spaces-inl.h:331:13 (d8+0x8b41ea)
#13 AllocateRaw src/heap/spaces-inl.h:269:31 (d8+0x8b41ea)
#14 AllocateRaw<(v8::internal::AllocationType)0> src/heap/heap-allocator-inl.h:107:28 (d8+0x8b41ea)
#15 AllocateRawWith<(v8::internal::HeapAllocator::AllocationRetryMode)1> src/heap/heap-allocator-inl.h:230:14 (d8+0x8b41ea)
#16 v8::internal::Factory::AllocateRawWithAllocationSite(v8::internal::Handle<v8::internal::Map>, v8::internal::AllocationType, v8::internal::Handle<v8::internal::AllocationSite>) src/heap/factory.cc:356:36 (d8+0x8b41ea)
#17 v8::internal::Factory::NewJSObjectFromMap(v8::internal::Handle<v8::internal::Map>, v8::internal::AllocationType, v8::internal::Handle<v8::internal::AllocationSite>) src/heap/factory.cc:2728:7 (d8+0x8ba650) (BuildId: 2d1896fcafe8220c)
#18 __RT_impl_Runtime_NewArray src/runtime/runtime-array.cc:116:58 (d8+0xfa02b1) (BuildId: 2d1896fcafe8220c)
#19 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) src/runtime/runtime-array.cc:44:1 (d8+0xfa02b1)
#20 Builtins_AsyncFromSyncIteratorPrototypeThrow setup-isolate-deserialize.cc (d8+0x1c3cd77)
#21 v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSFunction>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>) src/execution/execution.cc:538:10 (d8+0x8187b5) (BuildId: 2d1896fcafe8220c)
#22 v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2151:7 (d8+0x5b210e) (BuildId: 2d1896fcafe8220c)
#23 v8::Script::Run(v8::Local<v8::Context>) src/api/api.cc:2078:10 (d8+0x5b1840) (BuildId: 2d1896fcafe8220c)
#24 v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::PrintResult, v8::Shell::ReportExceptions, v8::Shell::ProcessMessageQueue) src/d8/d8.cc:879:28 (d8+0x5703cb) (BuildId: 2d1896fcafe8220c)
#25 v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:4379:10 (d8+0x588620) (BuildId: 2d1896fcafe8220c)
#26 v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:5107:39 (d8+0x58c487) (BuildId: 2d1896fcafe8220c)
#27 v8::Shell::Main(int, char**) src/d8/d8.cc:5886:18 (d8+0x58efe0) (BuildId: 2d1896fcafe8220c)
#28 main src/d8/d8.cc:5976:43 (d8+0x58f4ee) (BuildId: 2d1896fcafe8220c)
Previous read of size 8 at 0x7e9b00080008 by thread T6:
#0 GetFlags src/heap/basic-memory-chunk.h:211:45 (d8+0x88ee2f) (BuildId: 2d1896fcafe8220c)
#1 ShouldSkipEvacuationSlotRecording src/heap/basic-memory-chunk.h:240:29 (d8+0x88ee2f)
#2 RecordSlot src/heap/mark-compact-inl.h:68:21 (d8+0x88ee2f)
#3 void v8::internal::ConcurrentMarkingVisitor::RecordSlot<v8::internal::CompressedHeapObjectSlot>(v8::internal::HeapObject, v8::internal::CompressedHeapObjectSlot, v8::internal::HeapObject) src/heap/concurrent-marking.cc:481:5 (d8+0x88ee2f)
#4 void v8::internal::MarkingVisitorBase<v8::internal::ConcurrentMarkingVisitor, v8::internal::ConcurrentMarkingState>::ProcessStrongHeapObject<v8::internal::CompressedHeapObjectSlot>(v8::internal::HeapObject, v8::internal::CompressedHeapObjectSlot, v8::internal::HeapObject) src/heap/marking-visitor-inl.h:49:23 (d8+0x88ed97) (BuildId: 2d1896fcafe8220c)
#5 VisitPointersImpl<v8::internal::CompressedObjectSlot> src/heap/marking-visitor-inl.h:90:7 (d8+0x894a3a) (BuildId: 2d1896fcafe8220c)
#6 VisitPointers src/heap/marking-visitor.h:197:5 (d8+0x894a3a)
#7 IteratePointers<v8::internal::ConcurrentMarkingVisitor> src/objects/objects-body-descriptors-inl.h:127:6 (d8+0x894a3a)
#8 IterateBody<v8::internal::ConcurrentMarkingVisitor> src/objects/objects-body-descriptors-inl.h:358:5 (d8+0x894a3a)
#9 int v8::internal::ConcurrentMarkingVisitorUtility::VisitJSObjectSubclass<v8::internal::ConcurrentMarkingVisitor, v8::internal::JSFunction, v8::internal::JSFunction::BodyDescriptor>(v8::internal::ConcurrentMarkingVisitor*, v8::internal::Map, v8::internal::JSFunction) src/heap/concurrent-marking.cc:102:5 (d8+0x894a3a)
#10 VisitJSObjectSubclass<v8::internal::JSFunction, v8::internal::JSFunction::BodyDescriptor> src/heap/concurrent-marking.cc:489:12 (d8+0x884a6f) (BuildId: 2d1896fcafe8220c)
#11 VisitJSFunction src/heap/marking-visitor-inl.h:178:34 (d8+0x884a6f)
#12 Visit src/heap/objects-visiting-inl.h:65:5 (d8+0x884a6f)
#13 v8::internal::ConcurrentMarking::RunMajor(v8::JobDelegate*, v8::base::EnumSet<v8::internal::CodeFlushMode, int>, unsigned int, bool) src/heap/concurrent-marking.cc:758:41 (d8+0x884a6f)
#14 v8::internal::ConcurrentMarking::JobTaskMajor::Run(v8::JobDelegate*) src/heap/concurrent-marking.cc:606:28 (d8+0x898719) (BuildId: 2d1896fcafe8220c)
#15 v8::DelayedTasksPlatform::DelayedJob::Run(v8::JobDelegate*) src/d8/d8-platforms.cc:304:18 (d8+0x565b28) (BuildId: 2d1896fcafe8220c)
#16 v8::platform::DefaultJobWorker::Run() src/libplatform/default-job.h:147:18 (d8+0x1e5184b) (BuildId: 2d1896fcafe8220c)
#17 v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread::Run() src/libplatform/default-worker-threads-task-runner.cc:73:11 (d8+0x1e586b0) (BuildId: 2d1896fcafe8220c)
#18 NotifyStartedAndRun src/base/platform/platform.h:596:5 (d8+0x1e4bc22) (BuildId: 2d1896fcafe8220c)
#19 v8::base::ThreadEntry(void*) src/base/platform/platform-posix.cc:1112:11 (d8+0x1e4bc22)
Mutex M0 (0x7b5400000710) created at:
#0 pthread_mutex_init /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1316:3 (d8+0x4df75f) (BuildId: 2d1896fcafe8220c)
#1 InitializeNativeHandle src/base/platform/mutex.cc:99:12 (d8+0x1e4408b) (BuildId: 2d1896fcafe8220c)
#2 v8::base::Mutex::Mutex() src/base/platform/mutex.cc:152:3 (d8+0x1e4408b)
#3 v8::internal::PagedSpaceBase::PagedSpaceBase(v8::internal::Heap*, v8::internal::AllocationSpace, v8::internal::Executability, v8::internal::FreeList*, v8::internal::AllocationCounter&, v8::internal::LinearAllocationArea&, v8::internal::LinearAreaOriginalData&, v8::internal::CompactionSpaceKind) src/heap/paged-spaces.cc:120:17 (d8+0xa023d8) (BuildId: 2d1896fcafe8220c)
#4 PagedSpaceForNewSpace src/heap/new-spaces.cc:923:7 (d8+0x9f1f66) (BuildId: 2d1896fcafe8220c)
#5 v8::internal::PagedNewSpace::PagedNewSpace(v8::internal::Heap*, unsigned long, unsigned long, v8::internal::LinearAllocationArea&) src/heap/new-spaces.cc:1042:7 (d8+0x9f1f66)
#6 make_unique<v8::internal::PagedNewSpace, v8::internal::Heap *, unsigned long &, unsigned long &, v8::internal::LinearAllocationArea &> buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:670:30 (d8+0x90c4a9) (BuildId: 2d1896fcafe8220c)
#7 v8::internal::Heap::SetUpSpaces(v8::internal::LinearAllocationArea&, v8::internal::LinearAllocationArea&) src/heap/heap.cc:5559:27 (d8+0x90c4a9)
#8 v8::internal::Isolate::Init(v8::internal::SnapshotData*, v8::internal::SnapshotData*, v8::internal::SnapshotData*, bool) src/execution/isolate.cc:4264:9 (d8+0x843e08) (BuildId: 2d1896fcafe8220c)
#9 v8::internal::Isolate::InitWithSnapshot(v8::internal::SnapshotData*, v8::internal::SnapshotData*, v8::internal::SnapshotData*, bool) src/execution/isolate.cc:4036:10 (d8+0x8455f9) (BuildId: 2d1896fcafe8220c)
#10 v8::internal::Snapshot::Initialize(v8::internal::Isolate*) src/snapshot/snapshot.cc:182:27 (d8+0x10bc8e1) (BuildId: 2d1896fcafe8220c)
#11 v8::Isolate::Initialize(v8::Isolate*, v8::Isolate::CreateParams const&) src/api/api.cc:8836:8 (d8+0x5dd97f) (BuildId: 2d1896fcafe8220c)
#12 v8::Isolate::New(v8::Isolate::CreateParams const&) src/api/api.cc:8872:3 (d8+0x5ddc45) (BuildId: 2d1896fcafe8220c)
#13 v8::Shell::Main(int, char**) src/d8/d8.cc:5788:22 (d8+0x58e70c) (BuildId: 2d1896fcafe8220c)
#14 main src/d8/d8.cc:5976:43 (d8+0x58f4ee) (BuildId: 2d1896fcafe8220c)
Thread T6 'V8 DefaultWorke' (tid=24798, running) created by main thread at:
#0 pthread_create /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1022:3 (d8+0x4ddffb) (BuildId: 2d1896fcafe8220c)
#1 v8::base::Thread::Start() src/base/platform/platform-posix.cc:1144:14 (d8+0x1e4bb36) (BuildId: 2d1896fcafe8220c)
#2 WorkerThread src/libplatform/default-worker-threads-task-runner.cc:66:3 (d8+0x1e580ac) (BuildId: 2d1896fcafe8220c)
#3 make_unique<v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread, v8::platform::DefaultWorkerThreadsTaskRunner *> buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:670:30 (d8+0x1e580ac)
#4 v8::platform::DefaultWorkerThreadsTaskRunner::DefaultWorkerThreadsTaskRunner(unsigned int, double (*)()) src/libplatform/default-worker-threads-task-runner.cc:16:28 (d8+0x1e580ac)
#5 __shared_ptr_emplace<const int &, double (*)()> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:297:37 (d8+0x1e4e1b0) (BuildId: 2d1896fcafe8220c)
#6 allocate_shared<v8::platform::DefaultWorkerThreadsTaskRunner, std::Cr::allocator<v8::platform::DefaultWorkerThreadsTaskRunner>, const int &, double (*)(), void> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:956:55 (d8+0x1e4e1b0)
#7 make_shared<v8::platform::DefaultWorkerThreadsTaskRunner, const int &, double (*)(), void> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:965:12 (d8+0x1e4e1b0)
#8 EnsureBackgroundTaskRunnerInitialized src/libplatform/default-platform.cc:132:7 (d8+0x1e4e1b0)
#9 v8::platform::DefaultPlatform::DefaultPlatform(int, v8::platform::IdleTaskSupport, std::Cr::unique_ptr<v8::TracingController, std::Cr::default_delete<v8::TracingController>>) src/libplatform/default-platform.cc:108:5 (d8+0x1e4e1b0)
#10 make_unique<v8::platform::DefaultPlatform, int &, v8::platform::IdleTaskSupport &, std::Cr::unique_ptr<v8::TracingController, std::Cr::default_delete<v8::TracingController> > > buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:670:30 (d8+0x1e4d839) (BuildId: 2d1896fcafe8220c)
#11 v8::platform::NewDefaultPlatform(int, v8::platform::IdleTaskSupport, v8::platform::InProcessStackDumping, std::Cr::unique_ptr<v8::TracingController, std::Cr::default_delete<v8::TracingController>>) src/libplatform/default-platform.cc:53:19 (d8+0x1e4d839)
#12 v8::Shell::Main(int, char**) src/d8/d8.cc:5691:16 (d8+0x58e251) (BuildId: 2d1896fcafe8220c)
#13 main src/d8/d8.cc:5976:43 (d8+0x58f4ee) (BuildId: 2d1896fcafe8220c)
SUMMARY: ThreadSanitizer: data race src/base/flags.h:47:11 in operator|=
==================
ThreadSanitizer: reported 1 warnings

View File

@ -0,0 +1,4 @@
{
"crash_type": "Data race\nWRITE 8",
"crash_state": "ClearCellRangeRelaxed\nClear\nClearMarkBitsAndHandleLivenessStatistics"
}

View File

@ -0,0 +1,118 @@
==================
WARNING: ThreadSanitizer: data race (pid=10132)
Write of size 8 at 0x7e8d00440248 by thread T7 (mutexes: write M0):
#0 memset sanitizer_common/sanitizer_common_interceptors.inc:799:3 (d8+0x49d4ad) (BuildId: c849b9596314cb5d)
#1 ClearCellRangeRelaxed src/heap/marking.h:205:16 (d8+0x94d685) (BuildId: c849b9596314cb5d)
#2 Clear src/heap/marking.h:228:3 (d8+0x94d685)
#3 ClearMarkBitsAndHandleLivenessStatistics src/heap/sweeper.cc:303:33 (d8+0x94d685)
#4 v8::internal::Sweeper::RawSweep(v8::internal::Page*, v8::internal::Sweeper::FreeListRebuildingMode, v8::internal::FreeSpaceTreatmentMode, v8::internal::Sweeper::FreeSpaceMayContainInvalidatedSlots, v8::base::LockGuard<v8::base::Mutex, (v8::base::NullBehavior)0> const&) src/heap/sweeper.cc:412:3 (d8+0x94d685)
#5 v8::internal::Sweeper::ParallelSweepPage(v8::internal::Page*, v8::internal::AllocationSpace, v8::internal::Sweeper::FreeSpaceMayContainInvalidatedSlots) src/heap/sweeper.cc:494:17 (d8+0x94d9e6) (BuildId: c849b9596314cb5d)
#6 ConcurrentSweepSpace src/heap/sweeper.cc:437:5 (d8+0x95056a) (BuildId: c849b9596314cb5d)
#7 v8::internal::Sweeper::SweeperJob::RunImpl(v8::JobDelegate*) src/heap/sweeper.cc:117:22 (d8+0x95056a)
#8 v8::internal::Sweeper::SweeperJob::Run(v8::JobDelegate*) src/heap/sweeper.cc (d8+0x950295) (BuildId: c849b9596314cb5d)
#9 v8::DelayedTasksPlatform::DelayedJob::Run(v8::JobDelegate*) src/d8/d8-platforms.cc:297:18 (d8+0x51a6a8) (BuildId: c849b9596314cb5d)
#10 v8::platform::DefaultJobWorker::Run() src/libplatform/default-job.h:147:18 (d8+0x1a7bec8) (BuildId: c849b9596314cb5d)
#11 v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread::Run() src/libplatform/default-worker-threads-task-runner.cc:73:11 (d8+0x1a836f0) (BuildId: c849b9596314cb5d)
#12 NotifyStartedAndRun src/base/platform/platform.h:560:5 (d8+0x1a77162) (BuildId: c849b9596314cb5d)
#13 v8::base::ThreadEntry(void*) src/base/platform/platform-posix.cc:1051:11 (d8+0x1a77162)
Previous atomic read of size 4 at 0x7e8d00440248 by thread T3:
#0 __cxx_atomic_load<int> buildtools/third_party/libc++/trunk/include/atomic:1000:12 (d8+0x7e5423) (BuildId: c849b9596314cb5d)
#1 load buildtools/third_party/libc++/trunk/include/atomic:1611:17 (d8+0x7e5423)
#2 atomic_load_explicit<int> buildtools/third_party/libc++/trunk/include/atomic:1967:17 (d8+0x7e5423)
#3 Acquire_Load src/base/atomicops.h:240:10 (d8+0x7e5423)
#4 Acquire_Load<unsigned int> src/base/atomic-utils.h:73:9 (d8+0x7e5423)
#5 Get<v8::internal::AccessMode::ATOMIC> src/heap/marking.h:78:11 (d8+0x7e5423)
#6 IsBlackOrGrey<v8::internal::AccessMode::ATOMIC> src/heap/marking.h:398:21 (d8+0x7e5423)
#7 IsBlackOrGrey src/heap/marking-visitor.h:81:12 (d8+0x7e5423)
#8 void v8::internal::MarkingVisitorBase<v8::internal::ConcurrentMarkingVisitor, v8::internal::ConcurrentMarkingState>::ProcessWeakHeapObject<v8::internal::CompressedHeapObjectSlot>(v8::internal::HeapObject, v8::internal::CompressedHeapObjectSlot, v8::internal::HeapObject) src/heap/marking-visitor-inl.h:58:44 (d8+0x7e5423)
#9 VisitPointersImpl<v8::internal::CompressedMaybeObjectSlot> src/heap/marking-visitor-inl.h:90:7 (d8+0x7df885) (BuildId: c849b9596314cb5d)
#10 VisitPointers src/heap/marking-visitor.h:199:5 (d8+0x7df885)
#11 IterateMaybeWeakPointers<v8::internal::ConcurrentMarkingVisitor> src/objects/objects-body-descriptors-inl.h:139:6 (d8+0x7df885)
#12 IterateBody<v8::internal::ConcurrentMarkingVisitor> src/objects/objects-body-descriptors.h:176:5 (d8+0x7df885)
#13 VisitFeedbackVector src/heap/objects-visiting-inl.h:118:1 (d8+0x7df885)
#14 Visit src/heap/objects-visiting-inl.h:65:5 (d8+0x7df885)
#15 v8::internal::ConcurrentMarking::Run(v8::JobDelegate*, v8::base::EnumSet<v8::internal::CodeFlushMode, int>, unsigned int, bool) src/heap/concurrent-marking.cc:531:41 (d8+0x7df885)
#16 v8::internal::ConcurrentMarking::JobTask::Run(v8::JobDelegate*) src/heap/concurrent-marking.cc:420:28 (d8+0x7eb659) (BuildId: c849b9596314cb5d)
#17 v8::DelayedTasksPlatform::DelayedJob::Run(v8::JobDelegate*) src/d8/d8-platforms.cc:297:18 (d8+0x51a6a8) (BuildId: c849b9596314cb5d)
#18 v8::platform::DefaultJobWorker::Run() src/libplatform/default-job.h:147:18 (d8+0x1a7bec8) (BuildId: c849b9596314cb5d)
#19 v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread::Run() src/libplatform/default-worker-threads-task-runner.cc:73:11 (d8+0x1a836f0) (BuildId: c849b9596314cb5d)
#20 NotifyStartedAndRun src/base/platform/platform.h:560:5 (d8+0x1a77162) (BuildId: c849b9596314cb5d)
#21 v8::base::ThreadEntry(void*) src/base/platform/platform-posix.cc:1051:11 (d8+0x1a77162)
As if synchronized via sleep:
#0 usleep /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:358:3 (d8+0x490b4a) (BuildId: c849b9596314cb5d)
#1 v8::base::OS::Sleep(v8::base::TimeDelta) src/base/platform/platform-posix.cc:615:3 (d8+0x1a76295) (BuildId: c849b9596314cb5d)
#2 v8::DelayedTasksPlatform::DelayedJob::Run(v8::JobDelegate*) src/d8/d8-platforms.cc:296:7 (d8+0x51a687) (BuildId: c849b9596314cb5d)
#3 v8::platform::DefaultJobWorker::Run() src/libplatform/default-job.h:147:18 (d8+0x1a7bec8) (BuildId: c849b9596314cb5d)
#4 v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread::Run() src/libplatform/default-worker-threads-task-runner.cc:73:11 (d8+0x1a836f0) (BuildId: c849b9596314cb5d)
#5 NotifyStartedAndRun src/base/platform/platform.h:560:5 (d8+0x1a77162) (BuildId: c849b9596314cb5d)
#6 v8::base::ThreadEntry(void*) src/base/platform/platform-posix.cc:1051:11 (d8+0x1a77162)
Mutex M0 (0x7b0c000091e0) created at:
#0 pthread_mutex_init /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1316:3 (d8+0x494d4f) (BuildId: c849b9596314cb5d)
#1 InitializeNativeHandle src/base/platform/mutex.cc:95:12 (d8+0x1a71ecb) (BuildId: c849b9596314cb5d)
#2 v8::base::Mutex::Mutex() src/base/platform/mutex.cc:148:3 (d8+0x1a71ecb)
#3 v8::internal::MemoryChunk::Initialize(v8::internal::BasicMemoryChunk*, v8::internal::Heap*, v8::internal::Executability) src/heap/memory-chunk.cc:149:23 (d8+0x8fa1dd) (BuildId: c849b9596314cb5d)
#4 AllocateChunk src/heap/memory-allocator.cc:409:7 (d8+0x8f9094) (BuildId: c849b9596314cb5d)
#5 v8::internal::MemoryAllocator::AllocatePage(v8::internal::MemoryAllocator::AllocationMode, unsigned long, v8::internal::Space*, v8::internal::Executability) src/heap/memory-allocator.cc:562:13 (d8+0x8f9094)
#6 AllocatePage src/heap/paged-spaces.cc:322:38 (d8+0x9130dd) (BuildId: c849b9596314cb5d)
#7 v8::internal::PagedSpace::ExpandBackground(unsigned long) src/heap/paged-spaces.cc:338:16 (d8+0x9130dd)
#8 v8::internal::PagedSpace::RawRefillLabBackground(v8::internal::LocalHeap*, unsigned long, unsigned long, v8::internal::AllocationAlignment, v8::internal::AllocationOrigin) src/heap/paged-spaces.cc:631:14 (d8+0x914467) (BuildId: c849b9596314cb5d)
#9 v8::internal::ConcurrentAllocator::EnsureLab(v8::internal::AllocationOrigin) src/heap/concurrent-allocator.cc:135:25 (d8+0x7dc06c) (BuildId: c849b9596314cb5d)
#10 v8::internal::ConcurrentAllocator::AllocateInLabSlow(int, v8::internal::AllocationAlignment, v8::internal::AllocationOrigin) src/heap/concurrent-allocator.cc:124:8 (d8+0x7dbf7b) (BuildId: c849b9596314cb5d)
#11 AllocateInLab src/heap/concurrent-allocator-inl.h:41:16 (d8+0x82d0b1) (BuildId: c849b9596314cb5d)
#12 AllocateRaw src/heap/concurrent-allocator-inl.h:34:10 (d8+0x82d0b1)
#13 AllocateRaw<v8::internal::AllocationType::kSharedMap> src/heap/heap-allocator-inl.h:122:47 (d8+0x82d0b1)
#14 AllocateRaw src/heap/heap-allocator-inl.h:184:14 (d8+0x82d0b1)
#15 v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) src/heap/heap-allocator.cc:75:29 (d8+0x82d0b1)
#16 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) src/heap/heap-allocator.cc:100:7 (d8+0x82e234) (BuildId: c849b9596314cb5d)
#17 AllocateRawWith<v8::internal::HeapAllocator::kRetryOrFail> src/heap/heap-allocator-inl.h:237:16 (d8+0x808920) (BuildId: c849b9596314cb5d)
#18 v8::internal::Factory::NewMap(v8::internal::InstanceType, int, v8::internal::ElementsKind, int, v8::internal::AllocationType) src/heap/factory.cc:1848:36 (d8+0x808920)
#19 v8::internal::Builtin_Impl_SharedStructTypeConstructor(v8::internal::BuiltinArguments, v8::internal::Isolate*) src/builtins/builtins-struct.cc:84:39 (d8+0x65fa26) (BuildId: c849b9596314cb5d)
#20 v8::internal::Builtin_SharedStructTypeConstructor(int, unsigned long*, v8::internal::Isolate*) src/builtins/builtins-struct.cc:20:1 (d8+0x65f061) (BuildId: c849b9596314cb5d)
#21 <null> <null> (0x560ebfe66ef8)
#22 v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSFunction>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>) src/execution/execution.cc:531:10 (d8+0x77252c) (BuildId: c849b9596314cb5d)
#23 v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2105:7 (d8+0x561590) (BuildId: c849b9596314cb5d)
#24 v8::Script::Run(v8::Local<v8::Context>) src/api/api.cc:2048:10 (d8+0x560d20) (BuildId: c849b9596314cb5d)
#25 v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::PrintResult, v8::Shell::ReportExceptions, v8::Shell::ProcessMessageQueue) src/d8/d8.cc:773:28 (d8+0x5211d0) (BuildId: c849b9596314cb5d)
#26 v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:3990:10 (d8+0x53659a) (BuildId: c849b9596314cb5d)
#27 v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:4656:39 (d8+0x53a188) (BuildId: c849b9596314cb5d)
#28 v8::Shell::Main(int, char**) src/d8/d8.cc:5473:18 (d8+0x53cd5b) (BuildId: c849b9596314cb5d)
#29 main src/d8/d8.cc:5553:43 (d8+0x53cf4e) (BuildId: c849b9596314cb5d)
Thread T7 'V8 DefaultWorke' (tid=10149, running) created by main thread at:
#0 pthread_create /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1022:3 (d8+0x49356d) (BuildId: c849b9596314cb5d)
#1 v8::base::Thread::Start() src/base/platform/platform-posix.cc:1083:14 (d8+0x1a77076) (BuildId: c849b9596314cb5d)
#2 WorkerThread src/libplatform/default-worker-threads-task-runner.cc:66:3 (d8+0x1a830f2) (BuildId: c849b9596314cb5d)
#3 make_unique<v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread, v8::platform::DefaultWorkerThreadsTaskRunner *> buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:725:32 (d8+0x1a830f2)
#4 v8::platform::DefaultWorkerThreadsTaskRunner::DefaultWorkerThreadsTaskRunner(unsigned int, double (*)()) src/libplatform/default-worker-threads-task-runner.cc:16:28 (d8+0x1a830f2)
#5 __shared_ptr_emplace<const int &, double (*)()> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:293:37 (d8+0x1a7890d) (BuildId: c849b9596314cb5d)
#6 allocate_shared<v8::platform::DefaultWorkerThreadsTaskRunner, std::__1::allocator<v8::platform::DefaultWorkerThreadsTaskRunner>, const int &, double (*)(), void> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:1101:55 (d8+0x1a7890d)
#7 make_shared<v8::platform::DefaultWorkerThreadsTaskRunner, const int &, double (*)(), void> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:1110:12 (d8+0x1a7890d)
#8 EnsureBackgroundTaskRunnerInitialized src/libplatform/default-platform.cc:133:7 (d8+0x1a7890d)
#9 v8::platform::DefaultPlatform::DefaultPlatform(int, v8::platform::IdleTaskSupport, std::__1::unique_ptr<v8::TracingController, std::__1::default_delete<v8::TracingController> >) src/libplatform/default-platform.cc:109:5 (d8+0x1a7890d)
#10 make_unique<v8::platform::DefaultPlatform, int &, v8::platform::IdleTaskSupport &, std::__1::unique_ptr<v8::TracingController, std::__1::default_delete<v8::TracingController> > > buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:725:32 (d8+0x1a78029) (BuildId: c849b9596314cb5d)
#11 v8::platform::NewDefaultPlatform(int, v8::platform::IdleTaskSupport, v8::platform::InProcessStackDumping, std::__1::unique_ptr<v8::TracingController, std::__1::default_delete<v8::TracingController> >) src/libplatform/default-platform.cc:54:19 (d8+0x1a78029)
#12 v8::Shell::Main(int, char**) src/d8/d8.cc:5270:16 (d8+0x53b887) (BuildId: c849b9596314cb5d)
#13 main src/d8/d8.cc:5553:43 (d8+0x53cf4e) (BuildId: c849b9596314cb5d)
Thread T3 'V8 DefaultWorke' (tid=10144, running) created by main thread at:
#0 pthread_create /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1022:3 (d8+0x49356d) (BuildId: c849b9596314cb5d)
#1 v8::base::Thread::Start() src/base/platform/platform-posix.cc:1083:14 (d8+0x1a77076) (BuildId: c849b9596314cb5d)
#2 WorkerThread src/libplatform/default-worker-threads-task-runner.cc:66:3 (d8+0x1a830f2) (BuildId: c849b9596314cb5d)
#3 make_unique<v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread, v8::platform::DefaultWorkerThreadsTaskRunner *> buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:725:32 (d8+0x1a830f2)
#4 v8::platform::DefaultWorkerThreadsTaskRunner::DefaultWorkerThreadsTaskRunner(unsigned int, double (*)()) src/libplatform/default-worker-threads-task-runner.cc:16:28 (d8+0x1a830f2)
#5 __shared_ptr_emplace<const int &, double (*)()> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:293:37 (d8+0x1a7890d) (BuildId: c849b9596314cb5d)
#6 allocate_shared<v8::platform::DefaultWorkerThreadsTaskRunner, std::__1::allocator<v8::platform::DefaultWorkerThreadsTaskRunner>, const int &, double (*)(), void> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:1101:55 (d8+0x1a7890d)
#7 make_shared<v8::platform::DefaultWorkerThreadsTaskRunner, const int &, double (*)(), void> buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:1110:12 (d8+0x1a7890d)
#8 EnsureBackgroundTaskRunnerInitialized src/libplatform/default-platform.cc:133:7 (d8+0x1a7890d)
#9 v8::platform::DefaultPlatform::DefaultPlatform(int, v8::platform::IdleTaskSupport, std::__1::unique_ptr<v8::TracingController, std::__1::default_delete<v8::TracingController> >) src/libplatform/default-platform.cc:109:5 (d8+0x1a7890d)
#10 make_unique<v8::platform::DefaultPlatform, int &, v8::platform::IdleTaskSupport &, std::__1::unique_ptr<v8::TracingController, std::__1::default_delete<v8::TracingController> > > buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:725:32 (d8+0x1a78029) (BuildId: c849b9596314cb5d)
#11 v8::platform::NewDefaultPlatform(int, v8::platform::IdleTaskSupport, v8::platform::InProcessStackDumping, std::__1::unique_ptr<v8::TracingController, std::__1::default_delete<v8::TracingController> >) src/libplatform/default-platform.cc:54:19 (d8+0x1a78029)
#12 v8::Shell::Main(int, char**) src/d8/d8.cc:5270:16 (d8+0x53b887) (BuildId: c849b9596314cb5d)
#13 main src/d8/d8.cc:5553:43 (d8+0x53cf4e) (BuildId: c849b9596314cb5d)
SUMMARY: ThreadSanitizer: data race src/heap/marking.h:205:16 in ClearCellRangeRelaxed
==================
ThreadSanitizer: reported 1 warnings

View File

@ -0,0 +1,4 @@
{
"crash_type": "Data race\nREAD 8",
"crash_state": "v8::internal::compiler::JSObjectRef::GetOwnConstantElementFromHeap\nv8::internal::compiler::JSObjectRef::GetOwnConstantElement\nv8::internal::compiler::JSNativeContextSpecialization::ReduceElementLoadFromHeap"
}

View File

@ -0,0 +1,50 @@
WARNING: ThreadSanitizer: data race (pid=3187405)
Read of size 8 at 0x7ea200056b80 by thread T1:
#0 0x55ad577064dc in v8::internal::compiler::JSObjectRef::GetOwnConstantElementFromHeap(v8::internal::FixedArrayBase, v8::internal::ElementsKind, unsigned int) const src/base/memory.h:34:3
#1 0x55ad577062aa in v8::internal::compiler::JSObjectRef::GetOwnConstantElement(v8::internal::compiler::FixedArrayBaseRef, unsigned int, v8::internal::compiler::CompilationDependencies*) const src/compiler/heap-refs.cc:1815:42
#2 0x55ad577c4ff7 in v8::internal::compiler::JSNativeContextSpecialization::ReduceElementLoadFromHeapConstant(v8::internal::compiler::Node*, v8::internal::compiler::Node*, v8::internal::compiler::AccessMode, v8::internal::KeyedAccessLoadMode) src/compiler/js-native-context-specialization.cc:2325:32
#3 0x55ad577c2c8f in v8::internal::compiler::JSNativeContextSpecialization::ReduceElementAccess(v8::internal::compiler::Node*, v8::internal::compiler::Node*, v8::internal::compiler::Node*, v8::internal::compiler::ElementAccessFeedback const&) src/compiler/js-native-context-specialization.cc:2094:27
#4 0x55ad577c19f7 in v8::internal::compiler::JSNativeContextSpecialization::ReducePropertyAccess(v8::internal::compiler::Node*, v8::internal::compiler::Node*, v8::base::Optional<v8::internal::compiler::NameRef>, v8::internal::compiler::Node*, v8::internal::compiler::FeedbackSource const&, v8::internal::compiler::AccessMode) src/compiler/js-native-context-specialization.cc:2413:14
#5 0x55ad577b6099 in v8::internal::compiler::JSNativeContextSpecialization::ReduceJSLoadProperty(v8::internal::compiler::Node*) src/compiler/js-native-context-specialization.cc:2564:10
#6 0x55ad577afc8f in v8::internal::compiler::JSNativeContextSpecialization::Reduce(v8::internal::compiler::Node*) src/compiler/js-native-context-specialization.cc:108:14
#7 0x55ad576d605a in v8::internal::compiler::GraphReducer::Reduce(v8::internal::compiler::Node*) src/compiler/graph-reducer.cc:34:25
#8 0x55ad576d59f4 in v8::internal::compiler::GraphReducer::ReduceTop() src/compiler/graph-reducer.cc:178:25
#9 0x55ad576d5209 in v8::internal::compiler::GraphReducer::ReduceNode(v8::internal::compiler::Node*) src/compiler/graph-reducer.cc:75:7
#10 0x55ad576d5f35 in v8::internal::compiler::GraphReducer::ReduceGraph() src/compiler/graph-reducer.cc:97:36
#11 0x55ad57887506 in v8::internal::compiler::InliningPhase::Run(v8::internal::compiler::PipelineData*, v8::internal::Zone*) src/compiler/pipeline.cc:1422:19
#12 0x55ad578763c9 in auto v8::internal::compiler::PipelineImpl::Run<v8::internal::compiler::InliningPhase>() src/compiler/pipeline.cc:1319:16
#13 0x55ad57872135 in v8::internal::compiler::PipelineImpl::CreateGraph() src/compiler/pipeline.cc:2847:3
#14 0x55ad57871f32 in v8::internal::compiler::PipelineCompilationJob::ExecuteJobImpl(v8::internal::RuntimeCallStats*, v8::internal::LocalIsolate*) src/compiler/pipeline.cc:1247:18
#15 0x55ad565a9830 in v8::internal::OptimizedCompilationJob::ExecuteJob(v8::internal::RuntimeCallStats*, v8::internal::LocalIsolate*) src/codegen/compiler.cc:494:22
#16 0x55ad565ef91e in v8::internal::OptimizingCompileDispatcher::CompileNext(v8::internal::TurbofanCompilationJob*, v8::internal::LocalIsolate*) src/compiler-dispatcher/optimizing-compile-dispatcher.cc:105:12
#17 0x55ad565f2555 in v8::internal::OptimizingCompileDispatcher::CompileTask::RunInternal() src/compiler-dispatcher/optimizing-compile-dispatcher.cc:67:20
#18 0x55ad5640b898 in non-virtual thunk to v8::internal::CancelableTask::Run() src/tasks/cancelable-task.h:155:7
#19 0x55ad57cc46b1 in v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread::Run() src/libplatform/default-worker-threads-task-runner.cc:73:11
#20 0x55ad57cb7c23 in v8::base::ThreadEntry(void*) src/base/platform/platform.h:596:5
Previous write of size 8 at 0x7ea200056b80 by main thread:
#0 0x55ad56a49f14 in v8::internal::(anonymous namespace)::DictionaryElementsAccessor::SetLengthImpl(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSArray>, unsigned int, v8::internal::Handle<v8::internal::FixedArrayBase>) src/base/memory.h:41:3
#1 0x55ad56a675ea in v8::internal::(anonymous namespace)::ElementsAccessorBase<v8::internal::(anonymous namespace)::DictionaryElementsAccessor, v8::internal::(anonymous namespace)::ElementsKindTraits<(v8::internal::ElementsKind)13> >::SetLength(v8::internal::Handle<v8::internal::JSArray>, unsigned int) src/objects/elements.cc:758:12
#2 0x55ad56c5f9c0 in v8::internal::JSArray::SetLength(v8::internal::Handle<v8::internal::JSArray>, unsigned int) src/objects/objects.cc:5237:40
#3 0x55ad564a6616 in v8::internal::Accessors::ArrayLengthSetter(v8::Local<v8::Name>, v8::Local<v8::Value>, v8::PropertyCallbackInfo<v8::Boolean> const&) src/builtins/accessors.cc:202:7
#4 0x55ad568e961b in v8::internal::PropertyCallbackArguments::CallAccessorSetter(v8::internal::Handle<v8::internal::AccessorInfo>, v8::internal::Handle<v8::internal::Name>, v8::internal::Handle<v8::internal::Object>) src/api/api-arguments-inl.h:332:3
#5 0x55ad568e39df in v8::internal::Runtime_StoreCallbackProperty(int, unsigned long*, v8::internal::Isolate*) src/ic/ic.cc:3245:13
#6 0x55acdfe69bb8 in Builtins_AsyncFromSyncIteratorPrototypeThrow (/mnt/scratch0/clusterfuzz/bot/builds/v8-tsan_linux-release_8681dd6e7b8d9ebe281ad96b26057d51e550c14e/revisions/d8-tsan-linux-release-v8-component-83502/d8+0x1c30bb7)
#7 0x55ad5668cf86 in v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSFunction>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>) src/execution/execution.cc:538:10
#8 0x55ad5642808f in v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2231:7
#9 0x55ad564277c1 in v8::Script::Run(v8::Local<v8::Context>) src/api/api.cc:2158:10
#10 0x55ad563e63cc in v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::PrintResult, v8::Shell::ReportExceptions, v8::Shell::ProcessMessageQueue) src/d8/d8.cc:879:28
#11 0x55ad563fe621 in v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:4379:10
#12 0x55ad56402488 in v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:5107:39
#13 0x55ad56404fe1 in v8::Shell::Main(int, char**) src/d8/d8.cc:5886:18
#14 0x55ad564054ef in main src/d8/d8.cc:5976:43
Thread T1 'V8 DefaultWorke' (tid=3187415, running) created by main thread at:
#0 0x55ad56353ffc in pthread_create third_party/llvm/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1022:3
#1 0x55ad57cb7b37 in v8::base::Thread::Start() src/base/platform/platform-posix.cc:1144:14
#2 0x55ad57cc40ad in v8::platform::DefaultWorkerThreadsTaskRunner::DefaultWorkerThreadsTaskRunner(unsigned int, double (*)()) src/libplatform/default-worker-threads-task-runner.cc:66:3
#3 0x55ad57cba1b1 in v8::platform::DefaultPlatform::DefaultPlatform(int, v8::platform::IdleTaskSupport, std::Cr::unique_ptr<v8::TracingController, std::Cr::default_delete<v8::TracingController> >) buildtools/third_party/libc++/trunk/include/__memory/shared_ptr.h:297:37
#4 0x55ad57cb983a in v8::platform::NewDefaultPlatform(int, v8::platform::IdleTaskSupport, v8::platform::InProcessStackDumping, std::Cr::unique_ptr<v8::TracingController, std::Cr::default_delete<v8::TracingController> >) buildtools/third_party/libc++/trunk/include/__memory/unique_ptr.h:670:30
#5 0x55ad56404252 in v8::Shell::Main(int, char**) src/d8/d8.cc:5691:16
#6 0x55ad564054ef in main src/d8/d8.cc:5976:43
SUMMARY: ThreadSanitizer: data race src/base/memory.h:34:3 in v8::internal::compiler::JSObjectRef::GetOwnConstantElementFromHeap(v8::internal::FixedArrayBase, v8::internal::ElementsKind, unsigned int) const
==================
ThreadSanitizer: reported 1 warnings

View File

@ -0,0 +1,4 @@
{
"crash_type": "",
"crash_state": ""
}

View File

@ -0,0 +1,43 @@
<--- Last few GCs --->
<--- JS stacktrace --->
#
# Fatal javascript OOM in GC during deserialization
#
Received signal 6
==== C stack trace ===============================
[0x559eb24c1167]
[0x7f27da846980]
[0x7f27d9ecbfb7]
[0x7f27d9ecd921]
[0x559eb24bec92]
[0x559eb192b71f]
[0x559eb192b685]
[0x559eb1abd785]
[0x559eb1abc125]
[0x559eb1ac6bfb]
[0x559eb1ac6c91]
[0x559eb1e5f0c8]
[0x559eb1e604a0]
[0x559eb1e5f99e]
[0x559eb1ac53c8]
[0x559eb1e6dbf8]
[0x559eb1a5eb21]
[0x559eb1a5ef79]
[0x559eb1e6b9a9]
[0x559eb194c069]
[0x559eb194c23d]
[0x559eb1919e3a]
[0x559eb1919da8]
[0x559eb24bf710]
[0x7f27da83b6db]
[0x7f27d9fae71f]
[end of stack trace]
Aborted (core dumped)

View File

@ -0,0 +1,4 @@
{
"crash_type": "",
"crash_state": ""
}

View File

@ -0,0 +1,48 @@
<--- Last few GCs --->
<--- JS stacktrace --->
#
# Fatal javascript OOM in GC during deserialization
#
Received signal 6
==== C stack trace ===============================
[0x7f31c97776b7]
[0x7f31c631b980]
[0x7f31c5bb8e87]
[0x7f31c5bba7f1]
[0x7f31c9775332]
[0x7f31c7c8daf5]
[0x7f31c7c8d97d]
[0x7f31c81a33a5]
[0x7f31c81a15ed]
[0x7f31c81914df]
[0x7f31c81915c1]
[0x7f31c8134c33]
[0x7f31c8adbc18]
[0x7f31c8ad7af1]
[0x7f31c8ada0d5]
[0x7f31c8ad7fdd]
[0x7f31c8adb0be]
[0x7f31c8ad9c46]
[0x7f31c81b4499]
[0x7f31c81adcc9]
[0x7f31c8b16d0e]
[0x7f31c809ed03]
[0x7f31c809fcb1]
[0x7f31c8b114b7]
[0x7f31c7d02526]
[0x7f31c7d02b2d]
[0x565117ee00ed]
[0x565117ee0008]
[0x7f31c9776470]
[0x7f31c63106db]
[0x7f31c5c9b61f]
[end of stack trace]
Aborted (core dumped)

View File

@ -0,0 +1,4 @@
{
"crash_type": "DCHECK failure",
"crash_state": "!has_optimized_code() || optimized_code().marked_for_deoptimization() || (CodeKi"
}

Some files were not shown because too many files have changed in this diff Show More