Chromium Code Reviews| Index: tools/generate-runtime-tests.py |
| diff --git a/tools/generate-runtime-tests.py b/tools/generate-runtime-tests.py |
| index 63f417e0ee48aab599f37a70cf20e0c49ecfa174..5ce4c9efddc2485dc8b60a2abd61e37834e7d45d 100755 |
| --- a/tools/generate-runtime-tests.py |
| +++ b/tools/generate-runtime-tests.py |
| @@ -3,10 +3,18 @@ |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| +import itertools |
| +import multiprocessing |
| +import optparse |
| import os |
| +import random |
| import re |
| import shutil |
| +import signal |
| +import string |
| +import subprocess |
| import sys |
| +import time |
| # TODO(jkummerow): Support DATA_VIEW_{G,S}ETTER in runtime.cc |
| @@ -156,40 +164,504 @@ NON_JS_TYPES = [ |
| "SharedFunctionInfo"] |
| -# Maps argument types to concrete example inputs of that type. |
| -JS_TYPE_GENERATORS = { |
| - "Boolean": "true", |
| - "HeapObject": "new Object()", |
| - "Int32": "32", |
| - "JSArray": "new Array()", |
| - "JSArrayBuffer": "new ArrayBuffer(8)", |
| - "JSDataView": "new DataView(new ArrayBuffer(8))", |
| - "JSDate": "new Date()", |
| - "JSFunction": "function() {}", |
| - "JSFunctionProxy": "Proxy.createFunction({}, function() {})", |
| - "JSGeneratorObject": "(function*(){ yield 1; })()", |
| - "JSMap": "new Map()", |
| - "JSMapIterator": "%MapCreateIterator(new Map(), 3)", |
| - "JSObject": "new Object()", |
| - "JSProxy": "Proxy.create({})", |
| - "JSReceiver": "new Object()", |
| - "JSRegExp": "/ab/g", |
| - "JSSet": "new Set()", |
| - "JSSetIterator": "%SetCreateIterator(new Set(), 2)", |
| - "JSTypedArray": "new Int32Array(2)", |
| - "JSValue": "new String('foo')", |
| - "JSWeakCollection": "new WeakMap()", |
| - "Name": "\"name\"", |
| - "Number": "1.5", |
| - "Object": "new Object()", |
| - "PropertyDetails": "513", |
| - "SeqString": "\"seqstring\"", |
| - "Smi": 1, |
| - "StrictMode": "1", |
| - "String": "\"foo\"", |
| - "Symbol": "Symbol(\"symbol\")", |
| - "Uint32": "32", |
| -} |
| +class Generator(object): |
| + |
| + def RandomVariable(self, varname, vartype, simple): |
| + if simple: |
| + return self._Variable(varname, self.GENERATORS[vartype][0]) |
| + return self.GENERATORS[vartype][1](self, varname, |
| + self.DEFAULT_RECURSION_BUDGET) |
| + |
| + @staticmethod |
| + def IsTypeSupported(typename): |
| + return typename in Generator.GENERATORS |
| + |
| + USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__", |
| + "prototype", "0", "1", "-1"] |
| + DEFAULT_RECURSION_BUDGET = 2 |
| + PROXY_TRAPS = """{ |
| + getOwnPropertyDescriptor: function(name) { |
| + return {value: function() {}, configurable: true, writable: true, |
| + enumerable: true}; |
| + }, |
| + getPropertyDescriptor: function(name) { |
| + return {value: function() {}, configurable: true, writable: true, |
| + enumerable: true}; |
| + }, |
| + getOwnPropertyNames: function() { return []; }, |
| + getPropertyNames: function() { return []; }, |
| + defineProperty: function(name, descriptor) {}, |
| + delete: function(name) { return true; }, |
| + fix: function() {} |
| + }""" |
| + |
| + def _Variable(self, name, value, fallback=None): |
| + args = { "name": name, "value": value, "fallback": fallback } |
| + if fallback: |
| + wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args |
| + else: |
| + wrapper = "%s" |
| + return [wrapper % ("var %(name)s = %(value)s;" % args)] |
| + |
| + def _Boolean(self, name, recursion_budget): |
| + return self._Variable(name, random.choice(["true", "false"])) |
| + |
| + def _Oddball(self, name, recursion_budget): |
| + return self._Variable(name, |
| + random.choice(["true", "false", "undefined", "null"])) |
| + |
| + def _StrictMode(self, name, recursion_budget): |
| + return self._Variable(name, random.choice([0, 1])) |
| + |
| + def _Int32(self, name, recursion_budget=0): |
| + die = random.random() |
| + if die < 0.5: |
| + value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff, |
| + 0x40000000, -0x40000000, -0x80000000]) |
| + elif die < 0.75: |
| + value = random.randint(-1000, 1000) |
| + else: |
| + value = random.randint(-0x80000000, 0x7fffffff) |
| + return self._Variable(name, value) |
| + |
| + def _Uint32(self, name, recursion_budget=0): |
| + die = random.random() |
| + if die < 0.5: |
| + value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000, |
| + 0x7fffffff, 0xffffffff]) |
| + elif die < 0.75: |
| + value = random.randint(0, 1000) |
| + else: |
| + value = random.randint(0, 0xffffffff) |
| + return self._Variable(name, value) |
| + |
| + def _Smi(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.5: |
| + value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000]) |
| + elif die < 0.75: |
| + value = random.randint(-1000, 1000) |
| + else: |
| + value = random.randint(-0x40000000, 0x3fffffff) |
| + return self._Variable(name, value) |
| + |
| + def _Number(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.5: |
| + return self._Smi(name, recursion_budget) |
| + elif die < 0.6: |
| + value = random.choice(["Infinity", "-Infinity", "NaN", "-0", |
| + "1.7976931348623157e+308", # Max value. |
| + "2.2250738585072014e-308", # Min value. |
| + "4.9406564584124654e-324"]) # Min subnormal. |
| + else: |
| + value = random.lognormvariate(0, 15) |
| + return self._Variable(name, value) |
| + |
| + def _RawRandomString(self, minlength=0, maxlength=100, |
| + alphabet=string.ascii_letters): |
| + length = random.randint(minlength, maxlength) |
| + result = "" |
| + for i in xrange(length): |
| + result += random.choice(alphabet) |
| + return result |
| + |
| + def _SeqString(self, name, recursion_budget): |
| + s1 = self._RawRandomString(1, 5) |
| + s2 = self._RawRandomString(1, 5) |
| + # 'foo' + 'bar' |
| + return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2)) |
| + |
| + def _SlicedString(self, name): |
| + s = self._RawRandomString(20, 30) |
| + # 'ffoo12345678901234567890'.substr(1) |
| + return self._Variable(name, "\"%s\".substr(1)" % s) |
| + |
| + def _ConsString(self, name): |
| + s1 = self._RawRandomString(8, 15) |
| + s2 = self._RawRandomString(8, 15) |
| + # 'foo12345' + (function() { return 'bar12345';})() |
| + return self._Variable(name, |
| + "\"%s\" + (function() { return \"%s\";})()" % (s1, s2)) |
| + |
| + def _ExternalString(self, name): |
| + # Needs --expose-externalize-string. |
| + return None |
| + |
| + def _InternalizedString(self, name): |
| + return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20)) |
| + |
| + def _String(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.5: |
| + string = random.choice(self.USUAL_SUSPECT_PROPERTIES) |
| + return self._Variable(name, "\"%s\"" % string) |
| + elif die < 0.6: |
| + number_name = name + "_number" |
| + result = self._Number(number_name, recursion_budget) |
| + return result + self._Variable(name, "\"\" + %s" % number_name) |
| + elif die < 0.7: |
| + return self._SeqString(name, recursion_budget) |
| + elif die < 0.8: |
| + return self._ConsString(name) |
| + elif die < 0.9: |
| + return self._InternalizedString(name) |
| + else: |
| + return self._SlicedString(name) |
| + |
| + def _Symbol(self, name, recursion_budget): |
| + raw_string_name = name + "_1" |
| + result = self._String(raw_string_name, recursion_budget) |
| + return result + self._Variable(name, "Symbol(%s)" % raw_string_name) |
| + |
| + def _Name(self, name, recursion_budget): |
| + if random.random() < 0.2: |
| + return self._Symbol(name, recursion_budget) |
| + return self._String(name, recursion_budget) |
| + |
| + def _JSValue(self, name, recursion_budget): |
| + die = random.random() |
| + raw_name = name + "_1" |
| + if die < 0.33: |
| + result = self._String(raw_name, recursion_budget) |
| + return result + self._Variable(name, "new String(%s)" % raw_name) |
| + elif die < 0.66: |
| + result = self._Boolean(raw_name, recursion_budget) |
| + return result + self._Variable(name, "new Boolean(%s)" % raw_name) |
| + else: |
| + result = self._Number(raw_name, recursion_budget) |
| + return result + self._Variable(name, "new Number(%s)" % raw_name) |
| + |
| + def _RawRandomPropertyName(self): |
| + if random.random() < 0.5: |
| + return random.choice(self.USUAL_SUSPECT_PROPERTIES) |
| + return self._RawRandomString(0, 10) |
| + |
| + def _AddProperties(self, name, result, recursion_budget): |
| + propcount = random.randint(0, 3) |
| + propname = None |
| + for i in range(propcount): |
| + die = random.random() |
| + if die < 0.5: |
| + propname = "%s_prop%d" % (name, i) |
| + result += self._Name(propname, recursion_budget - 1) |
| + else: |
| + propname = "\"%s\"" % self._RawRandomPropertyName() |
| + propvalue_name = "%s_val%d" % (name, i) |
| + result += self._Object(propvalue_name, recursion_budget - 1) |
| + result.append("try { %s[%s] = %s; } catch (e) {}" % |
| + (name, propname, propvalue_name)) |
| + if random.random() < 0.2 and propname: |
| + # Force the object to slow mode. |
| + result.append("delete %s[%s];" % (name, propname)) |
| + |
| + def _RandomElementIndex(self, element_name, result): |
| + if random.random() < 0.5: |
| + return random.randint(-1000, 1000) |
| + result += self._Smi(element_name, 0) |
| + return element_name |
| + |
| + def _AddElements(self, name, result, recursion_budget): |
| + elementcount = random.randint(0, 3) |
| + for i in range(elementcount): |
| + element_name = "%s_idx%d" % (name, i) |
| + index = self._RandomElementIndex(element_name, result) |
| + value_name = "%s_elt%d" % (name, i) |
| + result += self._Object(value_name, recursion_budget - 1) |
| + result.append("try { %s[%s] = %s; } catch(e) {}" % |
| + (name, index, value_name)) |
| + |
| + def _AddAccessors(self, name, result, recursion_budget): |
| + accessorcount = random.randint(0, 3) |
| + for i in range(accessorcount): |
| + propname = self._RawRandomPropertyName() |
| + what = random.choice(["get", "set"]) |
| + function_name = "%s_access%d" % (name, i) |
| + result += self._PlainFunction(function_name, recursion_budget - 1) |
| + result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } " |
| + "catch (e) {}" % (name, propname, what, function_name)) |
| + |
| + def _PlainArray(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.5: |
| + literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]", |
| + "['a', 'b', 1, true]"]) |
| + return self._Variable(name, literal) |
| + else: |
| + new = random.choice(["", "new "]) |
| + length = random.randint(0, 101000) |
| + return self._Variable(name, "%sArray(%d)" % (new, length)) |
| + |
| + def _PlainObject(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.67: |
| + literal_propcount = random.randint(0, 3) |
| + properties = [] |
| + result = [] |
| + for i in range(literal_propcount): |
| + propname = self._RawRandomPropertyName() |
| + propvalue_name = "%s_lit%d" % (name, i) |
| + result += self._Object(propvalue_name, recursion_budget - 1) |
| + properties.append("\"%s\": %s" % (propname, propvalue_name)) |
| + return result + self._Variable(name, "{%s}" % ", ".join(properties)) |
| + else: |
| + return self._Variable(name, "new Object()") |
| + |
| + def _JSArray(self, name, recursion_budget): |
| + result = self._PlainArray(name, recursion_budget) |
| + self._AddAccessors(name, result, recursion_budget) |
| + self._AddProperties(name, result, recursion_budget) |
| + self._AddElements(name, result, recursion_budget) |
| + return result |
| + |
| + def _RawRandomBufferLength(self): |
| + if random.random() < 0.2: |
| + return random.choice([0, 1, 8, 0x40000000, 0x80000000]) |
| + return random.randint(0, 1000) |
| + |
| + def _JSArrayBuffer(self, name, recursion_budget): |
| + length = self._RawRandomBufferLength() |
| + return self._Variable(name, "new ArrayBuffer(%d)" % length) |
| + |
| + def _JSDataView(self, name, recursion_budget): |
| + buffer_name = name + "_buffer" |
| + result = self._JSArrayBuffer(buffer_name, recursion_budget) |
| + args = [buffer_name] |
| + die = random.random() |
| + if die < 0.67: |
| + offset = self._RawRandomBufferLength() |
| + args.append("%d" % offset) |
| + if die < 0.33: |
| + length = self._RawRandomBufferLength() |
| + args.append("%d" % length) |
| + result += self._Variable(name, "new DataView(%s)" % ", ".join(args), |
| + fallback="new DataView(new ArrayBuffer(8))") |
| + return result |
| + |
| + def _JSDate(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.25: |
| + return self._Variable(name, "new Date()") |
| + elif die < 0.5: |
| + ms_name = name + "_ms" |
| + result = self._Number(ms_name, recursion_budget) |
| + return result + self._Variable(name, "new Date(%s)" % ms_name) |
| + elif die < 0.75: |
| + str_name = name + "_str" |
| + month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", |
| + "Aug", "Sep", "Oct", "Nov", "Dec"]) |
| + day = random.randint(1, 28) |
| + year = random.randint(1900, 2100) |
| + hour = random.randint(0, 23) |
| + minute = random.randint(0, 59) |
| + second = random.randint(0, 59) |
| + str_value = ("\"%s %s, %s %s:%s:%s\"" % |
| + (month, day, year, hour, minute, second)) |
| + result = self._Variable(str_name, str_value) |
| + return result + self._Variable(name, "new Date(%s)" % str_name) |
| + else: |
| + components = tuple(map(lambda x: "%s_%s" % (name, x), |
| + ["y", "m", "d", "h", "min", "s", "ms"])) |
| + return ([j for i in map(self._Int32, components) for j in i] + |
| + self._Variable(name, "new Date(%s)" % ", ".join(components))) |
| + |
| + def _PlainFunction(self, name, recursion_budget): |
| + result_name = "result" |
| + body = ["function() {"] |
| + body += self._Object(result_name, recursion_budget - 1) |
| + body.append("return result;\n}") |
| + return self._Variable(name, "%s" % "\n".join(body)) |
| + |
| + def _JSFunction(self, name, recursion_budget): |
| + result = self._PlainFunction(name, recursion_budget) |
| + self._AddAccessors(name, result, recursion_budget) |
| + self._AddProperties(name, result, recursion_budget) |
| + self._AddElements(name, result, recursion_budget) |
| + return result |
| + |
| + def _JSFunctionProxy(self, name, recursion_budget): |
| + # TODO(jkummerow): Revisit this as the Proxy implementation evolves. |
| + return self._Variable(name, "Proxy.createFunction(%s, function() {})" % |
| + self.PROXY_TRAPS) |
| + |
| + def _JSGeneratorObject(self, name, recursion_budget): |
| + # TODO(jkummerow): Be more creative here? |
| + return self._Variable(name, "(function*() { yield 1; })()") |
| + |
| + def _JSMap(self, name, recursion_budget, weak=""): |
| + result = self._Variable(name, "new %sMap()" % weak) |
| + num_entries = random.randint(0, 3) |
| + for i in range(num_entries): |
| + key_name = "%s_k%d" % (name, i) |
| + value_name = "%s_v%d" % (name, i) |
| + if weak: |
| + result += self._JSObject(key_name, recursion_budget - 1) |
| + else: |
| + result += self._Object(key_name, recursion_budget - 1) |
| + result += self._Object(value_name, recursion_budget - 1) |
| + result.append("%s.set(%s, %s)" % (name, key_name, value_name)) |
| + return result |
| + |
| + def _JSMapIterator(self, name, recursion_budget): |
| + map_name = name + "_map" |
| + result = self._JSMap(map_name, recursion_budget) |
| + iterator_type = random.randint(1, 3) |
| + return (result + self._Variable(name, "%%MapCreateIterator(%s, %d)" % |
| + (map_name, iterator_type))) |
| + |
| + def _JSProxy(self, name, recursion_budget): |
| + # TODO(jkummerow): Revisit this as the Proxy implementation evolves. |
| + return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS) |
| + |
| + def _JSRegExp(self, name, recursion_budget): |
| + flags = random.choice(["", "g", "i", "m", "gi"]) |
| + string = "a(b|c)*a" # TODO(jkummerow): Be more creative here? |
| + ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"]) |
| + return self._Variable(name, ctor % (string, flags)) |
| + |
| + def _JSSet(self, name, recursion_budget, weak=""): |
| + result = self._Variable(name, "new %sSet()" % weak) |
| + num_entries = random.randint(0, 3) |
| + for i in range(num_entries): |
| + element_name = "%s_e%d" % (name, i) |
| + if weak: |
| + result += self._JSObject(element_name, recursion_budget - 1) |
| + else: |
| + result += self._Object(element_name, recursion_budget - 1) |
| + result.append("%s.add(%s)" % (name, element_name)) |
| + return result |
| + |
| + def _JSSetIterator(self, name, recursion_budget): |
| + set_name = name + "_set" |
| + result = self._JSSet(set_name, recursion_budget) |
| + iterator_type = random.randint(2, 3) |
| + return (result + self._Variable(name, "%%SetCreateIterator(%s, %d)" % |
| + (set_name, iterator_type))) |
| + |
| + def _JSTypedArray(self, name, recursion_budget): |
| + arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16", |
| + "Uint32", "Float32", "Float64", "Uint8Clamped"]) |
| + ctor_type = random.randint(0, 3) |
| + if ctor_type == 0: |
| + length = random.randint(0, 1000) |
| + return self._Variable(name, "new %sArray(%d)" % (arraytype, length), |
| + fallback="new %sArray(8)" % arraytype) |
| + elif ctor_type == 1: |
| + input_name = name + "_typedarray" |
| + result = self._JSTypedArray(input_name, recursion_budget - 1) |
| + return (result + |
| + self._Variable(name, "new %sArray(%s)" % (arraytype, input_name), |
| + fallback="new %sArray(8)" % arraytype)) |
| + elif ctor_type == 2: |
| + arraylike_name = name + "_arraylike" |
| + result = self._JSObject(arraylike_name, recursion_budget - 1) |
| + length = random.randint(0, 1000) |
| + result.append("try { %s.length = %d; } catch(e) {}" % |
| + (arraylike_name, length)) |
| + return (result + |
| + self._Variable(name, |
| + "new %sArray(%s)" % (arraytype, arraylike_name), |
| + fallback="new %sArray(8)" % arraytype)) |
| + else: |
| + die = random.random() |
| + buffer_name = name + "_buffer" |
| + args = [buffer_name] |
| + result = self._JSArrayBuffer(buffer_name, recursion_budget) |
| + if die < 0.67: |
| + offset_name = name + "_offset" |
| + args.append(offset_name) |
| + result += self._Int32(offset_name) |
| + if die < 0.33: |
| + length_name = name + "_length" |
| + args.append(length_name) |
| + result += self._Int32(length_name) |
| + return (result + |
| + self._Variable(name, |
| + "new %sArray(%s)" % (arraytype, ", ".join(args)), |
| + fallback="new %sArray(8)" % arraytype)) |
| + |
| + def _JSWeakCollection(self, name, recursion_budget): |
| + ctor = random.choice([self._JSMap, self._JSSet]) |
| + return ctor(name, recursion_budget, weak="Weak") |
| + |
| + def _PropertyDetails(self, name, recursion_budget): |
| + # TODO(jkummerow): Be more clever here? |
| + return self._Int32(name) |
| + |
| + def _JSObject(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.4: |
| + function = random.choice([self._PlainObject, self._PlainArray, |
| + self._PlainFunction]) |
| + elif die < 0.5: |
| + return self._Variable(name, "this") # Global object. |
| + else: |
| + function = random.choice([self._JSArrayBuffer, self._JSDataView, |
| + self._JSDate, self._JSFunctionProxy, |
| + self._JSGeneratorObject, self._JSMap, |
| + self._JSMapIterator, self._JSRegExp, |
| + self._JSSet, self._JSSetIterator, |
| + self._JSTypedArray, self._JSValue, |
| + self._JSWeakCollection]) |
| + result = function(name, recursion_budget) |
| + self._AddAccessors(name, result, recursion_budget) |
| + self._AddProperties(name, result, recursion_budget) |
| + self._AddElements(name, result, recursion_budget) |
| + return result |
| + |
| + def _JSReceiver(self, name, recursion_budget): |
| + if random.random() < 0.9: return self._JSObject(name, recursion_budget) |
| + return self._JSProxy(name, recursion_budget) |
| + |
| + def _HeapObject(self, name, recursion_budget): |
| + die = random.random() |
| + if die < 0.9: return self._JSReceiver(name, recursion_budget) |
| + elif die < 0.95: return self._Oddball(name, recursion_budget) |
| + else: return self._Name(name, recursion_budget) |
| + |
| + def _Object(self, name, recursion_budget): |
| + if recursion_budget <= 0: |
| + function = random.choice([self._Oddball, self._Number, self._Name, |
| + self._JSValue, self._JSRegExp]) |
| + return function(name, recursion_budget) |
| + if random.random() < 0.2: |
| + return self._Smi(name, recursion_budget) |
| + return self._HeapObject(name, recursion_budget) |
| + |
| + GENERATORS = { |
| + "Boolean": ["true", _Boolean], |
| + "HeapObject": ["new Object()", _HeapObject], |
| + "Int32": ["32", _Int32], |
| + "JSArray": ["new Array()", _JSArray], |
| + "JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer], |
| + "JSDataView": ["new DataView(new ArrayBuffer(8))", _JSDataView], |
| + "JSDate": ["new Date()", _JSDate], |
| + "JSFunction": ["function() {}", _JSFunction], |
| + "JSFunctionProxy": ["Proxy.createFunction({}, function() {})", |
| + _JSFunctionProxy], |
| + "JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject], |
| + "JSMap": ["new Map()", _JSMap], |
| + "JSMapIterator": ["%MapCreateIterator(new Map(), 3)", _JSMapIterator], |
| + "JSObject": ["new Object()", _JSObject], |
| + "JSProxy": ["Proxy.create({})", _JSProxy], |
| + "JSReceiver": ["new Object()", _JSReceiver], |
| + "JSRegExp": ["/ab/g", _JSRegExp], |
| + "JSSet": ["new Set()", _JSSet], |
| + "JSSetIterator": ["%SetCreateIterator(new Set(), 2)", _JSSetIterator], |
| + "JSTypedArray": ["new Int32Array(2)", _JSTypedArray], |
| + "JSValue": ["new String('foo')", _JSValue], |
| + "JSWeakCollection": ["new WeakMap()", _JSWeakCollection], |
| + "Name": ["\"name\"", _Name], |
| + "Number": ["1.5", _Number], |
| + "Object": ["new Object()", _Object], |
| + "PropertyDetails": ["513", _PropertyDetails], |
| + "SeqString": ["\"seqstring\"", _SeqString], |
| + "Smi": ["1", _Smi], |
| + "StrictMode": ["1", _StrictMode], |
| + "String": ["\"foo\"", _String], |
| + "Symbol": ["Symbol(\"symbol\")", _Symbol], |
| + "Uint32": ["32", _Uint32], |
| + } |
| class ArgParser(object): |
| @@ -254,7 +726,6 @@ class Function(object): |
| double_arg_parser, number_arg_parser, strict_mode_arg_parser, |
| boolean_arg_parser, property_details_parser] |
| - |
| def SetArgsLength(self, match): |
| self.argslength = int(match.group(1)) |
| @@ -285,6 +756,7 @@ class Function(object): |
| s.append(")") |
| return "".join(s) |
| + |
| # Parses HEADERFILENAME to find out which runtime functions are "inline". |
| def FindInlineRuntimeFunctions(): |
| inline_functions = [] |
| @@ -391,47 +863,141 @@ def ClassifyFunctions(functions): |
| if t in NON_JS_TYPES: |
| decision = cctest_fuzzable_functions |
| else: |
| - assert t in JS_TYPE_GENERATORS, \ |
| + assert Generator.IsTypeSupported(t), \ |
| ("type generator not found for %s, function: %s" % (t, f)) |
| decision.append(f) |
| return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) |
| -def GenerateJSTestcaseForFunction(f): |
| +def _GetKnownGoodArgs(function, generator): |
| + custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None) |
| + definitions = [] |
| + argslist = [] |
| + for i in range(function.argslength): |
| + if custom_input and custom_input[i] is not None: |
| + name = "arg%d" % i |
| + definitions.append("var %s = %s;" % (name, custom_input[i])) |
| + else: |
| + arg = function.args[i] |
| + name = arg.name |
| + definitions += generator.RandomVariable(name, arg.type, simple=True) |
| + argslist.append(name) |
| + return (definitions, argslist) |
| + |
| + |
| +def _GenerateTestcase(function, definitions, argslist, throws): |
| s = ["// Copyright 2014 the V8 project authors. All rights reserved.", |
| "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY", |
| - "// Flags: --allow-natives-syntax --harmony"] |
| - call = "%%%s%s(" % (f.inline, f.name) |
| - custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None) |
| - for i in range(f.argslength): |
| - if custom and custom[i] is not None: |
| - (name, value) = ("arg%d" % i, custom[i]) |
| - else: |
| - arg = f.args[i] |
| - (name, value) = (arg.name, JS_TYPE_GENERATORS[arg.type]) |
| - s.append("var %s = %s;" % (name, value)) |
| - if i > 0: call += ", " |
| - call += name |
| - call += ");" |
| - if f.name in THROWS: |
| + "// Flags: --allow-natives-syntax --harmony"] + definitions |
| + call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist)) |
| + if throws: |
| s.append("try {") |
| s.append(call); |
| s.append("} catch(e) {}") |
| else: |
| s.append(call) |
| testcase = "\n".join(s) |
| - path = os.path.join(BASEPATH, f.Filename()) |
| + return testcase |
| + |
| + |
| +def GenerateJSTestcaseForFunction(function): |
| + gen = Generator() |
| + (definitions, argslist) = _GetKnownGoodArgs(function, gen) |
| + testcase = _GenerateTestcase(function, definitions, argslist, |
| + function.name in THROWS) |
| + path = os.path.join(BASEPATH, function.Filename()) |
| with open(path, "w") as f: |
| f.write("%s\n" % testcase) |
| + |
| def GenerateTestcases(functions): |
| shutil.rmtree(BASEPATH) # Re-generate everything. |
| os.makedirs(BASEPATH) |
| for f in functions: |
| GenerateJSTestcaseForFunction(f) |
| -def PrintUsage(): |
| - print """Usage: %(this_script)s ACTION |
| + |
| +def _SaveFileName(save_path, process_id, save_file_index): |
| + return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index) |
| + |
| + |
| +def RunFuzzer(process_id, options, stop_running): |
| + base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id |
| + test_file_name = "%s.js" % base_file_name |
| + stderr_file_name = "%s.out" % base_file_name |
| + save_file_index = 0 |
| + while os.path.exists(_SaveFileName(options.save_path, process_id, |
| + save_file_index)): |
| + save_file_index += 1 |
| + MAX_SLEEP_TIME = 0.1 |
| + INITIAL_SLEEP_TIME = 0.001 |
| + SLEEP_TIME_FACTOR = 1.5 |
| + |
| + functions = FindRuntimeFunctions() |
| + (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ |
| + ClassifyFunctions(functions) |
| + |
| + try: |
| + for i in range(options.num_tests): |
| + if stop_running.is_set(): break |
| + function = random.choice(js_fuzzable_functions) # TODO: others too |
| + if function.argslength == 0: continue |
| + args = [] |
| + definitions = [] |
| + gen = Generator() |
| + for i in range(function.argslength): |
| + arg = function.args[i] |
| + argname = "arg%d%s" % (i, arg.name) |
| + args.append(argname) |
| + definitions += gen.RandomVariable(argname, arg.type, simple=False) |
| + testcase = _GenerateTestcase(function, definitions, args, True) |
| + with open(test_file_name, "w") as f: |
| + f.write("%s\n" % testcase) |
| + with open("/dev/null", "w") as devnull: |
| + with open(stderr_file_name, "w") as stderr: |
| + process = subprocess.Popen( |
|
Michael Achenbach
2014/05/16 07:23:52
Maybe unify this with commands.py in the future?
|
| + [options.binary, "--allow-natives-syntax", "--harmony", |
| + "--enable-slow-asserts", test_file_name], |
| + stdout=devnull, stderr=stderr) |
| + end_time = time.time() + options.timeout |
| + timed_out = False |
| + exit_code = None |
| + sleep_time = INITIAL_SLEEP_TIME |
| + while exit_code is None: |
| + if time.time() >= end_time: |
| + # Kill the process and wait for it to exit. |
| + os.kill(process.pid, signal.SIGTERM) |
| + exit_code = process.wait() |
| + timed_out = True |
| + else: |
| + exit_code = process.poll() |
| + time.sleep(sleep_time) |
| + sleep_time = sleep_time * SLEEP_TIME_FACTOR |
| + if sleep_time > MAX_SLEEP_TIME: |
| + sleep_time = MAX_SLEEP_TIME |
| + if exit_code != 0 and not timed_out: |
| + oom = False |
| + with open(stderr_file_name, "r") as stderr: |
| + for line in stderr: |
| + if line.strip() == "# Allocation failed - process out of memory": |
| + oom = True |
| + break |
| + if oom: continue |
| + save_name = _SaveFileName(options.save_path, process_id, |
| + save_file_index) |
| + shutil.copyfile(test_file_name, save_name) |
| + save_file_index += 1 |
| + except KeyboardInterrupt: |
| + stop_running.set() |
| + except Exception, e: |
| + print e |
| + finally: |
| + os.remove(test_file_name) |
| + os.remove(stderr_file_name) |
| + |
| + |
| +def BuildOptionParser(): |
| + usage = """Usage: %%prog [options] ACTION |
| where ACTION can be: |
| @@ -441,21 +1007,47 @@ check Check that runtime functions can be parsed as expected, and that |
| generate Parse source code for runtime functions, and auto-generate |
| test cases for them. Warning: this will nuke and re-create |
| %(path)s. |
| -""" % {"path": os.path.relpath(BASEPATH), "this_script": THIS_SCRIPT} |
| +fuzz Generate fuzz tests, run them, save those that crashed (see options). |
| +""" % {"path": os.path.relpath(BASEPATH)} |
| + |
| + o = optparse.OptionParser(usage=usage) |
| + o.add_option("--binary", default="out/x64.debug/d8", |
| + help="d8 binary used for running fuzz tests (default: %default)") |
| + o.add_option("-n", "--num-tests", default=1000, type="int", |
| + help="Number of fuzz tests to generate per worker process" |
| + " (default: %default)") |
| + o.add_option("--save-path", default="~/runtime_fuzz_output", |
| + help="Path to directory where failing tests will be stored" |
| + " (default: %default)") |
| + o.add_option("--timeout", default=20, type="int", |
| + help="Timeout for each fuzz test (in seconds, default:" |
| + "%default)") |
| + return o |
| -if __name__ == "__main__": |
| - if len(sys.argv) != 2: |
| - PrintUsage() |
| - sys.exit(1) |
| - action = sys.argv[1] |
| - if action in ["-h", "--help", "help"]: |
| - PrintUsage() |
| - sys.exit(0) |
| + |
| +def Main(): |
| + parser = BuildOptionParser() |
| + (options, args) = parser.parse_args() |
| + options.save_path = os.path.expanduser(options.save_path) |
| + |
| + if len(args) != 1 or args[0] == "help": |
| + parser.print_help() |
| + return 1 |
| + action = args[0] |
| functions = FindRuntimeFunctions() |
| (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ |
| ClassifyFunctions(functions) |
| + if action == "test": |
| + gen = Generator() |
| + vartype = "JSTypedArray" |
| + print("simple: %s" % gen.RandomVariable("x", vartype, True)) |
| + for i in range(10): |
| + print("----") |
| + print("%s" % "\n".join(gen.RandomVariable("x", vartype, False))) |
| + return 0 |
| + |
| if action == "info": |
| print("%d functions total; js_fuzzable_functions: %d, " |
| "cctest_fuzzable_functions: %d, unknown_functions: %d" |
| @@ -464,49 +1056,76 @@ if __name__ == "__main__": |
| print("unknown functions:") |
| for f in unknown_functions: |
| print(f) |
| - sys.exit(0) |
| + return 0 |
| if action == "check": |
| - error = False |
| + errors = 0 |
| + |
| def CheckCount(actual, expected, description): |
| - global error |
| if len(actual) != expected: |
| print("Expected to detect %d %s, but found %d." % ( |
| expected, description, len(actual))) |
| print("If this change is intentional, please update the expectations" |
| " at the top of %s." % THIS_SCRIPT) |
| - error = True |
| - CheckCount(functions, EXPECTED_FUNCTION_COUNT, "functions in total") |
| - CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, |
| - "JavaScript-fuzzable functions") |
| - CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, |
| - "cctest-fuzzable functions") |
| - CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, |
| - "functions with incomplete type information") |
| + return 1 |
| + return 0 |
| + |
| + errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT, |
| + "functions in total") |
| + errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, |
| + "JavaScript-fuzzable functions") |
| + errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, |
| + "cctest-fuzzable functions") |
| + errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, |
| + "functions with incomplete type information") |
| def CheckTestcasesExisting(functions): |
| - global error |
| + errors = 0 |
| for f in functions: |
| if not os.path.isfile(os.path.join(BASEPATH, f.Filename())): |
| print("Missing testcase for %s, please run '%s generate'" % |
| (f.name, THIS_SCRIPT)) |
| - error = True |
| + errors += 1 |
| files = filter(lambda filename: not filename.startswith("."), |
| os.listdir(BASEPATH)) |
| if (len(files) != len(functions)): |
| unexpected_files = set(files) - set([f.Filename() for f in functions]) |
| for f in unexpected_files: |
| print("Unexpected testcase: %s" % os.path.join(BASEPATH, f)) |
| - error = True |
| + errors += 1 |
| print("Run '%s generate' to automatically clean these up." |
| % THIS_SCRIPT) |
| - CheckTestcasesExisting(js_fuzzable_functions) |
| + return errors |
| + |
| + errors += CheckTestcasesExisting(js_fuzzable_functions) |
| - if error: |
| - sys.exit(1) |
| + if errors > 0: |
| + return 1 |
| print("Generated runtime tests: all good.") |
| - sys.exit(0) |
| + return 0 |
| if action == "generate": |
| GenerateTestcases(js_fuzzable_functions) |
| - sys.exit(0) |
| + return 0 |
| + |
| + if action == "fuzz": |
| + processes = [] |
| + if not os.path.isdir(options.save_path): |
| + os.makedirs(options.save_path) |
| + stop_running = multiprocessing.Event() |
| + for i in range(multiprocessing.cpu_count()): |
| + args = (i, options, stop_running) |
| + p = multiprocessing.Process(target=RunFuzzer, args=args) |
| + p.start() |
| + processes.append(p) |
| + try: |
| + for i in range(len(processes)): |
| + processes[i].join() |
| + except KeyboardInterrupt: |
| + stop_running.set() |
| + for i in range(len(processes)): |
| + processes[i].join() |
| + return 0 |
| + |
| +if __name__ == "__main__": |
| + sys.exit(Main()) |