1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
# 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.
# Fork from commands.py and output.py in v8 test driver.
import signal
import subprocess
import sys
from pathlib import Path
from threading import Event, Timer
# List of default flags passed to each d8 run.
DEFAULT_FLAGS = [
'--correctness-fuzzer-suppressions',
'--expose-gc',
'--fuzzing',
'--allow-natives-for-differential-fuzzing',
'--invoke-weak-callbacks',
'--omit-quit',
'--harmony',
'--experimental-fuzzing',
'--js-staging',
'--wasm-staging',
'--no-wasm-async-compilation',
# Limit wasm memory to just below 2GiB, to avoid differences between 32-bit
# and 64-bit builds.
'--wasm-max-mem-pages=32767',
'--suppress-asm-messages',
]
BASE_PATH = Path(__file__).parent.resolve()
# List of files passed to each d8 run before the testcase.
DEFAULT_MOCK = BASE_PATH / 'v8_mock.js'
SMOKE_TESTS = BASE_PATH / 'v8_smoke_tests.js'
# Suppressions on JavaScript level for known issues.
JS_SUPPRESSIONS = BASE_PATH / 'v8_suppressions.js'
# Config-specific mock files.
ARCH_MOCKS = BASE_PATH / 'v8_mock_archs.js'
WEBASSEMBLY_MOCKS = BASE_PATH / 'v8_mock_webassembly.js'
# Non-standard exit code only used to simulate crashes in tests.
CRASH_CODE_FOR_TESTING = 73
def _startup_files(options):
"""Default files and optional config-specific mock files."""
files = [DEFAULT_MOCK]
if not options.skip_suppressions:
files.append(JS_SUPPRESSIONS)
if options.first.arch != options.second.arch:
files.append(ARCH_MOCKS)
# Mock out WebAssembly when comparing with jitless mode.
if '--jitless' in options.first.flags + options.second.flags:
files.append(WEBASSEMBLY_MOCKS)
# Keep the smoke tests last, right before the actual test case.
return files + [SMOKE_TESTS]
class BaseException(Exception):
"""Used to abort the comparison workflow and print the given message."""
def __init__(self, message):
self.message = message
class PassException(BaseException):
"""Represents an early abort making the overall run pass."""
pass
class FailException(BaseException):
"""Represents an early abort making the overall run fail."""
pass
class Command(object):
"""Represents a configuration for running V8 multiple times with certain
flags and files.
"""
def __init__(self, options, label, executable, config_flags):
self.label = label
self.executable = executable
self.config_flags = config_flags
self.common_flags = DEFAULT_FLAGS[:]
self.common_flags.extend(['--random-seed', str(options.random_seed)])
self.files = _startup_files(options)
def run(self, testcase, timeout):
"""Run the executable with a specific testcase."""
raw_args = [self.executable] + self.flags + self.files + [testcase]
args = list(map(str, raw_args))
print(f'# Command line for {self.label} comparison:')
print(' '.join(args))
if self.executable.suffix == '.py':
# Wrap with python in tests.
args = [sys.executable] + args
return Execute(
args,
cwd=testcase.parent,
timeout=timeout,
)
@property
def flags(self):
return self.common_flags + self.config_flags
def remove_config_flag(self, remove_flag):
"""Removes all occurences of `remove_flag` from the config flags."""
self.config_flags = [
flag for flag in self.config_flags if flag != remove_flag
]
class Output(object):
def __init__(self, exit_code, stdout_bytes, pid):
self.exit_code = exit_code
self.stdout_bytes = stdout_bytes
self.pid = pid
@property
def stdout(self):
try:
return self.stdout_bytes.decode('utf-8')
except UnicodeDecodeError:
return self.stdout_bytes.decode('latin-1')
def HasCrashed(self):
return self.exit_code < 0 or self.exit_code == CRASH_CODE_FOR_TESTING
def Execute(args, cwd, timeout=None):
popen_args = [c for c in args if c != ""]
try:
process = subprocess.Popen(
args=popen_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd,
)
except Exception as e:
sys.stderr.write(f'Error executing: {popen_args}\n')
raise e
timeout_event = Event()
def kill_process():
timeout_event.set()
try:
process.kill()
except OSError:
sys.stderr.write(f'Error: Process {process.pid} already ended.\n')
timer = Timer(timeout, kill_process)
timer.start()
stdout_bytes, _ = process.communicate()
timer.cancel()
if timeout_event.is_set():
raise PassException('# V8 correctness - T-I-M-E-O-U-T')
return Output(
process.returncode,
stdout_bytes,
process.pid,
)
|