OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2014 the V8 project authors. All rights reserved. | 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 | 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 import itertools | |
7 import multiprocessing | |
8 import optparse | |
6 import os | 9 import os |
10 import random | |
7 import re | 11 import re |
8 import shutil | 12 import shutil |
13 import signal | |
14 import string | |
15 import subprocess | |
9 import sys | 16 import sys |
17 import time | |
10 | 18 |
11 # TODO(jkummerow): Support DATA_VIEW_{G,S}ETTER in runtime.cc | 19 # TODO(jkummerow): Support DATA_VIEW_{G,S}ETTER in runtime.cc |
12 | 20 |
13 FILENAME = "src/runtime.cc" | 21 FILENAME = "src/runtime.cc" |
14 HEADERFILENAME = "src/runtime.h" | 22 HEADERFILENAME = "src/runtime.h" |
15 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)") | 23 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)") |
16 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);") | 24 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);") |
17 FUNCTIONEND = "}\n" | 25 FUNCTIONEND = "}\n" |
18 | 26 |
19 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")) | 27 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")) |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
149 } | 157 } |
150 | 158 |
151 | 159 |
152 # Types of arguments that cannot be generated in a JavaScript testcase. | 160 # Types of arguments that cannot be generated in a JavaScript testcase. |
153 NON_JS_TYPES = [ | 161 NON_JS_TYPES = [ |
154 "Code", "Context", "FixedArray", "FunctionTemplateInfo", | 162 "Code", "Context", "FixedArray", "FunctionTemplateInfo", |
155 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo", | 163 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo", |
156 "SharedFunctionInfo"] | 164 "SharedFunctionInfo"] |
157 | 165 |
158 | 166 |
159 # Maps argument types to concrete example inputs of that type. | 167 class Generator(object): |
160 JS_TYPE_GENERATORS = { | 168 |
161 "Boolean": "true", | 169 def RandomVariable(self, varname, vartype, simple): |
162 "HeapObject": "new Object()", | 170 if simple: |
163 "Int32": "32", | 171 return self._Variable(varname, self.GENERATORS[vartype][0]) |
164 "JSArray": "new Array()", | 172 return self.GENERATORS[vartype][1](self, varname, |
165 "JSArrayBuffer": "new ArrayBuffer(8)", | 173 self.DEFAULT_RECURSION_BUDGET) |
166 "JSDataView": "new DataView(new ArrayBuffer(8))", | 174 |
167 "JSDate": "new Date()", | 175 @staticmethod |
168 "JSFunction": "function() {}", | 176 def IsTypeSupported(typename): |
169 "JSFunctionProxy": "Proxy.createFunction({}, function() {})", | 177 return typename in Generator.GENERATORS |
170 "JSGeneratorObject": "(function*(){ yield 1; })()", | 178 |
171 "JSMap": "new Map()", | 179 USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__", |
172 "JSMapIterator": "%MapCreateIterator(new Map(), 3)", | 180 "prototype", "0", "1", "-1"] |
173 "JSObject": "new Object()", | 181 DEFAULT_RECURSION_BUDGET = 2 |
174 "JSProxy": "Proxy.create({})", | 182 PROXY_TRAPS = """{ |
175 "JSReceiver": "new Object()", | 183 getOwnPropertyDescriptor: function(name) { |
176 "JSRegExp": "/ab/g", | 184 return {value: function() {}, configurable: true, writable: true, |
177 "JSSet": "new Set()", | 185 enumerable: true}; |
178 "JSSetIterator": "%SetCreateIterator(new Set(), 2)", | 186 }, |
179 "JSTypedArray": "new Int32Array(2)", | 187 getPropertyDescriptor: function(name) { |
180 "JSValue": "new String('foo')", | 188 return {value: function() {}, configurable: true, writable: true, |
181 "JSWeakCollection": "new WeakMap()", | 189 enumerable: true}; |
182 "Name": "\"name\"", | 190 }, |
183 "Number": "1.5", | 191 getOwnPropertyNames: function() { return []; }, |
184 "Object": "new Object()", | 192 getPropertyNames: function() { return []; }, |
185 "PropertyDetails": "513", | 193 defineProperty: function(name, descriptor) {}, |
186 "SeqString": "\"seqstring\"", | 194 delete: function(name) { return true; }, |
187 "Smi": 1, | 195 fix: function() {} |
188 "StrictMode": "1", | 196 }""" |
189 "String": "\"foo\"", | 197 |
190 "Symbol": "Symbol(\"symbol\")", | 198 def _Variable(self, name, value, fallback=None): |
191 "Uint32": "32", | 199 args = { "name": name, "value": value, "fallback": fallback } |
192 } | 200 if fallback: |
201 wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args | |
202 else: | |
203 wrapper = "%s" | |
204 return [wrapper % ("var %(name)s = %(value)s;" % args)] | |
205 | |
206 def _Boolean(self, name, recursion_budget): | |
207 return self._Variable(name, random.choice(["true", "false"])) | |
208 | |
209 def _Oddball(self, name, recursion_budget): | |
210 return self._Variable(name, | |
211 random.choice(["true", "false", "undefined", "null"])) | |
212 | |
213 def _StrictMode(self, name, recursion_budget): | |
214 return self._Variable(name, random.choice([0, 1])) | |
215 | |
216 def _Int32(self, name, recursion_budget=0): | |
217 die = random.random() | |
218 if die < 0.5: | |
219 value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff, | |
220 0x40000000, -0x40000000, -0x80000000]) | |
221 elif die < 0.75: | |
222 value = random.randint(-1000, 1000) | |
223 else: | |
224 value = random.randint(-0x80000000, 0x7fffffff) | |
225 return self._Variable(name, value) | |
226 | |
227 def _Uint32(self, name, recursion_budget=0): | |
228 die = random.random() | |
229 if die < 0.5: | |
230 value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000, | |
231 0x7fffffff, 0xffffffff]) | |
232 elif die < 0.75: | |
233 value = random.randint(0, 1000) | |
234 else: | |
235 value = random.randint(0, 0xffffffff) | |
236 return self._Variable(name, value) | |
237 | |
238 def _Smi(self, name, recursion_budget): | |
239 die = random.random() | |
240 if die < 0.5: | |
241 value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000]) | |
242 elif die < 0.75: | |
243 value = random.randint(-1000, 1000) | |
244 else: | |
245 value = random.randint(-0x40000000, 0x3fffffff) | |
246 return self._Variable(name, value) | |
247 | |
248 def _Number(self, name, recursion_budget): | |
249 die = random.random() | |
250 if die < 0.5: | |
251 return self._Smi(name, recursion_budget) | |
252 elif die < 0.6: | |
253 value = random.choice(["Infinity", "-Infinity", "NaN", "-0", | |
254 "1.7976931348623157e+308", # Max value. | |
255 "2.2250738585072014e-308", # Min value. | |
256 "4.9406564584124654e-324"]) # Min subnormal. | |
257 else: | |
258 value = random.lognormvariate(0, 15) | |
259 return self._Variable(name, value) | |
260 | |
261 def _RawRandomString(self, minlength=0, maxlength=100, | |
262 alphabet=string.ascii_letters): | |
263 length = random.randint(minlength, maxlength) | |
264 result = "" | |
265 for i in xrange(length): | |
266 result += random.choice(alphabet) | |
267 return result | |
268 | |
269 def _SeqString(self, name, recursion_budget): | |
270 s1 = self._RawRandomString(1, 5) | |
271 s2 = self._RawRandomString(1, 5) | |
272 # 'foo' + 'bar' | |
273 return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2)) | |
274 | |
275 def _SlicedString(self, name): | |
276 s = self._RawRandomString(20, 30) | |
277 # 'ffoo12345678901234567890'.substr(1) | |
278 return self._Variable(name, "\"%s\".substr(1)" % s) | |
279 | |
280 def _ConsString(self, name): | |
281 s1 = self._RawRandomString(8, 15) | |
282 s2 = self._RawRandomString(8, 15) | |
283 # 'foo12345' + (function() { return 'bar12345';})() | |
284 return self._Variable(name, | |
285 "\"%s\" + (function() { return \"%s\";})()" % (s1, s2)) | |
286 | |
287 def _ExternalString(self, name): | |
288 # Needs --expose-externalize-string. | |
289 return None | |
290 | |
291 def _InternalizedString(self, name): | |
292 return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20)) | |
293 | |
294 def _String(self, name, recursion_budget): | |
295 die = random.random() | |
296 if die < 0.5: | |
297 string = random.choice(self.USUAL_SUSPECT_PROPERTIES) | |
298 return self._Variable(name, "\"%s\"" % string) | |
299 elif die < 0.6: | |
300 number_name = name + "_number" | |
301 result = self._Number(number_name, recursion_budget) | |
302 return result + self._Variable(name, "\"\" + %s" % number_name) | |
303 elif die < 0.7: | |
304 return self._SeqString(name, recursion_budget) | |
305 elif die < 0.8: | |
306 return self._ConsString(name) | |
307 elif die < 0.9: | |
308 return self._InternalizedString(name) | |
309 else: | |
310 return self._SlicedString(name) | |
311 | |
312 def _Symbol(self, name, recursion_budget): | |
313 raw_string_name = name + "_1" | |
314 result = self._String(raw_string_name, recursion_budget) | |
315 return result + self._Variable(name, "Symbol(%s)" % raw_string_name) | |
316 | |
317 def _Name(self, name, recursion_budget): | |
318 if random.random() < 0.2: | |
319 return self._Symbol(name, recursion_budget) | |
320 return self._String(name, recursion_budget) | |
321 | |
322 def _JSValue(self, name, recursion_budget): | |
323 die = random.random() | |
324 raw_name = name + "_1" | |
325 if die < 0.33: | |
326 result = self._String(raw_name, recursion_budget) | |
327 return result + self._Variable(name, "new String(%s)" % raw_name) | |
328 elif die < 0.66: | |
329 result = self._Boolean(raw_name, recursion_budget) | |
330 return result + self._Variable(name, "new Boolean(%s)" % raw_name) | |
331 else: | |
332 result = self._Number(raw_name, recursion_budget) | |
333 return result + self._Variable(name, "new Number(%s)" % raw_name) | |
334 | |
335 def _RawRandomPropertyName(self): | |
336 if random.random() < 0.5: | |
337 return random.choice(self.USUAL_SUSPECT_PROPERTIES) | |
338 return self._RawRandomString(0, 10) | |
339 | |
340 def _AddProperties(self, name, result, recursion_budget): | |
341 propcount = random.randint(0, 3) | |
342 propname = None | |
343 for i in range(propcount): | |
344 die = random.random() | |
345 if die < 0.5: | |
346 propname = "%s_prop%d" % (name, i) | |
347 result += self._Name(propname, recursion_budget - 1) | |
348 else: | |
349 propname = "\"%s\"" % self._RawRandomPropertyName() | |
350 propvalue_name = "%s_val%d" % (name, i) | |
351 result += self._Object(propvalue_name, recursion_budget - 1) | |
352 result.append("try { %s[%s] = %s; } catch (e) {}" % | |
353 (name, propname, propvalue_name)) | |
354 if random.random() < 0.2 and propname: | |
355 # Force the object to slow mode. | |
356 result.append("delete %s[%s];" % (name, propname)) | |
357 | |
358 def _RandomElementIndex(self, element_name, result): | |
359 if random.random() < 0.5: | |
360 return random.randint(-1000, 1000) | |
361 result += self._Smi(element_name, 0) | |
362 return element_name | |
363 | |
364 def _AddElements(self, name, result, recursion_budget): | |
365 elementcount = random.randint(0, 3) | |
366 for i in range(elementcount): | |
367 element_name = "%s_idx%d" % (name, i) | |
368 index = self._RandomElementIndex(element_name, result) | |
369 value_name = "%s_elt%d" % (name, i) | |
370 result += self._Object(value_name, recursion_budget - 1) | |
371 result.append("try { %s[%s] = %s; } catch(e) {}" % | |
372 (name, index, value_name)) | |
373 | |
374 def _AddAccessors(self, name, result, recursion_budget): | |
375 accessorcount = random.randint(0, 3) | |
376 for i in range(accessorcount): | |
377 propname = self._RawRandomPropertyName() | |
378 what = random.choice(["get", "set"]) | |
379 function_name = "%s_access%d" % (name, i) | |
380 result += self._PlainFunction(function_name, recursion_budget - 1) | |
381 result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } " | |
382 "catch (e) {}" % (name, propname, what, function_name)) | |
383 | |
384 def _PlainArray(self, name, recursion_budget): | |
385 die = random.random() | |
386 if die < 0.5: | |
387 literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]", | |
388 "['a', 'b', 1, true]"]) | |
389 return self._Variable(name, literal) | |
390 else: | |
391 new = random.choice(["", "new "]) | |
392 length = random.randint(0, 101000) | |
393 return self._Variable(name, "%sArray(%d)" % (new, length)) | |
394 | |
395 def _PlainObject(self, name, recursion_budget): | |
396 die = random.random() | |
397 if die < 0.67: | |
398 literal_propcount = random.randint(0, 3) | |
399 properties = [] | |
400 result = [] | |
401 for i in range(literal_propcount): | |
402 propname = self._RawRandomPropertyName() | |
403 propvalue_name = "%s_lit%d" % (name, i) | |
404 result += self._Object(propvalue_name, recursion_budget - 1) | |
405 properties.append("\"%s\": %s" % (propname, propvalue_name)) | |
406 return result + self._Variable(name, "{%s}" % ", ".join(properties)) | |
407 else: | |
408 return self._Variable(name, "new Object()") | |
409 | |
410 def _JSArray(self, name, recursion_budget): | |
411 result = self._PlainArray(name, recursion_budget) | |
412 self._AddAccessors(name, result, recursion_budget) | |
413 self._AddProperties(name, result, recursion_budget) | |
414 self._AddElements(name, result, recursion_budget) | |
415 return result | |
416 | |
417 def _RawRandomBufferLength(self): | |
418 if random.random() < 0.2: | |
419 return random.choice([0, 1, 8, 0x40000000, 0x80000000]) | |
420 return random.randint(0, 1000) | |
421 | |
422 def _JSArrayBuffer(self, name, recursion_budget): | |
423 length = self._RawRandomBufferLength() | |
424 return self._Variable(name, "new ArrayBuffer(%d)" % length) | |
425 | |
426 def _JSDataView(self, name, recursion_budget): | |
427 buffer_name = name + "_buffer" | |
428 result = self._JSArrayBuffer(buffer_name, recursion_budget) | |
429 args = [buffer_name] | |
430 die = random.random() | |
431 if die < 0.67: | |
432 offset = self._RawRandomBufferLength() | |
433 args.append("%d" % offset) | |
434 if die < 0.33: | |
435 length = self._RawRandomBufferLength() | |
436 args.append("%d" % length) | |
437 result += self._Variable(name, "new DataView(%s)" % ", ".join(args), | |
438 fallback="new DataView(new ArrayBuffer(8))") | |
439 return result | |
440 | |
441 def _JSDate(self, name, recursion_budget): | |
442 die = random.random() | |
443 if die < 0.25: | |
444 return self._Variable(name, "new Date()") | |
445 elif die < 0.5: | |
446 ms_name = name + "_ms" | |
447 result = self._Number(ms_name, recursion_budget) | |
448 return result + self._Variable(name, "new Date(%s)" % ms_name) | |
449 elif die < 0.75: | |
450 str_name = name + "_str" | |
451 month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", | |
452 "Aug", "Sep", "Oct", "Nov", "Dec"]) | |
453 day = random.randint(1, 28) | |
454 year = random.randint(1900, 2100) | |
455 hour = random.randint(0, 23) | |
456 minute = random.randint(0, 59) | |
457 second = random.randint(0, 59) | |
458 str_value = ("\"%s %s, %s %s:%s:%s\"" % | |
459 (month, day, year, hour, minute, second)) | |
460 result = self._Variable(str_name, str_value) | |
461 return result + self._Variable(name, "new Date(%s)" % str_name) | |
462 else: | |
463 components = tuple(map(lambda x: "%s_%s" % (name, x), | |
464 ["y", "m", "d", "h", "min", "s", "ms"])) | |
465 return ([j for i in map(self._Int32, components) for j in i] + | |
466 self._Variable(name, "new Date(%s)" % ", ".join(components))) | |
467 | |
468 def _PlainFunction(self, name, recursion_budget): | |
469 result_name = "result" | |
470 body = ["function() {"] | |
471 body += self._Object(result_name, recursion_budget - 1) | |
472 body.append("return result;\n}") | |
473 return self._Variable(name, "%s" % "\n".join(body)) | |
474 | |
475 def _JSFunction(self, name, recursion_budget): | |
476 result = self._PlainFunction(name, recursion_budget) | |
477 self._AddAccessors(name, result, recursion_budget) | |
478 self._AddProperties(name, result, recursion_budget) | |
479 self._AddElements(name, result, recursion_budget) | |
480 return result | |
481 | |
482 def _JSFunctionProxy(self, name, recursion_budget): | |
483 # TODO(jkummerow): Revisit this as the Proxy implementation evolves. | |
484 return self._Variable(name, "Proxy.createFunction(%s, function() {})" % | |
485 self.PROXY_TRAPS) | |
486 | |
487 def _JSGeneratorObject(self, name, recursion_budget): | |
488 # TODO(jkummerow): Be more creative here? | |
489 return self._Variable(name, "(function*() { yield 1; })()") | |
490 | |
491 def _JSMap(self, name, recursion_budget, weak=""): | |
492 result = self._Variable(name, "new %sMap()" % weak) | |
493 num_entries = random.randint(0, 3) | |
494 for i in range(num_entries): | |
495 key_name = "%s_k%d" % (name, i) | |
496 value_name = "%s_v%d" % (name, i) | |
497 if weak: | |
498 result += self._JSObject(key_name, recursion_budget - 1) | |
499 else: | |
500 result += self._Object(key_name, recursion_budget - 1) | |
501 result += self._Object(value_name, recursion_budget - 1) | |
502 result.append("%s.set(%s, %s)" % (name, key_name, value_name)) | |
503 return result | |
504 | |
505 def _JSMapIterator(self, name, recursion_budget): | |
506 map_name = name + "_map" | |
507 result = self._JSMap(map_name, recursion_budget) | |
508 iterator_type = random.randint(1, 3) | |
509 return (result + self._Variable(name, "%%MapCreateIterator(%s, %d)" % | |
510 (map_name, iterator_type))) | |
511 | |
512 def _JSProxy(self, name, recursion_budget): | |
513 # TODO(jkummerow): Revisit this as the Proxy implementation evolves. | |
514 return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS) | |
515 | |
516 def _JSRegExp(self, name, recursion_budget): | |
517 flags = random.choice(["", "g", "i", "m", "gi"]) | |
518 string = "a(b|c)*a" # TODO(jkummerow): Be more creative here? | |
519 ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"]) | |
520 return self._Variable(name, ctor % (string, flags)) | |
521 | |
522 def _JSSet(self, name, recursion_budget, weak=""): | |
523 result = self._Variable(name, "new %sSet()" % weak) | |
524 num_entries = random.randint(0, 3) | |
525 for i in range(num_entries): | |
526 element_name = "%s_e%d" % (name, i) | |
527 if weak: | |
528 result += self._JSObject(element_name, recursion_budget - 1) | |
529 else: | |
530 result += self._Object(element_name, recursion_budget - 1) | |
531 result.append("%s.add(%s)" % (name, element_name)) | |
532 return result | |
533 | |
534 def _JSSetIterator(self, name, recursion_budget): | |
535 set_name = name + "_set" | |
536 result = self._JSSet(set_name, recursion_budget) | |
537 iterator_type = random.randint(2, 3) | |
538 return (result + self._Variable(name, "%%SetCreateIterator(%s, %d)" % | |
539 (set_name, iterator_type))) | |
540 | |
541 def _JSTypedArray(self, name, recursion_budget): | |
542 arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16", | |
543 "Uint32", "Float32", "Float64", "Uint8Clamped"]) | |
544 ctor_type = random.randint(0, 3) | |
545 if ctor_type == 0: | |
546 length = random.randint(0, 1000) | |
547 return self._Variable(name, "new %sArray(%d)" % (arraytype, length), | |
548 fallback="new %sArray(8)" % arraytype) | |
549 elif ctor_type == 1: | |
550 input_name = name + "_typedarray" | |
551 result = self._JSTypedArray(input_name, recursion_budget - 1) | |
552 return (result + | |
553 self._Variable(name, "new %sArray(%s)" % (arraytype, input_name), | |
554 fallback="new %sArray(8)" % arraytype)) | |
555 elif ctor_type == 2: | |
556 arraylike_name = name + "_arraylike" | |
557 result = self._JSObject(arraylike_name, recursion_budget - 1) | |
558 length = random.randint(0, 1000) | |
559 result.append("try { %s.length = %d; } catch(e) {}" % | |
560 (arraylike_name, length)) | |
561 return (result + | |
562 self._Variable(name, | |
563 "new %sArray(%s)" % (arraytype, arraylike_name), | |
564 fallback="new %sArray(8)" % arraytype)) | |
565 else: | |
566 die = random.random() | |
567 buffer_name = name + "_buffer" | |
568 args = [buffer_name] | |
569 result = self._JSArrayBuffer(buffer_name, recursion_budget) | |
570 if die < 0.67: | |
571 offset_name = name + "_offset" | |
572 args.append(offset_name) | |
573 result += self._Int32(offset_name) | |
574 if die < 0.33: | |
575 length_name = name + "_length" | |
576 args.append(length_name) | |
577 result += self._Int32(length_name) | |
578 return (result + | |
579 self._Variable(name, | |
580 "new %sArray(%s)" % (arraytype, ", ".join(args)), | |
581 fallback="new %sArray(8)" % arraytype)) | |
582 | |
583 def _JSWeakCollection(self, name, recursion_budget): | |
584 ctor = random.choice([self._JSMap, self._JSSet]) | |
585 return ctor(name, recursion_budget, weak="Weak") | |
586 | |
587 def _PropertyDetails(self, name, recursion_budget): | |
588 # TODO(jkummerow): Be more clever here? | |
589 return self._Int32(name) | |
590 | |
591 def _JSObject(self, name, recursion_budget): | |
592 die = random.random() | |
593 if die < 0.4: | |
594 function = random.choice([self._PlainObject, self._PlainArray, | |
595 self._PlainFunction]) | |
596 elif die < 0.5: | |
597 return self._Variable(name, "this") # Global object. | |
598 else: | |
599 function = random.choice([self._JSArrayBuffer, self._JSDataView, | |
600 self._JSDate, self._JSFunctionProxy, | |
601 self._JSGeneratorObject, self._JSMap, | |
602 self._JSMapIterator, self._JSRegExp, | |
603 self._JSSet, self._JSSetIterator, | |
604 self._JSTypedArray, self._JSValue, | |
605 self._JSWeakCollection]) | |
606 result = function(name, recursion_budget) | |
607 self._AddAccessors(name, result, recursion_budget) | |
608 self._AddProperties(name, result, recursion_budget) | |
609 self._AddElements(name, result, recursion_budget) | |
610 return result | |
611 | |
612 def _JSReceiver(self, name, recursion_budget): | |
613 if random.random() < 0.9: return self._JSObject(name, recursion_budget) | |
614 return self._JSProxy(name, recursion_budget) | |
615 | |
616 def _HeapObject(self, name, recursion_budget): | |
617 die = random.random() | |
618 if die < 0.9: return self._JSReceiver(name, recursion_budget) | |
619 elif die < 0.95: return self._Oddball(name, recursion_budget) | |
620 else: return self._Name(name, recursion_budget) | |
621 | |
622 def _Object(self, name, recursion_budget): | |
623 if recursion_budget <= 0: | |
624 function = random.choice([self._Oddball, self._Number, self._Name, | |
625 self._JSValue, self._JSRegExp]) | |
626 return function(name, recursion_budget) | |
627 if random.random() < 0.2: | |
628 return self._Smi(name, recursion_budget) | |
629 return self._HeapObject(name, recursion_budget) | |
630 | |
631 GENERATORS = { | |
632 "Boolean": ["true", _Boolean], | |
633 "HeapObject": ["new Object()", _HeapObject], | |
634 "Int32": ["32", _Int32], | |
635 "JSArray": ["new Array()", _JSArray], | |
636 "JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer], | |
637 "JSDataView": ["new DataView(new ArrayBuffer(8))", _JSDataView], | |
638 "JSDate": ["new Date()", _JSDate], | |
639 "JSFunction": ["function() {}", _JSFunction], | |
640 "JSFunctionProxy": ["Proxy.createFunction({}, function() {})", | |
641 _JSFunctionProxy], | |
642 "JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject], | |
643 "JSMap": ["new Map()", _JSMap], | |
644 "JSMapIterator": ["%MapCreateIterator(new Map(), 3)", _JSMapIterator], | |
645 "JSObject": ["new Object()", _JSObject], | |
646 "JSProxy": ["Proxy.create({})", _JSProxy], | |
647 "JSReceiver": ["new Object()", _JSReceiver], | |
648 "JSRegExp": ["/ab/g", _JSRegExp], | |
649 "JSSet": ["new Set()", _JSSet], | |
650 "JSSetIterator": ["%SetCreateIterator(new Set(), 2)", _JSSetIterator], | |
651 "JSTypedArray": ["new Int32Array(2)", _JSTypedArray], | |
652 "JSValue": ["new String('foo')", _JSValue], | |
653 "JSWeakCollection": ["new WeakMap()", _JSWeakCollection], | |
654 "Name": ["\"name\"", _Name], | |
655 "Number": ["1.5", _Number], | |
656 "Object": ["new Object()", _Object], | |
657 "PropertyDetails": ["513", _PropertyDetails], | |
658 "SeqString": ["\"seqstring\"", _SeqString], | |
659 "Smi": ["1", _Smi], | |
660 "StrictMode": ["1", _StrictMode], | |
661 "String": ["\"foo\"", _String], | |
662 "Symbol": ["Symbol(\"symbol\")", _Symbol], | |
663 "Uint32": ["32", _Uint32], | |
664 } | |
193 | 665 |
194 | 666 |
195 class ArgParser(object): | 667 class ArgParser(object): |
196 def __init__(self, regex, ctor): | 668 def __init__(self, regex, ctor): |
197 self.regex = regex | 669 self.regex = regex |
198 self.ArgCtor = ctor | 670 self.ArgCtor = ctor |
199 | 671 |
200 | 672 |
201 class Arg(object): | 673 class Arg(object): |
202 def __init__(self, typename, varname, index): | 674 def __init__(self, typename, varname, index): |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
247 | 719 |
248 property_details_parser = ArgParser( | 720 property_details_parser = ArgParser( |
249 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"), | 721 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"), |
250 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2)))) | 722 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2)))) |
251 | 723 |
252 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser, | 724 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser, |
253 smi_arg_parser, | 725 smi_arg_parser, |
254 double_arg_parser, number_arg_parser, strict_mode_arg_parser, | 726 double_arg_parser, number_arg_parser, strict_mode_arg_parser, |
255 boolean_arg_parser, property_details_parser] | 727 boolean_arg_parser, property_details_parser] |
256 | 728 |
257 | |
258 def SetArgsLength(self, match): | 729 def SetArgsLength(self, match): |
259 self.argslength = int(match.group(1)) | 730 self.argslength = int(match.group(1)) |
260 | 731 |
261 def TryParseArg(self, line): | 732 def TryParseArg(self, line): |
262 for parser in Function.arg_parsers: | 733 for parser in Function.arg_parsers: |
263 match = parser.regex.match(line) | 734 match = parser.regex.match(line) |
264 if match: | 735 if match: |
265 arg = parser.ArgCtor(match) | 736 arg = parser.ArgCtor(match) |
266 self.args[arg.index] = arg | 737 self.args[arg.index] = arg |
267 return True | 738 return True |
(...skipping 10 matching lines...) Expand all Loading... | |
278 if self.args: | 749 if self.args: |
279 argcount = max([self.args[i].index + 1 for i in self.args]) | 750 argcount = max([self.args[i].index + 1 for i in self.args]) |
280 else: | 751 else: |
281 argcount = 0 | 752 argcount = 0 |
282 for i in range(argcount): | 753 for i in range(argcount): |
283 if i > 0: s.append(", ") | 754 if i > 0: s.append(", ") |
284 s.append(self.args[i].type if i in self.args else "<unknown>") | 755 s.append(self.args[i].type if i in self.args else "<unknown>") |
285 s.append(")") | 756 s.append(")") |
286 return "".join(s) | 757 return "".join(s) |
287 | 758 |
759 | |
288 # Parses HEADERFILENAME to find out which runtime functions are "inline". | 760 # Parses HEADERFILENAME to find out which runtime functions are "inline". |
289 def FindInlineRuntimeFunctions(): | 761 def FindInlineRuntimeFunctions(): |
290 inline_functions = [] | 762 inline_functions = [] |
291 with open(HEADERFILENAME, "r") as f: | 763 with open(HEADERFILENAME, "r") as f: |
292 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n" | 764 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n" |
293 inline_opt_list = "#define INLINE_OPTIMIZED_FUNCTION_LIST(F) \\\n" | 765 inline_opt_list = "#define INLINE_OPTIMIZED_FUNCTION_LIST(F) \\\n" |
294 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?") | 766 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?") |
295 mode = "SEARCHING" | 767 mode = "SEARCHING" |
296 for line in f: | 768 for line in f: |
297 if mode == "ACTIVE": | 769 if mode == "ACTIVE": |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
384 # All good, there's a custom definition. | 856 # All good, there's a custom definition. |
385 pass | 857 pass |
386 elif not i in f.args: | 858 elif not i in f.args: |
387 # No custom definition and no parse result -> give up. | 859 # No custom definition and no parse result -> give up. |
388 decision = unknown_functions | 860 decision = unknown_functions |
389 else: | 861 else: |
390 t = f.args[i].type | 862 t = f.args[i].type |
391 if t in NON_JS_TYPES: | 863 if t in NON_JS_TYPES: |
392 decision = cctest_fuzzable_functions | 864 decision = cctest_fuzzable_functions |
393 else: | 865 else: |
394 assert t in JS_TYPE_GENERATORS, \ | 866 assert Generator.IsTypeSupported(t), \ |
395 ("type generator not found for %s, function: %s" % (t, f)) | 867 ("type generator not found for %s, function: %s" % (t, f)) |
396 decision.append(f) | 868 decision.append(f) |
397 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) | 869 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) |
398 | 870 |
399 | 871 |
400 def GenerateJSTestcaseForFunction(f): | 872 def _GetKnownGoodArgs(function, generator): |
873 custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None) | |
874 definitions = [] | |
875 argslist = [] | |
876 for i in range(function.argslength): | |
877 if custom_input and custom_input[i] is not None: | |
878 name = "arg%d" % i | |
879 definitions.append("var %s = %s;" % (name, custom_input[i])) | |
880 else: | |
881 arg = function.args[i] | |
882 name = arg.name | |
883 definitions += generator.RandomVariable(name, arg.type, simple=True) | |
884 argslist.append(name) | |
885 return (definitions, argslist) | |
886 | |
887 | |
888 def _GenerateTestcase(function, definitions, argslist, throws): | |
401 s = ["// Copyright 2014 the V8 project authors. All rights reserved.", | 889 s = ["// Copyright 2014 the V8 project authors. All rights reserved.", |
402 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY", | 890 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY", |
403 "// Flags: --allow-natives-syntax --harmony"] | 891 "// Flags: --allow-natives-syntax --harmony"] + definitions |
404 call = "%%%s%s(" % (f.inline, f.name) | 892 call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist)) |
405 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None) | 893 if throws: |
406 for i in range(f.argslength): | |
407 if custom and custom[i] is not None: | |
408 (name, value) = ("arg%d" % i, custom[i]) | |
409 else: | |
410 arg = f.args[i] | |
411 (name, value) = (arg.name, JS_TYPE_GENERATORS[arg.type]) | |
412 s.append("var %s = %s;" % (name, value)) | |
413 if i > 0: call += ", " | |
414 call += name | |
415 call += ");" | |
416 if f.name in THROWS: | |
417 s.append("try {") | 894 s.append("try {") |
418 s.append(call); | 895 s.append(call); |
419 s.append("} catch(e) {}") | 896 s.append("} catch(e) {}") |
420 else: | 897 else: |
421 s.append(call) | 898 s.append(call) |
422 testcase = "\n".join(s) | 899 testcase = "\n".join(s) |
423 path = os.path.join(BASEPATH, f.Filename()) | 900 return testcase |
901 | |
902 | |
903 def GenerateJSTestcaseForFunction(function): | |
904 gen = Generator() | |
905 (definitions, argslist) = _GetKnownGoodArgs(function, gen) | |
906 testcase = _GenerateTestcase(function, definitions, argslist, | |
907 function.name in THROWS) | |
908 path = os.path.join(BASEPATH, function.Filename()) | |
424 with open(path, "w") as f: | 909 with open(path, "w") as f: |
425 f.write("%s\n" % testcase) | 910 f.write("%s\n" % testcase) |
426 | 911 |
912 | |
427 def GenerateTestcases(functions): | 913 def GenerateTestcases(functions): |
428 shutil.rmtree(BASEPATH) # Re-generate everything. | 914 shutil.rmtree(BASEPATH) # Re-generate everything. |
429 os.makedirs(BASEPATH) | 915 os.makedirs(BASEPATH) |
430 for f in functions: | 916 for f in functions: |
431 GenerateJSTestcaseForFunction(f) | 917 GenerateJSTestcaseForFunction(f) |
432 | 918 |
433 def PrintUsage(): | 919 |
434 print """Usage: %(this_script)s ACTION | 920 def _SaveFileName(save_path, process_id, save_file_index): |
921 return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index) | |
922 | |
923 | |
924 def RunFuzzer(process_id, options, stop_running): | |
925 base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id | |
926 test_file_name = "%s.js" % base_file_name | |
927 stderr_file_name = "%s.out" % base_file_name | |
928 save_file_index = 0 | |
929 while os.path.exists(_SaveFileName(options.save_path, process_id, | |
930 save_file_index)): | |
931 save_file_index += 1 | |
932 MAX_SLEEP_TIME = 0.1 | |
933 INITIAL_SLEEP_TIME = 0.001 | |
934 SLEEP_TIME_FACTOR = 1.5 | |
935 | |
936 functions = FindRuntimeFunctions() | |
937 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ | |
938 ClassifyFunctions(functions) | |
939 | |
940 try: | |
941 for i in range(options.num_tests): | |
942 if stop_running.is_set(): break | |
943 function = random.choice(js_fuzzable_functions) # TODO: others too | |
944 if function.argslength == 0: continue | |
945 args = [] | |
946 definitions = [] | |
947 gen = Generator() | |
948 for i in range(function.argslength): | |
949 arg = function.args[i] | |
950 argname = "arg%d%s" % (i, arg.name) | |
951 args.append(argname) | |
952 definitions += gen.RandomVariable(argname, arg.type, simple=False) | |
953 testcase = _GenerateTestcase(function, definitions, args, True) | |
954 with open(test_file_name, "w") as f: | |
955 f.write("%s\n" % testcase) | |
956 with open("/dev/null", "w") as devnull: | |
957 with open(stderr_file_name, "w") as stderr: | |
958 process = subprocess.Popen( | |
Michael Achenbach
2014/05/16 07:23:52
Maybe unify this with commands.py in the future?
| |
959 [options.binary, "--allow-natives-syntax", "--harmony", | |
960 "--enable-slow-asserts", test_file_name], | |
961 stdout=devnull, stderr=stderr) | |
962 end_time = time.time() + options.timeout | |
963 timed_out = False | |
964 exit_code = None | |
965 sleep_time = INITIAL_SLEEP_TIME | |
966 while exit_code is None: | |
967 if time.time() >= end_time: | |
968 # Kill the process and wait for it to exit. | |
969 os.kill(process.pid, signal.SIGTERM) | |
970 exit_code = process.wait() | |
971 timed_out = True | |
972 else: | |
973 exit_code = process.poll() | |
974 time.sleep(sleep_time) | |
975 sleep_time = sleep_time * SLEEP_TIME_FACTOR | |
976 if sleep_time > MAX_SLEEP_TIME: | |
977 sleep_time = MAX_SLEEP_TIME | |
978 if exit_code != 0 and not timed_out: | |
979 oom = False | |
980 with open(stderr_file_name, "r") as stderr: | |
981 for line in stderr: | |
982 if line.strip() == "# Allocation failed - process out of memory": | |
983 oom = True | |
984 break | |
985 if oom: continue | |
986 save_name = _SaveFileName(options.save_path, process_id, | |
987 save_file_index) | |
988 shutil.copyfile(test_file_name, save_name) | |
989 save_file_index += 1 | |
990 except KeyboardInterrupt: | |
991 stop_running.set() | |
992 except Exception, e: | |
993 print e | |
994 finally: | |
995 os.remove(test_file_name) | |
996 os.remove(stderr_file_name) | |
997 | |
998 | |
999 def BuildOptionParser(): | |
1000 usage = """Usage: %%prog [options] ACTION | |
435 | 1001 |
436 where ACTION can be: | 1002 where ACTION can be: |
437 | 1003 |
438 info Print diagnostic info. | 1004 info Print diagnostic info. |
439 check Check that runtime functions can be parsed as expected, and that | 1005 check Check that runtime functions can be parsed as expected, and that |
440 test cases exist. | 1006 test cases exist. |
441 generate Parse source code for runtime functions, and auto-generate | 1007 generate Parse source code for runtime functions, and auto-generate |
442 test cases for them. Warning: this will nuke and re-create | 1008 test cases for them. Warning: this will nuke and re-create |
443 %(path)s. | 1009 %(path)s. |
444 """ % {"path": os.path.relpath(BASEPATH), "this_script": THIS_SCRIPT} | 1010 fuzz Generate fuzz tests, run them, save those that crashed (see options). |
1011 """ % {"path": os.path.relpath(BASEPATH)} | |
445 | 1012 |
446 if __name__ == "__main__": | 1013 o = optparse.OptionParser(usage=usage) |
447 if len(sys.argv) != 2: | 1014 o.add_option("--binary", default="out/x64.debug/d8", |
448 PrintUsage() | 1015 help="d8 binary used for running fuzz tests (default: %default)") |
449 sys.exit(1) | 1016 o.add_option("-n", "--num-tests", default=1000, type="int", |
450 action = sys.argv[1] | 1017 help="Number of fuzz tests to generate per worker process" |
451 if action in ["-h", "--help", "help"]: | 1018 " (default: %default)") |
452 PrintUsage() | 1019 o.add_option("--save-path", default="~/runtime_fuzz_output", |
453 sys.exit(0) | 1020 help="Path to directory where failing tests will be stored" |
1021 " (default: %default)") | |
1022 o.add_option("--timeout", default=20, type="int", | |
1023 help="Timeout for each fuzz test (in seconds, default:" | |
1024 "%default)") | |
1025 return o | |
1026 | |
1027 | |
1028 def Main(): | |
1029 parser = BuildOptionParser() | |
1030 (options, args) = parser.parse_args() | |
1031 options.save_path = os.path.expanduser(options.save_path) | |
1032 | |
1033 if len(args) != 1 or args[0] == "help": | |
1034 parser.print_help() | |
1035 return 1 | |
1036 action = args[0] | |
454 | 1037 |
455 functions = FindRuntimeFunctions() | 1038 functions = FindRuntimeFunctions() |
456 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ | 1039 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ |
457 ClassifyFunctions(functions) | 1040 ClassifyFunctions(functions) |
458 | 1041 |
1042 if action == "test": | |
1043 gen = Generator() | |
1044 vartype = "JSTypedArray" | |
1045 print("simple: %s" % gen.RandomVariable("x", vartype, True)) | |
1046 for i in range(10): | |
1047 print("----") | |
1048 print("%s" % "\n".join(gen.RandomVariable("x", vartype, False))) | |
1049 return 0 | |
1050 | |
459 if action == "info": | 1051 if action == "info": |
460 print("%d functions total; js_fuzzable_functions: %d, " | 1052 print("%d functions total; js_fuzzable_functions: %d, " |
461 "cctest_fuzzable_functions: %d, unknown_functions: %d" | 1053 "cctest_fuzzable_functions: %d, unknown_functions: %d" |
462 % (len(functions), len(js_fuzzable_functions), | 1054 % (len(functions), len(js_fuzzable_functions), |
463 len(cctest_fuzzable_functions), len(unknown_functions))) | 1055 len(cctest_fuzzable_functions), len(unknown_functions))) |
464 print("unknown functions:") | 1056 print("unknown functions:") |
465 for f in unknown_functions: | 1057 for f in unknown_functions: |
466 print(f) | 1058 print(f) |
467 sys.exit(0) | 1059 return 0 |
468 | 1060 |
469 if action == "check": | 1061 if action == "check": |
470 error = False | 1062 errors = 0 |
1063 | |
471 def CheckCount(actual, expected, description): | 1064 def CheckCount(actual, expected, description): |
472 global error | |
473 if len(actual) != expected: | 1065 if len(actual) != expected: |
474 print("Expected to detect %d %s, but found %d." % ( | 1066 print("Expected to detect %d %s, but found %d." % ( |
475 expected, description, len(actual))) | 1067 expected, description, len(actual))) |
476 print("If this change is intentional, please update the expectations" | 1068 print("If this change is intentional, please update the expectations" |
477 " at the top of %s." % THIS_SCRIPT) | 1069 " at the top of %s." % THIS_SCRIPT) |
478 error = True | 1070 return 1 |
479 CheckCount(functions, EXPECTED_FUNCTION_COUNT, "functions in total") | 1071 return 0 |
480 CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, | 1072 |
481 "JavaScript-fuzzable functions") | 1073 errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT, |
482 CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, | 1074 "functions in total") |
483 "cctest-fuzzable functions") | 1075 errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, |
484 CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, | 1076 "JavaScript-fuzzable functions") |
485 "functions with incomplete type information") | 1077 errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, |
1078 "cctest-fuzzable functions") | |
1079 errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, | |
1080 "functions with incomplete type information") | |
486 | 1081 |
487 def CheckTestcasesExisting(functions): | 1082 def CheckTestcasesExisting(functions): |
488 global error | 1083 errors = 0 |
489 for f in functions: | 1084 for f in functions: |
490 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())): | 1085 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())): |
491 print("Missing testcase for %s, please run '%s generate'" % | 1086 print("Missing testcase for %s, please run '%s generate'" % |
492 (f.name, THIS_SCRIPT)) | 1087 (f.name, THIS_SCRIPT)) |
493 error = True | 1088 errors += 1 |
494 files = filter(lambda filename: not filename.startswith("."), | 1089 files = filter(lambda filename: not filename.startswith("."), |
495 os.listdir(BASEPATH)) | 1090 os.listdir(BASEPATH)) |
496 if (len(files) != len(functions)): | 1091 if (len(files) != len(functions)): |
497 unexpected_files = set(files) - set([f.Filename() for f in functions]) | 1092 unexpected_files = set(files) - set([f.Filename() for f in functions]) |
498 for f in unexpected_files: | 1093 for f in unexpected_files: |
499 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f)) | 1094 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f)) |
500 error = True | 1095 errors += 1 |
501 print("Run '%s generate' to automatically clean these up." | 1096 print("Run '%s generate' to automatically clean these up." |
502 % THIS_SCRIPT) | 1097 % THIS_SCRIPT) |
503 CheckTestcasesExisting(js_fuzzable_functions) | 1098 return errors |
504 | 1099 |
505 if error: | 1100 errors += CheckTestcasesExisting(js_fuzzable_functions) |
506 sys.exit(1) | 1101 |
1102 if errors > 0: | |
1103 return 1 | |
507 print("Generated runtime tests: all good.") | 1104 print("Generated runtime tests: all good.") |
508 sys.exit(0) | 1105 return 0 |
509 | 1106 |
510 if action == "generate": | 1107 if action == "generate": |
511 GenerateTestcases(js_fuzzable_functions) | 1108 GenerateTestcases(js_fuzzable_functions) |
512 sys.exit(0) | 1109 return 0 |
1110 | |
1111 if action == "fuzz": | |
1112 processes = [] | |
1113 if not os.path.isdir(options.save_path): | |
1114 os.makedirs(options.save_path) | |
1115 stop_running = multiprocessing.Event() | |
1116 for i in range(multiprocessing.cpu_count()): | |
1117 args = (i, options, stop_running) | |
1118 p = multiprocessing.Process(target=RunFuzzer, args=args) | |
1119 p.start() | |
1120 processes.append(p) | |
1121 try: | |
1122 for i in range(len(processes)): | |
1123 processes[i].join() | |
1124 except KeyboardInterrupt: | |
1125 stop_running.set() | |
1126 for i in range(len(processes)): | |
1127 processes[i].join() | |
1128 return 0 | |
1129 | |
1130 if __name__ == "__main__": | |
1131 sys.exit(Main()) | |
OLD | NEW |