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 |