Index: tools/foozzie/v8_foozzie.py |
diff --git a/tools/foozzie/v8_foozzie.py b/tools/foozzie/v8_foozzie.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..5a4dacb76fbe8db8b9c784ed615a9be857082b5d |
--- /dev/null |
+++ b/tools/foozzie/v8_foozzie.py |
@@ -0,0 +1,257 @@ |
+#!/usr/bin/env python |
+# 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. |
+ |
+""" |
+V8 correctness fuzzer launcher script. |
+""" |
+ |
+import argparse |
+import itertools |
+import os |
+import re |
+import sys |
+import traceback |
+ |
+import v8_commands |
+import v8_suppressions |
+ |
+CONFIGS = dict( |
+ default=[], |
+ validate_asm=['--validate-asm'], # Maybe add , '--disable-asm-warnings' |
+ fullcode=['--nocrankshaft', '--turbo-filter=~'], |
+ noturbo=['--turbo-filter=~', '--noturbo-asm'], |
+ noturbo_opt=['--always-opt', '--turbo-filter=~', '--noturbo-asm'], |
+ ignition_staging=['--ignition-staging'], |
+ ignition_turbo=['--ignition-staging', '--turbo'], |
+ ignition_turbo_opt=['--ignition-staging', '--turbo', '--always-opt'], |
+) |
+ |
+# Timeout in seconds for one d8 run. |
+TIMEOUT = 3 |
+ |
+# Return codes. |
+RETURN_PASS = 0 |
+RETURN_FAILURE = 2 |
+ |
+BASE_PATH = os.path.dirname(os.path.abspath(__file__)) |
+PREAMBLE = [ |
+ os.path.join(BASE_PATH, 'v8_mock.js'), |
+ os.path.join(BASE_PATH, 'v8_suppressions.js'), |
+] |
+ |
+FLAGS = ['--abort_on_stack_overflow', '--expose-gc', '--allow-natives-syntax', |
+ '--invoke-weak-callbacks', '--omit-quit', '--es-staging'] |
+ |
+SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64'] |
+ |
+# Output for suppressed failure case. |
+FAILURE_HEADER_TEMPLATE = """ |
+# |
+# V8 correctness failure |
+# V8 correctness configs: %(configs)s |
+# V8 correctness sources: %(sources)s |
+# V8 correctness suppression: %(suppression)s""".strip() |
+ |
+# Extended output for failure case. The 'CHECK' is for the minimizer. |
+FAILURE_TEMPLATE = FAILURE_HEADER_TEMPLATE + """ |
+# |
+# CHECK |
+# |
+# Compared %(first_config_label)s with %(second_config_label)s |
+# |
+# Flags of %(first_config_label)s: |
+%(first_config_flags)s |
+# Flags of %(second_config_label)s: |
+%(second_config_flags)s |
+# |
+# Difference: |
+%(difference)s |
+# |
+### Start of configuration %(first_config_label)s: |
+%(first_config_output)s |
+### End of configuration %(first_config_label)s |
+# |
+### Start of configuration %(second_config_label)s: |
+%(second_config_output)s |
+### End of configuration %(second_config_label)s |
+""".strip() |
+ |
+ |
+def parse_args(): |
+ parser = argparse.ArgumentParser() |
+ parser.add_argument( |
+ '--random-seed', type=int, required=True, |
+ help='random seed passed to both runs') |
+ parser.add_argument( |
+ '--first-arch', help='first architecture', default='x64') |
+ parser.add_argument( |
+ '--second-arch', help='second architecture', default='x64') |
+ parser.add_argument( |
+ '--first-config', help='first configuration', default='fullcode') |
+ parser.add_argument( |
+ '--second-config', help='second configuration', default='fullcode') |
+ parser.add_argument( |
+ '--first-d8', default='d8', |
+ help='optional path to first d8 executable, ' |
+ 'default: bundled in the same directory as this script') |
+ parser.add_argument( |
+ '--second-d8', |
+ help='optional path to second d8 executable, default: same as first') |
+ parser.add_argument('testcase', help='path to test case') |
+ options = parser.parse_args() |
+ |
+ # Ensure we make a sane comparison. |
+ assert (options.first_arch != options.second_arch or |
+ options.first_config != options.second_config) , ( |
+ 'Need either arch or config difference.') |
+ assert options.first_arch in SUPPORTED_ARCHS |
+ assert options.second_arch in SUPPORTED_ARCHS |
+ assert options.first_config in CONFIGS |
+ assert options.second_config in CONFIGS |
+ |
+ # Ensure we have a test case. |
+ assert (os.path.exists(options.testcase) and |
+ os.path.isfile(options.testcase)), ( |
+ 'Test case %s doesn\'t exist' % options.testcase) |
+ |
+ # Use first d8 as default for second d8. |
+ options.second_d8 = options.second_d8 or options.first_d8 |
+ |
+ # Ensure absolute paths. |
+ options.first_d8 = os.path.abspath(options.first_d8) |
+ options.second_d8 = os.path.abspath(options.second_d8) |
+ |
+ # Ensure executables exist. |
+ assert os.path.exists(options.first_d8) |
+ assert os.path.exists(options.second_d8) |
+ |
+ # Ensure we use different executables when we claim we compare |
+ # different architectures. |
+ # TODO(machenbach): Infer arch from gn's build output. |
+ if options.first_arch != options.second_arch: |
+ assert options.first_d8 != options.second_d8 |
+ |
+ return options |
+ |
+ |
+def test_pattern_bailout(testcase, ignore_fun): |
+ """Print failure state and return if ignore_fun matches testcase.""" |
+ with open(testcase) as f: |
+ bug = (ignore_fun(f.read()) or '').strip() |
+ if bug: |
+ print FAILURE_HEADER_TEMPLATE % dict( |
+ configs='', sources='', suppression=bug) |
+ return True |
+ return False |
+ |
+ |
+def pass_bailout(output, step_number): |
+ """Print info and return if in timeout or crash pass states.""" |
+ if output.HasTimedOut(): |
+ # Dashed output, so that no other clusterfuzz tools can match the |
+ # words timeout or crash. |
+ print '# V8 correctness - T-I-M-E-O-U-T %d' % step_number |
+ return True |
+ if output.HasCrashed(): |
+ print '# V8 correctness - C-R-A-S-H %d' % step_number |
+ return True |
+ return False |
+ |
+ |
+def fail_bailout(output, ignore_by_output_fun): |
+ """Print failure state and return if ignore_by_output_fun matches output.""" |
+ bug = (ignore_by_output_fun(output.stdout) or '').strip() |
+ if bug: |
+ print FAILURE_HEADER_TEMPLATE % dict( |
+ configs='', sources='', suppression=bug) |
+ return True |
+ return False |
+ |
+ |
+def main(): |
+ options = parse_args() |
+ |
+ # Suppressions are architecture and configuration specific. |
+ suppress = v8_suppressions.get_suppression( |
+ options.first_arch, options.first_config, |
+ options.second_arch, options.second_config, |
+ ) |
+ |
+ if test_pattern_bailout(options.testcase, suppress.ignore): |
+ return RETURN_FAILURE |
+ |
+ common_flags = FLAGS + ['--random-seed', str(options.random_seed)] |
+ first_config_flags = common_flags + CONFIGS[options.first_config] |
+ second_config_flags = common_flags + CONFIGS[options.second_config] |
+ |
+ def run_d8(d8, config_flags): |
+ return v8_commands.Execute( |
+ [d8] + config_flags + PREAMBLE + [options.testcase], |
+ cwd=os.path.dirname(options.testcase), |
+ timeout=TIMEOUT, |
+ ) |
+ |
+ first_config_output = run_d8(options.first_d8, first_config_flags) |
+ |
+ # Early bailout based on first run's output. |
+ if pass_bailout(first_config_output, 1): |
+ return RETURN_PASS |
+ if fail_bailout(first_config_output, suppress.ignore_by_output1): |
+ return RETURN_FAILURE |
+ |
+ second_config_output = run_d8(options.second_d8, second_config_flags) |
+ |
+ # Bailout based on second run's output. |
+ if pass_bailout(second_config_output, 2): |
+ return RETURN_PASS |
+ if fail_bailout(second_config_output, suppress.ignore_by_output2): |
+ return RETURN_FAILURE |
+ |
+ difference = suppress.diff( |
+ first_config_output.stdout, second_config_output.stdout) |
+ if difference: |
+ # The first three entries will be parsed by clusterfuzz. Format changes |
+ # will require changes on the clusterfuzz side. |
+ first_config_label = '%s,%s' % (options.first_arch, options.first_config) |
+ second_config_label = '%s,%s' % (options.second_arch, options.second_config) |
+ print FAILURE_TEMPLATE % dict( |
+ configs='%s:%s' % (first_config_label, second_config_label), |
+ sources='', # TODO |
+ suppression='', # We can't tie bugs to differences. |
+ first_config_label=first_config_label, |
+ second_config_label=second_config_label, |
+ first_config_flags=' '.join(first_config_flags), |
+ second_config_flags=' '.join(second_config_flags), |
+ first_config_output=first_config_output.stdout, |
+ second_config_output=second_config_output.stdout, |
+ difference=difference, |
+ ) |
+ return RETURN_FAILURE |
+ |
+ # TODO(machenbach): Figure out if we could also return a bug in case there's |
+ # no difference, but one of the line suppressions has matched - and without |
+ # the match there would be a difference. |
+ |
+ print '# V8 correctness - pass' |
+ return RETURN_PASS |
+ |
+ |
+if __name__ == "__main__": |
+ try: |
+ result = main() |
+ except SystemExit: |
+ # Make sure clusterfuzz reports internal errors and wrong usage. |
+ # Use one label for all internal and usage errors. |
+ print FAILURE_HEADER_TEMPLATE % dict( |
+ configs='', sources='', suppression='wrong_usage') |
+ result = RETURN_FAILURE |
+ except Exception as e: |
+ print FAILURE_HEADER_TEMPLATE % dict( |
+ configs='', sources='', suppression='internal_error') |
+ print '# Internal error: %s' % e |
+ traceback.print_exc(file=sys.stdout) |
+ result = RETURN_FAILURE |
+ |
+ sys.exit(result) |