OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
2 # Copyright 2014 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 import os | |
7 import re | |
8 import shutil | |
9 import sys | |
10 | |
11 # TODO(jkummerow): Support DATA_VIEW_{G,S}ETTER in runtime.cc | |
12 | |
13 FILENAME = "src/runtime.cc" | |
14 HEADERFILENAME = "src/runtime.h" | |
15 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)") | |
16 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);") | |
17 FUNCTIONEND = "}\n" | |
18 | |
19 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")) | |
20 BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen") | |
Michael Achenbach
2014/05/08 08:03:12
Wouldn't this fit better into the fuzz-natives tes
Jakob Kummerow
2014/05/08 09:45:38
Semantically yes, but the distinction between mjsu
| |
21 THIS_SCRIPT = os.path.relpath(sys.argv[0]) | |
22 | |
23 # Counts of functions in each detection state. These are used to assert | |
24 # that the parser doesn't bit-rot. Change the values as needed when you add, | |
25 # remove or change runtime functions, but make sure we don't lose our ability | |
26 # to parse them! | |
27 EXPECTED_FUNCTION_COUNT = 339 | |
28 EXPECTED_FUZZABLE_COUNT = 317 | |
29 EXPECTED_CCTEST_COUNT = 6 | |
30 EXPECTED_UNKNOWN_COUNT = 5 | |
31 | |
32 | |
33 # Don't call these at all. | |
34 BLACKLISTED = [ | |
35 "Abort", # Kills the process. | |
36 "AbortJS", # Kills the process. | |
37 "CompileForOnStackReplacement", # Riddled with ASSERTs. | |
38 "IS_VAR", # Not implemented in the runtime. | |
39 "ListNatives", # Not available in Release mode. | |
40 "SetAllocationTimeout", # Too slow for fuzzing. | |
41 "SystemBreak", # Kills (int3) the process. | |
42 | |
43 # These are weird. They violate some invariants when called after | |
44 # bootstrapping. | |
45 "DisableAccessChecks", | |
46 "EnableAccessChecks", | |
47 | |
48 # Seems to be incompatible with --stress-runs. | |
49 "LiveEditReplaceScript", | |
50 | |
51 # TODO(jkummerow): Fix these and un-blacklist them! | |
52 "CreateDateTimeFormat", | |
53 "CreateNumberFormat", | |
54 ] | |
55 | |
56 | |
57 # These will always throw. | |
58 THROWS = [ | |
59 "CheckExecutionState", # Needs to hit a break point. | |
60 "CheckIsBootstrapping", # Needs to be bootstrapping. | |
61 "DebugEvaluate", # Needs to hit a break point. | |
62 "DebugEvaluateGlobal", # Needs to hit a break point. | |
63 "DebugIndexedInterceptorElementValue", # Needs an indexed interceptor. | |
64 "DebugNamedInterceptorPropertyValue", # Needs a named interceptor. | |
65 "DebugSetScriptSource", # Checks compilation state of script. | |
66 "GetAllScopesDetails", # Needs to hit a break point. | |
67 "GetFrameCount", # Needs to hit a break point. | |
68 "GetFrameDetails", # Needs to hit a break point. | |
69 "GetRootNaN", # Needs to be bootstrapping. | |
70 "GetScopeCount", # Needs to hit a break point. | |
71 "GetScopeDetails", # Needs to hit a break point. | |
72 "GetStepInPositions", # Needs to hit a break point. | |
73 "GetTemplateField", # Needs a {Function,Object}TemplateInfo. | |
74 "GetThreadCount", # Needs to hit a break point. | |
75 "GetThreadDetails", # Needs to hit a break point. | |
76 "IsAccessAllowedForObserver", # Needs access-check-required object. | |
77 "LiveEditFunctionSourceUpdated", # Needs a SharedFunctionInfo. | |
78 "LiveEditPatchFunctionPositions", # Needs a SharedFunctionInfo. | |
79 "LiveEditReplaceFunctionCode", # Needs a SharedFunctionInfo. | |
80 "LiveEditReplaceRefToNestedFunction", # Needs a SharedFunctionInfo. | |
81 "LiveEditRestartFrame", # Needs to hit a break point. | |
82 "UnblockConcurrentRecompilation" # Needs --block-concurrent-recompilation. | |
83 ] | |
84 | |
85 | |
86 # Definitions used in CUSTOM_KNOWN_GOOD_INPUT below. | |
87 _BREAK_ITERATOR = ( | |
88 "%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())") | |
89 _COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))" | |
90 _DATETIME_FORMAT = ( | |
91 "%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))") | |
92 _NUMBER_FORMAT = ( | |
93 "%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))") | |
94 _SCRIPT = "%DebugGetLoadedScripts()[1]" | |
95 | |
96 | |
97 # Custom definitions for function input that does not throw. | |
98 # Format: "FunctionName": ["arg0", "arg1", ..., argslength]. | |
99 # None means "fall back to autodetected value". | |
100 CUSTOM_KNOWN_GOOD_INPUT = { | |
101 "Apply": ["function() {}", None, None, None, None, None], | |
102 "ArrayBufferSliceImpl": [None, None, 0, None], | |
103 "ArrayConcat": ["[1, 'a']", None], | |
104 "BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None], | |
105 "BreakIteratorBreakType": [_BREAK_ITERATOR, None], | |
106 "BreakIteratorCurrent": [_BREAK_ITERATOR, None], | |
107 "BreakIteratorFirst": [_BREAK_ITERATOR, None], | |
108 "BreakIteratorNext": [_BREAK_ITERATOR, None], | |
109 "CompileString": [None, "false", None], | |
110 "CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None], | |
111 "CreateJSFunctionProxy": [None, "function() {}", None, None, None], | |
112 "CreatePrivateSymbol": ["\"foo\"", None], | |
113 "CreateSymbol": ["\"foo\"", None], | |
114 "DateParseString": [None, "new Array(8)", None], | |
115 "DebugSetScriptSource": [_SCRIPT, None, None], | |
116 "DefineOrRedefineAccessorProperty": [None, None, "function() {}", | |
117 "function() {}", 2, None], | |
118 "GetBreakLocations": [None, 0, None], | |
119 "GetDefaultReceiver": ["function() {}", None], | |
120 "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None], | |
121 "InternalCompare": [_COLLATOR, None, None, None], | |
122 "InternalDateFormat": [_DATETIME_FORMAT, None, None], | |
123 "InternalDateParse": [_DATETIME_FORMAT, None, None], | |
124 "InternalNumberFormat": [_NUMBER_FORMAT, None, None], | |
125 "InternalNumberParse": [_NUMBER_FORMAT, None, None], | |
126 "IsSloppyModeFunction": ["function() {}", None], | |
127 "LiveEditFindSharedFunctionInfosForScript": [_SCRIPT, None], | |
128 "LiveEditGatherCompileInfo": [_SCRIPT, None, None], | |
129 "LoadMutableDouble": ["{foo: 1.2}", None, None], | |
130 "NewObjectFromBound": ["(function() {}).bind({})", None], | |
131 "NumberToRadixString": [None, "2", None], | |
132 "ParseJson": ["\"{}\"", 1], | |
133 "SetAccessorProperty": [None, None, "undefined", "undefined", None, None, | |
134 None], | |
135 "SetCreateIterator": [None, "2", None], | |
136 "SetDebugEventListener": ["undefined", None, None], | |
137 "SetScriptBreakPoint": [_SCRIPT, None, 0, None, None], | |
138 "StringBuilderConcat": ["[1, 2, 3]", 3, None, None], | |
139 "StringBuilderJoin": ["['a', 'b']", 4, None, None], | |
140 "StringMatch": [None, None, "['a', 'b']", None], | |
141 "StringNormalize": [None, 2, None], | |
142 "TypedArrayInitialize": [None, None, "new ArrayBuffer(8)", None, None, None], | |
143 "TypedArraySetFastCases": [None, None, "0", None], | |
144 } | |
145 | |
146 | |
147 # Types of arguments that cannot be generated in a JavaScript testcase. | |
148 NON_JS_TYPES = [ | |
149 "Code", "Context", "FixedArray", "FunctionTemplateInfo", | |
150 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo", | |
151 "SharedFunctionInfo"] | |
152 | |
153 | |
154 # Maps argument types to concrete example inputs of that type. | |
155 JS_TYPE_GENERATORS = { | |
156 "Boolean": "true", | |
157 "HeapObject": "new Object()", | |
158 "Int32": "32", | |
159 "JSArray": "new Array()", | |
160 "JSArrayBuffer": "new ArrayBuffer(8)", | |
161 "JSDataView": "new DataView(new ArrayBuffer(8))", | |
162 "JSDate": "new Date()", | |
163 "JSFunction": "function() {}", | |
164 "JSFunctionProxy": "Proxy.createFunction({}, function() {})", | |
165 "JSGeneratorObject": "(function*(){ yield 1; })()", | |
166 "JSMap": "new Map()", | |
167 "JSMapIterator": "%MapCreateIterator(new Map(), 3)", | |
168 "JSObject": "new Object()", | |
169 "JSProxy": "Proxy.create({})", | |
170 "JSReceiver": "new Object()", | |
171 "JSRegExp": "/ab/g", | |
172 "JSSet": "new Set()", | |
173 "JSSetIterator": "%SetCreateIterator(new Set(), 2)", | |
174 "JSTypedArray": "new Int32Array(2)", | |
175 "JSValue": "new String('foo')", | |
176 "JSWeakCollection": "new WeakMap()", | |
177 "Name": "\"name\"", | |
178 "Number": "1.5", | |
179 "Object": "new Object()", | |
180 "PropertyDetails": "513", | |
181 "SeqString": "\"seqstring\"", | |
182 "Smi": 1, | |
183 "StrictMode": "1", | |
184 "String": "\"foo\"", | |
185 "Symbol": "Symbol(\"symbol\")", | |
186 "Uint32": "32", | |
187 } | |
188 | |
189 | |
190 class ArgParser(object): | |
191 def __init__(self, regex, ctor): | |
192 self.regex = regex | |
193 self.ArgCtor = ctor | |
Michael Achenbach
2014/05/08 08:03:12
nit: lower case?
Jakob Kummerow
2014/05/08 09:45:38
Chose upper case since this isn't just a field, it
| |
194 | |
195 | |
196 class Arg(object): | |
197 def __init__(self, typename, varname, index): | |
198 self.type = typename | |
199 self.name = "_%s" % varname | |
200 self.index = index | |
201 | |
202 | |
203 class Function(object): | |
204 def __init__(self, match): | |
205 self.name = match.group(1) | |
206 self.argslength = -1 | |
207 self.args = {} | |
208 self.inline = "" | |
209 | |
210 handle_arg_parser = ArgParser( | |
211 re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"), | |
212 lambda match: Arg(match.group(1), match.group(2), int(match.group(3)))) | |
213 | |
214 plain_arg_parser = ArgParser( | |
215 re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"), | |
216 lambda match: Arg(match.group(1), match.group(2), int(match.group(3)))) | |
217 | |
218 number_handle_arg_parser = ArgParser( | |
219 re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"), | |
220 lambda match: Arg("Number", match.group(1), int(match.group(2)))) | |
221 | |
222 smi_arg_parser = ArgParser( | |
223 re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"), | |
224 lambda match: Arg("Smi", match.group(1), int(match.group(2)))) | |
225 | |
226 double_arg_parser = ArgParser( | |
227 re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"), | |
228 lambda match: Arg("Number", match.group(1), int(match.group(2)))) | |
229 | |
230 number_arg_parser = ArgParser( | |
231 re.compile( | |
232 "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"), | |
233 lambda match: Arg(match.group(2), match.group(1), int(match.group(3)))) | |
234 | |
235 strict_mode_arg_parser = ArgParser( | |
236 re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"), | |
237 lambda match: Arg("StrictMode", match.group(1), int(match.group(2)))) | |
238 | |
239 boolean_arg_parser = ArgParser( | |
240 re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"), | |
241 lambda match: Arg("Boolean", match.group(1), int(match.group(2)))) | |
242 | |
243 property_details_parser = ArgParser( | |
244 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"), | |
245 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2)))) | |
246 | |
247 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser, | |
248 smi_arg_parser, | |
249 double_arg_parser, number_arg_parser, strict_mode_arg_parser, | |
250 boolean_arg_parser, property_details_parser] | |
251 | |
252 | |
253 def SetArgsLength(self, match): | |
254 self.argslength = int(match.group(1)) | |
255 | |
256 def TryParseArg(self, line): | |
257 for parser in Function.arg_parsers: | |
258 match = parser.regex.match(line) | |
259 if match: | |
260 arg = parser.ArgCtor(match) | |
261 self.args[arg.index] = arg | |
262 return True | |
263 return False | |
264 | |
265 def Filename(self): | |
266 return "%s.js" % self.name.lower() | |
267 | |
268 def __str__(self): | |
269 s = [self.name, "("] | |
270 argcount = self.argslength | |
271 if argcount < 0: | |
272 print("WARNING: unknown argslength for function %s" % self.name) | |
273 if self.args: | |
274 argcount = max([self.args[i].index + 1 for i in self.args]) | |
275 else: | |
276 argcount = 0 | |
277 for i in range(argcount): | |
278 if i > 0: s.append(", ") | |
279 s.append(self.args[i].type if i in self.args else "<unknown>") | |
280 s.append(")") | |
281 return "".join(s) | |
282 | |
283 # Parses HEADERFILENAME to find out which runtime functions are "inline". | |
284 def FindInlineRuntimeFunctions(): | |
285 inline_functions = [] | |
286 with open(HEADERFILENAME, "r") as f: | |
287 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n" | |
288 inline_opt_list = "#define INLINE_OPTIMIZED_FUNCTION_LIST(F) \\\n" | |
289 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?") | |
290 mode = "SEARCHING" | |
291 for line in f: | |
292 if mode == "ACTIVE": | |
293 match = inline_function.match(line) | |
294 if match: | |
295 inline_functions.append(match.group(1)) | |
296 if not line.endswith("\\\n"): | |
297 mode = "SEARCHING" | |
298 elif mode == "SEARCHING": | |
299 if line == inline_list or line == inline_opt_list: | |
300 mode = "ACTIVE" | |
301 return inline_functions | |
302 | |
303 | |
304 # Detects runtime functions by parsing FILENAME. | |
305 def FindRuntimeFunctions(): | |
306 inline_functions = FindInlineRuntimeFunctions() | |
307 functions = [] | |
308 with open(FILENAME, "r") as f: | |
309 function = None | |
310 partial_line = "" | |
311 for line in f: | |
312 # Multi-line definition support, ignoring macros. | |
313 if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"): | |
314 if line.endswith("\\\n"): continue | |
315 partial_line = line.rstrip() | |
316 continue | |
317 if partial_line: | |
318 partial_line += " " + line.strip() | |
319 if partial_line.endswith("{"): | |
320 line = partial_line | |
321 partial_line = "" | |
322 else: | |
323 continue | |
324 | |
325 match = FUNCTION.match(line) | |
326 if match: | |
327 function = Function(match) | |
328 if function.name in inline_functions: | |
329 function.inline = "_" | |
330 continue | |
331 if function is None: continue | |
332 | |
333 match = ARGSLENGTH.match(line) | |
334 if match: | |
335 function.SetArgsLength(match) | |
336 continue | |
337 | |
338 if function.TryParseArg(line): | |
339 continue | |
340 | |
341 if line == FUNCTIONEND: | |
342 if function is not None: | |
343 functions.append(function) | |
344 function = None | |
345 return functions | |
346 | |
347 # Classifies runtime functions. | |
348 def ClassifyFunctions(functions): | |
349 # Can be fuzzed with a JavaScript testcase. | |
350 js_fuzzable_functions = [] | |
351 # We have enough information to fuzz these, but they need inputs that | |
352 # cannot be created or passed around in JavaScript. | |
353 cctest_fuzzable_functions = [] | |
354 # This script does not have enough information about these. | |
355 unknown_functions = [] | |
356 | |
357 types = {} | |
358 for f in functions: | |
359 if f.name in BLACKLISTED: | |
360 continue | |
361 decision = js_fuzzable_functions | |
362 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None) | |
363 if f.argslength < 0: | |
364 # Unknown length -> give up unless there's a custom definition. | |
365 if custom and custom[-1] is not None: | |
366 f.argslength = custom[-1] | |
367 assert len(custom) == f.argslength + 1, \ | |
368 ("%s: last custom definition must be argslength" % f.name) | |
369 else: | |
370 decision = unknown_functions | |
371 else: | |
372 if custom: | |
373 # Any custom definitions must match the known argslength. | |
374 assert len(custom) == f.argslength + 1, \ | |
375 ("%s should have %d custom definitions but has %d" % | |
376 (f.name, f.argslength + 1, len(custom))) | |
377 for i in range(f.argslength): | |
378 if custom and custom[i] is not None: | |
379 # All good, there's a custom definition. | |
380 pass | |
381 elif not i in f.args: | |
382 # No custom definition and no parse result -> give up. | |
383 decision = unknown_functions | |
384 else: | |
385 t = f.args[i].type | |
386 if t in NON_JS_TYPES: | |
387 decision = cctest_fuzzable_functions | |
388 else: | |
389 assert t in JS_TYPE_GENERATORS, \ | |
390 ("type generator not found for %s, function: %s" % (t, f)) | |
391 decision.append(f) | |
392 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) | |
393 | |
394 | |
395 def GenerateJSTestcaseForFunction(f): | |
396 s = ["// Copyright 2014 the V8 project authors. All rights reserved.", | |
397 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY", | |
398 "// Flags: --allow-natives-syntax --harmony"] | |
399 call = "%%%s%s(" % (f.inline, f.name) | |
400 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None) | |
401 for i in range(f.argslength): | |
402 if custom and custom[i] is not None: | |
403 (name, value) = ("arg%d" % i, custom[i]) | |
404 else: | |
405 arg = f.args[i] | |
406 (name, value) = (arg.name, JS_TYPE_GENERATORS[arg.type]) | |
407 s.append("var %s = %s;" % (name, value)) | |
408 if i > 0: call += ", " | |
Michael Achenbach
2014/05/08 08:03:12
how about collecting the names in a list and then
Jakob Kummerow
2014/05/08 09:45:38
I've already refactored this in a follow-up CL tha
| |
409 call += name | |
410 call += ");" | |
411 if f.name in THROWS: | |
412 s.append("try {") | |
413 s.append(call); | |
414 s.append("} catch(e) {}") | |
415 else: | |
416 s.append(call) | |
417 testcase = "\n".join(s) | |
418 path = os.path.join(BASEPATH, f.Filename()) | |
419 with open(path, "w") as f: | |
420 f.write("%s\n" % testcase) | |
421 | |
422 def GenerateTestcases(functions): | |
423 shutil.rmtree(BASEPATH) # Re-generate everything. | |
424 os.makedirs(BASEPATH) | |
425 for f in functions: | |
426 GenerateJSTestcaseForFunction(f) | |
427 | |
428 def PrintUsage(): | |
429 print """Usage: %(this_script)s ACTION | |
430 | |
431 where ACTION can be: | |
432 | |
433 info Print diagnostic info. | |
434 check Check that runtime functions can be parsed as expected, and that | |
435 test cases exist. | |
436 generate Parse source code for runtime functions, and auto-generate | |
437 test cases for them. Warning: this will nuke and re-create | |
438 %(path)s. | |
439 """ % {"path": os.path.relpath(BASEPATH), "this_script": THIS_SCRIPT} | |
440 | |
441 if __name__ == "__main__": | |
442 if len(sys.argv) != 2: | |
443 PrintUsage() | |
444 sys.exit(1) | |
445 action = sys.argv[1] | |
446 if action in ["-h", "--help", "help"]: | |
447 PrintUsage() | |
448 sys.exit(0) | |
449 | |
450 functions = FindRuntimeFunctions() | |
Michael Achenbach
2014/05/08 08:03:12
Would it be useful if it found also functions defi
Jakob Kummerow
2014/05/08 09:45:38
Potentially. This is just a first step. There is p
| |
451 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ | |
452 ClassifyFunctions(functions) | |
453 | |
454 if action == "info": | |
455 print("%d functions total; js_fuzzable_functions: %d, " | |
456 "cctest_fuzzable_functions: %d, unknown_functions: %d" | |
457 % (len(functions), len(js_fuzzable_functions), | |
458 len(cctest_fuzzable_functions), len(unknown_functions))) | |
459 print("unknown functions:") | |
460 for f in unknown_functions: | |
461 print(f) | |
462 sys.exit(0) | |
463 | |
464 if action == "check": | |
465 error = False | |
466 def CheckCount(actual, expected, description): | |
467 global error | |
468 if len(actual) != expected: | |
469 print("Expected to detect %d %s, but found %d." % ( | |
470 expected, description, len(actual))) | |
471 error = True | |
472 CheckCount(functions, EXPECTED_FUNCTION_COUNT, "functions in total") | |
473 CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, | |
474 "JavaScript-fuzzable functions") | |
475 CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, | |
476 "cctest-fuzzable functions") | |
477 CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, | |
478 "functions with incomplete type information") | |
479 | |
480 def CheckTestcasesExisting(functions): | |
481 global error | |
482 for f in functions: | |
483 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())): | |
484 print("Missing testcase for %s, please run '%s generate'" % | |
485 (f.name, THIS_SCRIPT)) | |
486 error = True | |
487 files = os.listdir(BASEPATH) | |
Michael Achenbach
2014/05/08 08:03:12
Wonder if there appears a .svn folder on the bots
Jakob Kummerow
2014/05/08 09:45:38
Good point. Let's filter out dotfiles.
| |
488 if (len(files) != len(functions)): | |
489 unexpected_files = set(files) - set([f.Filename() for f in functions]) | |
490 for f in unexpected_files: | |
491 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f)) | |
492 error = True | |
493 CheckTestcasesExisting(js_fuzzable_functions) | |
494 | |
495 if error: | |
496 sys.exit(1) | |
497 print("All good.") | |
498 sys.exit(0) | |
499 | |
500 if action == "generate": | |
501 GenerateTestcases(js_fuzzable_functions) | |
502 sys.exit(0) | |
OLD | NEW |