1# Copyright 2016 the V8 project authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5# Fork from commands.py and output.py in v8 test driver.
6
7import os
8import signal
9import subprocess
10import sys
11from threading import Event, Timer
12
13import v8_fuzz_config
14
15PYTHON3 = sys.version_info >= (3, 0)
16
17# List of default flags passed to each d8 run.
18DEFAULT_FLAGS = [
19    '--correctness-fuzzer-suppressions',
20    '--expose-gc',
21    '--fuzzing',
22    '--allow-natives-for-differential-fuzzing',
23    '--invoke-weak-callbacks',
24    '--omit-quit',
25    '--harmony',
26    '--wasm-staging',
27    '--no-wasm-async-compilation',
28    '--suppress-asm-messages',
29]
30
31BASE_PATH = os.path.dirname(os.path.abspath(__file__))
32
33# List of files passed to each d8 run before the testcase.
34DEFAULT_MOCK = os.path.join(BASE_PATH, 'v8_mock.js')
35
36# Suppressions on JavaScript level for known issues.
37JS_SUPPRESSIONS = os.path.join(BASE_PATH, 'v8_suppressions.js')
38
39# Config-specific mock files.
40ARCH_MOCKS = os.path.join(BASE_PATH, 'v8_mock_archs.js')
41WEBASSEMBLY_MOCKS = os.path.join(BASE_PATH, 'v8_mock_webassembly.js')
42
43
44def _startup_files(options):
45  """Default files and optional config-specific mock files."""
46  files = [DEFAULT_MOCK]
47  if not options.skip_suppressions:
48    files.append(JS_SUPPRESSIONS)
49  if options.first.arch != options.second.arch:
50    files.append(ARCH_MOCKS)
51  # Mock out WebAssembly when comparing with jitless mode.
52  if '--jitless' in options.first.flags + options.second.flags:
53    files.append(WEBASSEMBLY_MOCKS)
54  return files
55
56
57class BaseException(Exception):
58  """Used to abort the comparison workflow and print the given message."""
59  def __init__(self, message):
60    self.message = message
61
62
63class PassException(BaseException):
64  """Represents an early abort making the overall run pass."""
65  pass
66
67
68class FailException(BaseException):
69  """Represents an early abort making the overall run fail."""
70  pass
71
72
73class Command(object):
74  """Represents a configuration for running V8 multiple times with certain
75  flags and files.
76  """
77  def __init__(self, options, label, executable, config_flags):
78    self.label = label
79    self.executable = executable
80    self.config_flags = config_flags
81    self.common_flags =  DEFAULT_FLAGS[:]
82    self.common_flags.extend(['--random-seed', str(options.random_seed)])
83
84    self.files = _startup_files(options)
85
86  def run(self, testcase, timeout, verbose=False):
87    """Run the executable with a specific testcase."""
88    args = [self.executable] + self.flags + self.files + [testcase]
89    if verbose:
90      print('# Command line for %s comparison:' % self.label)
91      print(' '.join(args))
92    if self.executable.endswith('.py'):
93      # Wrap with python in tests.
94      args = [sys.executable] + args
95    return Execute(
96        args,
97        cwd=os.path.dirname(os.path.abspath(testcase)),
98        timeout=timeout,
99    )
100
101  @property
102  def flags(self):
103    return self.common_flags + self.config_flags
104
105
106class Output(object):
107  def __init__(self, exit_code, stdout, pid):
108    self.exit_code = exit_code
109    self.stdout = stdout
110    self.pid = pid
111
112  def HasCrashed(self):
113    return self.exit_code < 0
114
115
116def Execute(args, cwd, timeout=None):
117  popen_args = [c for c in args if c != ""]
118  kwargs = {}
119  if PYTHON3:
120    kwargs['encoding'] = 'utf-8'
121  try:
122    process = subprocess.Popen(
123      args=popen_args,
124      stdout=subprocess.PIPE,
125      stderr=subprocess.PIPE,
126      cwd=cwd,
127      **kwargs
128    )
129  except Exception as e:
130    sys.stderr.write("Error executing: %s\n" % popen_args)
131    raise e
132
133  timeout_event = Event()
134
135  def kill_process():
136    timeout_event.set()
137    try:
138      process.kill()
139    except OSError:
140      sys.stderr.write('Error: Process %s already ended.\n' % process.pid)
141
142  timer = Timer(timeout, kill_process)
143  timer.start()
144  stdout, _ = process.communicate()
145  timer.cancel()
146
147  if timeout_event.is_set():
148    raise PassException('# V8 correctness - T-I-M-E-O-U-T')
149
150  return Output(
151      process.returncode,
152      stdout,
153      process.pid,
154  )
155