| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2016 the V8 project authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """ | 6 """ |
| 7 V8 correctness fuzzer launcher script. | 7 V8 correctness fuzzer launcher script. |
| 8 """ | 8 """ |
| 9 | 9 |
| 10 import argparse | 10 import argparse |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 44 | 44 |
| 45 FLAGS = ['--abort_on_stack_overflow', '--expose-gc', '--allow-natives-syntax', | 45 FLAGS = ['--abort_on_stack_overflow', '--expose-gc', '--allow-natives-syntax', |
| 46 '--invoke-weak-callbacks', '--omit-quit', '--es-staging'] | 46 '--invoke-weak-callbacks', '--omit-quit', '--es-staging'] |
| 47 | 47 |
| 48 SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64'] | 48 SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64'] |
| 49 | 49 |
| 50 # Output for suppressed failure case. | 50 # Output for suppressed failure case. |
| 51 FAILURE_HEADER_TEMPLATE = """# | 51 FAILURE_HEADER_TEMPLATE = """# |
| 52 # V8 correctness failure | 52 # V8 correctness failure |
| 53 # V8 correctness configs: %(configs)s | 53 # V8 correctness configs: %(configs)s |
| 54 # V8 correctness sources: %(sources)s | 54 # V8 correctness sources: %(source_key)s |
| 55 # V8 correctness suppression: %(suppression)s | 55 # V8 correctness suppression: %(suppression)s |
| 56 """ | 56 """ |
| 57 | 57 |
| 58 # Extended output for failure case. The 'CHECK' is for the minimizer. | 58 # Extended output for failure case. The 'CHECK' is for the minimizer. |
| 59 FAILURE_TEMPLATE = FAILURE_HEADER_TEMPLATE + """# | 59 FAILURE_TEMPLATE = FAILURE_HEADER_TEMPLATE + """# |
| 60 # CHECK | 60 # CHECK |
| 61 # | 61 # |
| 62 # Compared %(first_config_label)s with %(second_config_label)s | 62 # Compared %(first_config_label)s with %(second_config_label)s |
| 63 # | 63 # |
| 64 # Flags of %(first_config_label)s: | 64 # Flags of %(first_config_label)s: |
| 65 %(first_config_flags)s | 65 %(first_config_flags)s |
| 66 # Flags of %(second_config_label)s: | 66 # Flags of %(second_config_label)s: |
| 67 %(second_config_flags)s | 67 %(second_config_flags)s |
| 68 # | 68 # |
| 69 # Difference: | 69 # Difference: |
| 70 %(difference)s | 70 %(difference)s |
| 71 # | 71 # |
| 72 # Source file: |
| 73 %(source)s |
| 74 # |
| 72 ### Start of configuration %(first_config_label)s: | 75 ### Start of configuration %(first_config_label)s: |
| 73 %(first_config_output)s | 76 %(first_config_output)s |
| 74 ### End of configuration %(first_config_label)s | 77 ### End of configuration %(first_config_label)s |
| 75 # | 78 # |
| 76 ### Start of configuration %(second_config_label)s: | 79 ### Start of configuration %(second_config_label)s: |
| 77 %(second_config_output)s | 80 %(second_config_output)s |
| 78 ### End of configuration %(second_config_label)s | 81 ### End of configuration %(second_config_label)s |
| 79 """ | 82 """ |
| 80 | 83 |
| 81 FUZZ_TEST_RE = re.compile(r'.*fuzz(-\d+\.js)') | 84 FUZZ_TEST_RE = re.compile(r'.*fuzz(-\d+\.js)') |
| 85 SOURCE_RE = re.compile(r'print\("v8-foozzie source: (.*)"\);') |
| 82 | 86 |
| 83 def parse_args(): | 87 def parse_args(): |
| 84 parser = argparse.ArgumentParser() | 88 parser = argparse.ArgumentParser() |
| 85 parser.add_argument( | 89 parser.add_argument( |
| 86 '--random-seed', type=int, required=True, | 90 '--random-seed', type=int, required=True, |
| 87 help='random seed passed to both runs') | 91 help='random seed passed to both runs') |
| 88 parser.add_argument( | 92 parser.add_argument( |
| 89 '--first-arch', help='first architecture', default='x64') | 93 '--first-arch', help='first architecture', default='x64') |
| 90 parser.add_argument( | 94 parser.add_argument( |
| 91 '--second-arch', help='second architecture', default='x64') | 95 '--second-arch', help='second architecture', default='x64') |
| (...skipping 18 matching lines...) Expand all Loading... |
| 110 assert options.first_arch in SUPPORTED_ARCHS | 114 assert options.first_arch in SUPPORTED_ARCHS |
| 111 assert options.second_arch in SUPPORTED_ARCHS | 115 assert options.second_arch in SUPPORTED_ARCHS |
| 112 assert options.first_config in CONFIGS | 116 assert options.first_config in CONFIGS |
| 113 assert options.second_config in CONFIGS | 117 assert options.second_config in CONFIGS |
| 114 | 118 |
| 115 # Ensure we have a test case. | 119 # Ensure we have a test case. |
| 116 assert (os.path.exists(options.testcase) and | 120 assert (os.path.exists(options.testcase) and |
| 117 os.path.isfile(options.testcase)), ( | 121 os.path.isfile(options.testcase)), ( |
| 118 'Test case %s doesn\'t exist' % options.testcase) | 122 'Test case %s doesn\'t exist' % options.testcase) |
| 119 | 123 |
| 120 # Deduce metadata file name from test case. This also removes | |
| 121 # the prefix the test case might get during minimization. | |
| 122 suffix = FUZZ_TEST_RE.match(os.path.basename(options.testcase)).group(1) | |
| 123 options.meta_data_path = os.path.join( | |
| 124 os.path.dirname(options.testcase), 'meta' + suffix) | |
| 125 assert os.path.exists(options.meta_data_path), ( | |
| 126 'Metadata %s doesn\'t exist' % options.meta_data_path) | |
| 127 | |
| 128 # Use first d8 as default for second d8. | 124 # Use first d8 as default for second d8. |
| 129 options.second_d8 = options.second_d8 or options.first_d8 | 125 options.second_d8 = options.second_d8 or options.first_d8 |
| 130 | 126 |
| 131 # Ensure absolute paths. | 127 # Ensure absolute paths. |
| 132 if not os.path.isabs(options.first_d8): | 128 if not os.path.isabs(options.first_d8): |
| 133 options.first_d8 = os.path.join(BASE_PATH, options.first_d8) | 129 options.first_d8 = os.path.join(BASE_PATH, options.first_d8) |
| 134 if not os.path.isabs(options.second_d8): | 130 if not os.path.isabs(options.second_d8): |
| 135 options.second_d8 = os.path.join(BASE_PATH, options.second_d8) | 131 options.second_d8 = os.path.join(BASE_PATH, options.second_d8) |
| 136 | 132 |
| 137 # Ensure executables exist. | 133 # Ensure executables exist. |
| 138 assert os.path.exists(options.first_d8) | 134 assert os.path.exists(options.first_d8) |
| 139 assert os.path.exists(options.second_d8) | 135 assert os.path.exists(options.second_d8) |
| 140 | 136 |
| 141 # Ensure we use different executables when we claim we compare | 137 # Ensure we use different executables when we claim we compare |
| 142 # different architectures. | 138 # different architectures. |
| 143 # TODO(machenbach): Infer arch from gn's build output. | 139 # TODO(machenbach): Infer arch from gn's build output. |
| 144 if options.first_arch != options.second_arch: | 140 if options.first_arch != options.second_arch: |
| 145 assert options.first_d8 != options.second_d8 | 141 assert options.first_d8 != options.second_d8 |
| 146 | 142 |
| 147 return options | 143 return options |
| 148 | 144 |
| 149 | 145 |
| 150 def metadata_bailout(metadata, ignore_fun): | 146 def get_meta_data(content): |
| 151 """Print failure state and return if ignore_fun matches metadata.""" | 147 """Extracts original-source-file paths from test case content.""" |
| 152 bug = (ignore_fun(metadata) or '').strip() | 148 sources = [] |
| 149 for line in content.splitlines(): |
| 150 match = SOURCE_RE.match(line) |
| 151 if match: |
| 152 sources.append(match.group(1)) |
| 153 return {'sources': sources} |
| 154 |
| 155 |
| 156 def content_bailout(content, ignore_fun): |
| 157 """Print failure state and return if ignore_fun matches content.""" |
| 158 bug = (ignore_fun(content) or '').strip() |
| 153 if bug: | 159 if bug: |
| 154 print FAILURE_HEADER_TEMPLATE % dict( | 160 print FAILURE_HEADER_TEMPLATE % dict( |
| 155 configs='', sources='', suppression=bug) | 161 configs='', source_key='', suppression=bug) |
| 156 return True | 162 return True |
| 157 return False | 163 return False |
| 158 | 164 |
| 159 | 165 |
| 160 def test_pattern_bailout(testcase, ignore_fun): | |
| 161 """Print failure state and return if ignore_fun matches testcase.""" | |
| 162 with open(testcase) as f: | |
| 163 bug = (ignore_fun(f.read()) or '').strip() | |
| 164 if bug: | |
| 165 print FAILURE_HEADER_TEMPLATE % dict( | |
| 166 configs='', sources='', suppression=bug) | |
| 167 return True | |
| 168 return False | |
| 169 | |
| 170 | |
| 171 def pass_bailout(output, step_number): | 166 def pass_bailout(output, step_number): |
| 172 """Print info and return if in timeout or crash pass states.""" | 167 """Print info and return if in timeout or crash pass states.""" |
| 173 if output.HasTimedOut(): | 168 if output.HasTimedOut(): |
| 174 # Dashed output, so that no other clusterfuzz tools can match the | 169 # Dashed output, so that no other clusterfuzz tools can match the |
| 175 # words timeout or crash. | 170 # words timeout or crash. |
| 176 print '# V8 correctness - T-I-M-E-O-U-T %d' % step_number | 171 print '# V8 correctness - T-I-M-E-O-U-T %d' % step_number |
| 177 return True | 172 return True |
| 178 if output.HasCrashed(): | 173 if output.HasCrashed(): |
| 179 print '# V8 correctness - C-R-A-S-H %d' % step_number | 174 print '# V8 correctness - C-R-A-S-H %d' % step_number |
| 180 return True | 175 return True |
| 181 return False | 176 return False |
| 182 | 177 |
| 183 | 178 |
| 184 def fail_bailout(output, ignore_by_output_fun): | 179 def fail_bailout(output, ignore_by_output_fun): |
| 185 """Print failure state and return if ignore_by_output_fun matches output.""" | 180 """Print failure state and return if ignore_by_output_fun matches output.""" |
| 186 bug = (ignore_by_output_fun(output.stdout) or '').strip() | 181 bug = (ignore_by_output_fun(output.stdout) or '').strip() |
| 187 if bug: | 182 if bug: |
| 188 print FAILURE_HEADER_TEMPLATE % dict( | 183 print FAILURE_HEADER_TEMPLATE % dict( |
| 189 configs='', sources='', suppression=bug) | 184 configs='', source_key='', suppression=bug) |
| 190 return True | 185 return True |
| 191 return False | 186 return False |
| 192 | 187 |
| 193 | 188 |
| 194 def main(): | 189 def main(): |
| 195 options = parse_args() | 190 options = parse_args() |
| 196 | 191 |
| 197 # Suppressions are architecture and configuration specific. | 192 # Suppressions are architecture and configuration specific. |
| 198 suppress = v8_suppressions.get_suppression( | 193 suppress = v8_suppressions.get_suppression( |
| 199 options.first_arch, options.first_config, | 194 options.first_arch, options.first_config, |
| 200 options.second_arch, options.second_config, | 195 options.second_arch, options.second_config, |
| 201 ) | 196 ) |
| 202 | 197 |
| 203 # Get metadata. | 198 # Static bailout based on test case content or metadata. |
| 204 # TODO(machenbach): We probably don't need the metadata file anymore | 199 with open(options.testcase) as f: |
| 205 # now that the metadata is printed in the test cases. | 200 content = f.read() |
| 206 with open(options.meta_data_path) as f: | 201 if content_bailout(get_meta_data(content), suppress.ignore_by_metadata): |
| 207 metadata = json.load(f) | 202 return RETURN_FAIL |
| 208 | 203 if content_bailout(content, suppress.ignore_by_content): |
| 209 if metadata_bailout(metadata, suppress.ignore_by_metadata): | |
| 210 return RETURN_FAIL | 204 return RETURN_FAIL |
| 211 | 205 |
| 212 if test_pattern_bailout(options.testcase, suppress.ignore_by_content): | 206 # Set up runtime arguments. |
| 213 return RETURN_FAIL | |
| 214 | |
| 215 common_flags = FLAGS + ['--random-seed', str(options.random_seed)] | 207 common_flags = FLAGS + ['--random-seed', str(options.random_seed)] |
| 216 first_config_flags = common_flags + CONFIGS[options.first_config] | 208 first_config_flags = common_flags + CONFIGS[options.first_config] |
| 217 second_config_flags = common_flags + CONFIGS[options.second_config] | 209 second_config_flags = common_flags + CONFIGS[options.second_config] |
| 218 | 210 |
| 219 def run_d8(d8, config_flags): | 211 def run_d8(d8, config_flags): |
| 220 args = [d8] + config_flags + PREAMBLE + [options.testcase] | 212 args = [d8] + config_flags + PREAMBLE + [options.testcase] |
| 221 if d8.endswith('.py'): | 213 if d8.endswith('.py'): |
| 222 # Wrap with python in tests. | 214 # Wrap with python in tests. |
| 223 args = [sys.executable] + args | 215 args = [sys.executable] + args |
| 224 return v8_commands.Execute( | 216 return v8_commands.Execute( |
| (...skipping 11 matching lines...) Expand all Loading... |
| 236 return RETURN_FAIL | 228 return RETURN_FAIL |
| 237 | 229 |
| 238 second_config_output = run_d8(options.second_d8, second_config_flags) | 230 second_config_output = run_d8(options.second_d8, second_config_flags) |
| 239 | 231 |
| 240 # Bailout based on second run's output. | 232 # Bailout based on second run's output. |
| 241 if pass_bailout(second_config_output, 2): | 233 if pass_bailout(second_config_output, 2): |
| 242 return RETURN_PASS | 234 return RETURN_PASS |
| 243 if fail_bailout(second_config_output, suppress.ignore_by_output2): | 235 if fail_bailout(second_config_output, suppress.ignore_by_output2): |
| 244 return RETURN_FAIL | 236 return RETURN_FAIL |
| 245 | 237 |
| 246 difference, source_key = suppress.diff( | 238 difference, source, source_key = suppress.diff( |
| 247 first_config_output.stdout, second_config_output.stdout) | 239 first_config_output.stdout, second_config_output.stdout) |
| 248 if difference: | 240 if difference: |
| 249 # The first three entries will be parsed by clusterfuzz. Format changes | 241 # The first three entries will be parsed by clusterfuzz. Format changes |
| 250 # will require changes on the clusterfuzz side. | 242 # will require changes on the clusterfuzz side. |
| 251 first_config_label = '%s,%s' % (options.first_arch, options.first_config) | 243 first_config_label = '%s,%s' % (options.first_arch, options.first_config) |
| 252 second_config_label = '%s,%s' % (options.second_arch, options.second_config) | 244 second_config_label = '%s,%s' % (options.second_arch, options.second_config) |
| 253 print FAILURE_TEMPLATE % dict( | 245 print FAILURE_TEMPLATE % dict( |
| 254 configs='%s:%s' % (first_config_label, second_config_label), | 246 configs='%s:%s' % (first_config_label, second_config_label), |
| 255 sources=source_key, | 247 source_key=source_key, |
| 256 suppression='', # We can't tie bugs to differences. | 248 suppression='', # We can't tie bugs to differences. |
| 257 first_config_label=first_config_label, | 249 first_config_label=first_config_label, |
| 258 second_config_label=second_config_label, | 250 second_config_label=second_config_label, |
| 259 first_config_flags=' '.join(first_config_flags), | 251 first_config_flags=' '.join(first_config_flags), |
| 260 second_config_flags=' '.join(second_config_flags), | 252 second_config_flags=' '.join(second_config_flags), |
| 261 first_config_output=first_config_output.stdout, | 253 first_config_output=first_config_output.stdout, |
| 262 second_config_output=second_config_output.stdout, | 254 second_config_output=second_config_output.stdout, |
| 255 source=source, |
| 263 difference=difference, | 256 difference=difference, |
| 264 ) | 257 ) |
| 265 return RETURN_FAIL | 258 return RETURN_FAIL |
| 266 | 259 |
| 267 # TODO(machenbach): Figure out if we could also return a bug in case there's | 260 # TODO(machenbach): Figure out if we could also return a bug in case there's |
| 268 # no difference, but one of the line suppressions has matched - and without | 261 # no difference, but one of the line suppressions has matched - and without |
| 269 # the match there would be a difference. | 262 # the match there would be a difference. |
| 270 | 263 |
| 271 print '# V8 correctness - pass' | 264 print '# V8 correctness - pass' |
| 272 return RETURN_PASS | 265 return RETURN_PASS |
| 273 | 266 |
| 274 | 267 |
| 275 if __name__ == "__main__": | 268 if __name__ == "__main__": |
| 276 try: | 269 try: |
| 277 result = main() | 270 result = main() |
| 278 except SystemExit: | 271 except SystemExit: |
| 279 # Make sure clusterfuzz reports internal errors and wrong usage. | 272 # Make sure clusterfuzz reports internal errors and wrong usage. |
| 280 # Use one label for all internal and usage errors. | 273 # Use one label for all internal and usage errors. |
| 281 print FAILURE_HEADER_TEMPLATE % dict( | 274 print FAILURE_HEADER_TEMPLATE % dict( |
| 282 configs='', sources='', suppression='wrong_usage') | 275 configs='', source_key='', suppression='wrong_usage') |
| 283 result = RETURN_FAIL | 276 result = RETURN_FAIL |
| 284 except Exception as e: | 277 except Exception as e: |
| 285 print FAILURE_HEADER_TEMPLATE % dict( | 278 print FAILURE_HEADER_TEMPLATE % dict( |
| 286 configs='', sources='', suppression='internal_error') | 279 configs='', source_key='', suppression='internal_error') |
| 287 print '# Internal error: %s' % e | 280 print '# Internal error: %s' % e |
| 288 traceback.print_exc(file=sys.stdout) | 281 traceback.print_exc(file=sys.stdout) |
| 289 result = RETURN_FAIL | 282 result = RETURN_FAIL |
| 290 | 283 |
| 291 sys.exit(result) | 284 sys.exit(result) |
| OLD | NEW |