Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2016 the V8 project authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """ | |
| 7 V8 correctness fuzzer launcher script. | |
| 8 """ | |
| 9 | |
| 10 import argparse | |
| 11 import itertools | |
| 12 import os | |
| 13 import re | |
| 14 import sys | |
| 15 import traceback | |
| 16 | |
| 17 import v8_commands | |
| 18 import v8_suppressions | |
| 19 | |
| 20 CONFIGS = { | |
| 21 'default': [], | |
| 22 'validate_asm': ['--validate-asm'], # Maybe add , '--disable-asm-warnings' | |
| 23 'fullcode': ['--nocrankshaft', '--turbo-filter=~'], | |
| 24 'noturbo': ['--turbo-filter=~', '--noturbo-asm'], | |
| 25 'noturbo_opt': ['--always-opt', '--turbo-filter=~', '--noturbo-asm'], | |
| 26 'ignition_staging': ['--ignition-staging'], | |
| 27 'ignition_turbo': ['--ignition-staging', '--turbo'], | |
| 28 'ignition_turbo_opt': ['--ignition-staging', '--turbo', '--always-opt'], | |
| 29 } | |
| 30 | |
| 31 # Timeout in seconds for one d8 run. | |
| 32 TIMEOUT = 3 | |
| 33 | |
| 34 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) | |
| 35 PREAMBLE = [ | |
| 36 os.path.join(BASE_PATH, 'v8_mock.js'), | |
| 37 os.path.join(BASE_PATH, 'v8_suppressions.js'), | |
| 38 ] | |
| 39 | |
| 40 FLAGS = ['--abort_on_stack_overflow', '--expose-gc', '--allow-natives-syntax', | |
| 41 '--invoke-weak-callbacks', '--omit-quit', '--es-staging'] | |
| 42 | |
| 43 SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64'] | |
| 44 | |
| 45 # Output for suppressed failure case. | |
| 46 FAILURE_HEADER_TEMPLATE = """# | |
|
tandrii(chromium)
2016/12/16 16:46:26
Nit: instead of """# i'd do:
"""
#
....
""".strip
Michael Achenbach
2016/12/19 08:42:45
Done.
| |
| 47 # V8 correctness failure | |
| 48 # V8 correctness configs: %(configs)s | |
| 49 # V8 correctness sources: %(sources)s | |
| 50 # V8 correctness suppression: %(suppression)s""" | |
| 51 | |
| 52 # Extended output for failure case. The 'CHECK' is for the minimizer. | |
| 53 FAILURE_TEMPLATE = FAILURE_HEADER_TEMPLATE + """# | |
| 54 # CHECK | |
| 55 # | |
| 56 # Compared %(first_config_label)s with %(second_config_label)s | |
| 57 # | |
| 58 # Flags of %(first_config_label)s: | |
| 59 %(first_config_flags)s | |
| 60 # Flags of %(second_config_label)s: | |
| 61 %(second_config_flags)s | |
| 62 # | |
| 63 # Difference: | |
| 64 %(difference)s | |
| 65 # | |
| 66 ### Start of configuration %(first_config_label)s: | |
| 67 %(first_config_output)s | |
| 68 ### End of configuration %(first_config_label)s | |
| 69 # | |
| 70 ### Start of configuration %(second_config_label)s: | |
| 71 %(second_config_output)s | |
| 72 ### End of configuration %(second_config_label)s | |
| 73 """ | |
| 74 | |
| 75 | |
| 76 def parse_args(): | |
| 77 parser = argparse.ArgumentParser() | |
| 78 parser.add_argument( | |
| 79 '--random-seed', type=int, required=True, | |
| 80 help='random seed passed to both runs') | |
| 81 parser.add_argument( | |
| 82 '--first-arch', help='first architecture', default='x64') | |
| 83 parser.add_argument( | |
| 84 '--second-arch', help='second architecture', default='x64') | |
| 85 parser.add_argument( | |
| 86 '--first-config', help='first configuration', default='fullcode') | |
| 87 parser.add_argument( | |
| 88 '--second-config', help='second configuration', default='fullcode') | |
| 89 parser.add_argument( | |
| 90 '--first-d8', default='d8', | |
| 91 help='optional path to first d8 executable, default: side-by-side') | |
|
tandrii(chromium)
2016/12/16 16:46:26
what does "side-by-side" mean?
Michael Achenbach
2016/12/19 08:42:45
Clarified.
| |
| 92 parser.add_argument( | |
| 93 '--second-d8', | |
| 94 help='optional path to second d8 executable, default: same as first') | |
| 95 parser.add_argument('testcase', help='path to test case') | |
| 96 options = parser.parse_args() | |
| 97 | |
| 98 # Ensure we make a sane comparison. | |
| 99 assert (options.first_arch != options.second_arch or | |
| 100 options.first_config != options.second_config) , ( | |
| 101 'Need either arch or config difference.') | |
| 102 assert options.first_arch in SUPPORTED_ARCHS | |
| 103 assert options.second_arch in SUPPORTED_ARCHS | |
| 104 assert options.first_config in CONFIGS | |
| 105 assert options.second_config in CONFIGS | |
| 106 | |
| 107 # Ensure we have a test case. | |
| 108 assert (os.path.exists(options.testcase) and | |
|
tandrii(chromium)
2016/12/16 16:46:26
if x:
parser.error()
is nicer, but assert is cer
Michael Achenbach
2016/12/19 08:42:45
The script is designed to be called from automated
| |
| 109 os.path.isfile(options.testcase)), ( | |
| 110 'Test case %s doesn\'t exist' % options.testcase) | |
| 111 | |
| 112 # Use first d8 as default for second d8. | |
| 113 options.second_d8 = options.second_d8 or options.first_d8 | |
| 114 | |
| 115 # Ensure absolute paths. | |
| 116 options.first_d8 = os.path.abspath(options.first_d8) | |
| 117 options.second_d8 = os.path.abspath(options.second_d8) | |
| 118 | |
| 119 # Ensure executables exist. | |
| 120 assert os.path.exists(options.first_d8) | |
| 121 assert os.path.exists(options.second_d8) | |
| 122 | |
| 123 # Ensure we use different executables when we claim we compare | |
| 124 # different architectures. | |
| 125 # TODO(machenbach): Infer arch from gn's build output. | |
| 126 if options.first_arch != options.second_arch: | |
| 127 assert options.first_d8 != options.second_d8 | |
| 128 | |
| 129 return options | |
| 130 | |
| 131 | |
| 132 def test_pattern_bailout(testcase, ignore_fun): | |
| 133 """Print failure state and return if ignore_fun matches testcase.""" | |
| 134 with open(testcase) as f: | |
| 135 bug = (ignore_fun(f.read()) or '').strip() | |
| 136 if bug: | |
| 137 print FAILURE_HEADER_TEMPLATE % { | |
| 138 'configs': '', | |
| 139 'sources': '', | |
| 140 'suppression': bug, | |
| 141 } | |
| 142 return True | |
| 143 return False | |
| 144 | |
| 145 | |
| 146 def pass_bailout(output, step_number): | |
| 147 """Print info and return if in timeout or crash pass states.""" | |
| 148 if output.HasTimedOut(): | |
| 149 # Dashed output, so that no other clusterfuzz tools can match the | |
| 150 # words timeout or crash. | |
| 151 print '# V8 correctness - T-I-M-E-O-U-T %d' % step_number | |
| 152 return True | |
| 153 if output.HasCrashed(): | |
| 154 print '# V8 correctness - C-R-A-S-H %d' % step_number | |
| 155 return True | |
| 156 return False | |
| 157 | |
| 158 | |
| 159 def fail_bailout(output, ignore_by_output_fun): | |
| 160 """Print failure state and return if ignore_by_output_fun matches output.""" | |
| 161 bug = (ignore_by_output_fun(output.stdout) or '').strip() | |
| 162 if bug: | |
| 163 print FAILURE_HEADER_TEMPLATE % { | |
| 164 'configs': '', | |
| 165 'sources': '', | |
| 166 'suppression': bug, | |
| 167 } | |
| 168 return True | |
| 169 return False | |
| 170 | |
| 171 | |
| 172 def main(): | |
| 173 options = parse_args() | |
| 174 | |
| 175 # Suppressions are architecture and configuration specific. | |
| 176 suppress = v8_suppressions.get_suppression( | |
| 177 options.first_arch, options.first_config, | |
| 178 options.second_arch, options.second_config, | |
| 179 ) | |
| 180 | |
| 181 if test_pattern_bailout(options.testcase, suppress.ignore): | |
| 182 return 2 | |
|
tandrii(chromium)
2016/12/16 16:46:26
i'd make 2 a meaningful constant, perhaps 0 too.
Michael Achenbach
2016/12/19 08:42:45
Done.
| |
| 183 | |
| 184 common_flags = FLAGS + ['--random-seed', str(options.random_seed)] | |
| 185 first_config_flags = common_flags + CONFIGS[options.first_config] | |
| 186 second_config_flags = common_flags + CONFIGS[options.second_config] | |
| 187 | |
| 188 def run_d8(d8, config_flags): | |
| 189 return v8_commands.Execute( | |
| 190 [d8] + config_flags + PREAMBLE + [options.testcase], | |
| 191 cwd=os.path.dirname(options.testcase), | |
| 192 timeout=TIMEOUT, | |
| 193 ) | |
| 194 | |
| 195 first_config_output = run_d8(options.first_d8, first_config_flags) | |
| 196 | |
| 197 # Early bailout based on first run's output. | |
| 198 if pass_bailout(first_config_output, 1): | |
| 199 return 0 | |
| 200 if fail_bailout(first_config_output, suppress.ignore_by_output1): | |
| 201 return 2 | |
| 202 | |
| 203 second_config_output = run_d8(options.second_d8, second_config_flags) | |
| 204 | |
| 205 # Bailout based on second run's output. | |
| 206 if pass_bailout(second_config_output, 2): | |
| 207 return 0 | |
| 208 if fail_bailout(second_config_output, suppress.ignore_by_output2): | |
| 209 return 2 | |
| 210 | |
| 211 difference = suppress.diff( | |
| 212 first_config_output.stdout, second_config_output.stdout) | |
| 213 if difference: | |
| 214 # The first three entries will be parsed by clusterfuzz. Format changes | |
| 215 # will require changes on the clusterfuzz side. | |
| 216 first_config_label = '%s,%s' % (options.first_arch, options.first_config) | |
| 217 second_config_label = '%s,%s' % (options.second_arch, options.second_config) | |
| 218 print FAILURE_TEMPLATE % { | |
| 219 'configs': '%s:%s' % (first_config_label, second_config_label), | |
| 220 'sources': '', # TODO | |
| 221 'suppression': '', # We can't tie bugs to differences. | |
| 222 'first_config_label': first_config_label, | |
| 223 'second_config_label': second_config_label, | |
| 224 'first_config_flags': ' '.join(first_config_flags), | |
| 225 'second_config_flags': ' '.join(second_config_flags), | |
| 226 'first_config_output': first_config_output.stdout, | |
| 227 'second_config_output': second_config_output.stdout, | |
| 228 'difference': difference, | |
| 229 } | |
| 230 return 2 | |
| 231 | |
| 232 # TODO(machenbach): Figure out if we could also return a bug in case there's | |
| 233 # no difference, but one of the line suppressions has matched - and without | |
| 234 # the match there would be a difference. | |
| 235 | |
| 236 print '# V8 correctness - pass' | |
| 237 return 0 | |
| 238 | |
| 239 | |
| 240 if __name__ == "__main__": | |
| 241 try: | |
| 242 result = main() | |
| 243 except SystemExit: | |
| 244 # Make sure clusterfuzz reports internal errors and wrong usage. | |
| 245 # Use one label for all internal and usage errors. | |
| 246 print FAILURE_HEADER_TEMPLATE % { | |
| 247 'configs': '', | |
|
tandrii(chromium)
2016/12/19 08:43:32
nitty IMO: % dict(configs='', sources='', suppress
Michael Achenbach
2016/12/19 09:42:19
Done. Also for the other dicts here.
| |
| 248 'sources': '', | |
| 249 'suppression': 'wrong_usage', | |
| 250 } | |
| 251 result = 2 | |
| 252 except Exception as e: | |
| 253 print FAILURE_HEADER_TEMPLATE % { | |
| 254 'configs': '', | |
| 255 'sources': '', | |
| 256 'suppression': 'internal_error', | |
| 257 } | |
| 258 print '# Internal error: %s' % e | |
| 259 traceback.print_exc(file=sys.stdout) | |
| 260 result = 2 | |
| 261 | |
| 262 sys.exit(result) | |
| 263 | |
| OLD | NEW |