Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Unified Diff: tools/generate-runtime-tests.py

Issue 288443005: Add randomized test generation capability to tools/generate-runtime-tests.py (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: addressed comments Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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())
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698