foundationdb/fdbrpc/actorFuzz.py

432 lines
11 KiB
Python

#
# actorFuzz.py
#
# This source file is part of the FoundationDB open source project
#
# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import random
import copy
class Context:
tok = 0
inLoop = False
indent = 0
def __init__(self):
self.random = random.Random()
def uniqueID(self):
return self.random.randint(100000, 999999)
class InfiniteLoop (Exception):
pass
class ExecContext:
iterationsLeft = 1000
ifstate = 0
def __init__(self, inputSeq):
self.input = iter(inputSeq)
self.output = []
def inp(self):
return next(self.input)
def out(self, x):
self.output.append(x)
def infinityCheck(self):
self.iterationsLeft -= 1
if self.iterationsLeft <= 0:
raise InfiniteLoop()
OK = 1
BREAK = 2
THROW = 3
RETURN = 4
CONTINUE = 5
def indent(cx):
return "\t" * cx.indent
class F (object):
def unreachable(self):
return False
def containsbreak(self):
return False
class hashF (F):
def __init__(self, cx):
self.cx = cx
self.uniqueID = cx.uniqueID()
def __str__(self):
return indent(self.cx) + "outputStream.send( %d );\n" % self.uniqueID
def eval(self, ecx):
ecx.infinityCheck()
ecx.out(self.uniqueID)
return OK
class compoundF(F):
def __init__(self, cx, children):
self.cx = cx
self.children = []
for c in children:
self.children.append(c)
if c.unreachable():
self.unreachable = lambda: 1
break
def __str__(self):
return "".join(str(c) for c in self.children)
def eval(self, ecx):
for c in self.children:
ecx.infinityCheck()
result = c.eval(ecx)
if result != OK:
break
return result
def containsbreak(self):
return any(c.containsbreak() for c in self.children)
class loopF (F):
def __init__(self, cx):
self.cx = cx
ccx = copy.copy(cx)
ccx.indent += 1
ccx.inLoop = True
self.body = compoundF(ccx, [hashF(ccx)] + [fuzzCode(ccx)(ccx)] + [hashF(ccx)])
self.uniqueID = cx.uniqueID()
self.forever = cx.random.random() < 0.1
def __str__(self):
if self.forever:
return (indent(self.cx) + "loop {\n" +
str(self.body) +
indent(self.cx) + "}\n")
else:
return (indent(self.cx) + "state int i%d; for(i%d = 0; i%d < 5; i%d++) {\n" % ((self.uniqueID,) * 4) +
str(self.body) +
indent(self.cx) + "}\n")
def eval(self, ecx):
if self.forever:
while True:
ecx.infinityCheck()
result = self.body.eval(ecx)
if result == BREAK:
break
elif result not in (OK, CONTINUE):
return result
else:
for i in range(5):
ecx.infinityCheck()
result = self.body.eval(ecx)
if result == BREAK:
break
elif result not in (OK, CONTINUE):
return result
return OK
def unreachable(self):
return self.forever and not self.body.containsbreak()
class rangeForF (F):
def __init__(self, cx):
self.cx = cx
ccx = copy.copy(cx)
ccx.indent += 1
ccx.inLoop = True
self.body = compoundF(ccx, [hashF(ccx)] + [fuzzCode(ccx)(ccx)] + [hashF(ccx)])
self.uniqueID = cx.uniqueID()
def __str__(self):
return (
indent(self.cx) +
("\n" + indent(self.cx)).join([
"state std::vector<int> V;",
"V.push_back(1);",
"V.push_back(2);",
"V.push_back(3);",
"for( auto i : V ) {\n",
]).replace("V", "list%d" % self.uniqueID) +
str(self.body) +
indent(self.cx) + "}\n")
def eval(self, ecx):
for i in range(1, 4):
ecx.infinityCheck()
result = self.body.eval(ecx)
if result == BREAK:
break
elif result not in (OK, CONTINUE):
return result
return OK
def unreachable(self):
return False
class ifF (F):
def __init__(self, cx):
self.cx = cx
ccx = copy.copy(cx)
ccx.indent += 1
self.toggle = cx.random.randint(0, 1)
self.ifbody = compoundF(ccx, [hashF(ccx)] + [fuzzCode(ccx)(ccx)] + [hashF(ccx)])
if cx.random.random() < 0.5:
ccx = copy.copy(cx)
ccx.indent += 1
self.elsebody = compoundF(ccx, [hashF(ccx)] + [fuzzCode(ccx)(ccx)] + [hashF(ccx)])
else:
self.elsebody = None
def __str__(self):
s = (indent(self.cx) + "if ( (++ifstate&1) == %d ) {\n" % self.toggle +
str(self.ifbody))
if self.elsebody:
s += (indent(self.cx) + "} else {\n" +
str(self.elsebody))
s += indent(self.cx) + "}\n"
return s
def eval(self, ecx):
ecx.infinityCheck()
ecx.ifstate = ecx.ifstate + 1
if (ecx.ifstate & 1) == self.toggle:
return self.ifbody.eval(ecx)
elif self.elsebody:
return self.elsebody.eval(ecx)
else:
return OK
def unreachable(self):
return self.elsebody and self.ifbody.unreachable() and self.elsebody.unreachable()
def containsbreak(self):
return self.ifbody.containsbreak() or (self.elsebody and self.elsebody.containsbreak())
class tryF (F):
def __init__(self, cx):
self.cx = cx
ccx = copy.copy(cx)
ccx.indent += 1
self.body = compoundF(ccx, [hashF(ccx)] + [fuzzCode(ccx)(ccx)] + [hashF(ccx)])
ccx = copy.copy(cx)
ccx.indent += 1
self.catch = compoundF(ccx, [hashF(ccx)] + [fuzzCode(ccx)(ccx)] + [hashF(ccx)])
def __str__(self):
return (indent(self.cx) + "try {\n" +
str(self.body) +
indent(self.cx) + "} catch (...) {\n" +
str(self.catch) +
indent(self.cx) + "}\n"
)
def eval(self, ecx):
ecx.infinityCheck()
result = self.body.eval(ecx)
if result != THROW:
return result
return self.catch.eval(ecx)
def unreachable(self):
return self.body.unreachable() and self.catch.unreachable()
def containsbreak(self):
return self.body.containsbreak() or self.catch.containsbreak()
def doubleF(cx):
return compoundF(cx, [fuzzCode(cx)(cx)] + [hashF(cx)] + [fuzzCode(cx)(cx)])
class breakF(F):
def __init__(self, cx):
self.cx = cx
def __str__(self):
return indent(self.cx) + "break;\n"
def unreachable(self):
return True
def eval(self, ecx):
ecx.infinityCheck()
return BREAK
def containsbreak(self):
return True
class continueF(F):
def __init__(self, cx):
self.cx = cx
def __str__(self):
return indent(self.cx) + "continue;\n"
def unreachable(self):
return True
def eval(self, ecx):
ecx.infinityCheck()
return CONTINUE
class waitF(F):
def __init__(self, cx):
self.cx = cx
self.uniqueID = cx.uniqueID()
def __str__(self):
return (
indent(self.cx) + "int input = waitNext( inputStream );\n" +
indent(self.cx) + "outputStream.send( input + %d );\n" % self.uniqueID
)
def eval(self, ecx):
ecx.infinityCheck()
input = ecx.inp()
ecx.out((input + self.uniqueID) & 0xffffffff)
return OK
class throwF(F):
def __init__(self, cx):
self.cx = cx
def __str__(self):
return indent(self.cx) + "throw operation_failed();\n"
def unreachable(self):
return True
def eval(self, ecx):
ecx.infinityCheck()
return THROW
class throwF2(throwF):
def __str__(self):
return indent(self.cx) + "throw_operation_failed();\n"
def unreachable(self):
return False # The actor compiler doesn't know the function never returns
class throwF3(throwF):
def __str__(self):
return indent(self.cx) + "Void _ = wait( error ); // throw operation_failed()\n"
def unreachable(self):
return False # The actor compiler doesn't know that 'error' always contains an error
class returnF(F):
def __init__(self, cx):
self.cx = cx
self.uniqueID = cx.uniqueID()
def __str__(self):
return indent(self.cx) + "return %d;\n" % self.uniqueID
def unreachable(self):
return True
def eval(self, ecx):
ecx.infinityCheck()
ecx.returnValue = self.uniqueID
return RETURN
def fuzzCode(cx):
choices = [loopF, rangeForF, tryF, doubleF, ifF]
if (cx.indent < 2):
choices = choices * 2
choices += [waitF, returnF]
if (cx.inLoop):
choices += [breakF, continueF]
choices = choices * 3 + [throwF, throwF2, throwF3]
return cx.random.choice(choices)
def randomActor(index):
while 1:
cx = Context()
cx.indent += 1
actor = fuzzCode(cx)(cx)
actor = compoundF(cx, [actor, returnF(cx)]) # Add a return at the end if the end is reachable
name = "actorFuzz%d" % index
text = ("ACTOR Future<int> %s( FutureStream<int> inputStream, PromiseStream<int> outputStream, Future<Void> error ) {\n" % name
+ "\tstate int ifstate = 0;\n"
+ str(actor)
+ "}")
ecx = actor.ecx = ExecContext((i + 1) * 1000 for i in range(1000000))
try:
result = actor.eval(ecx)
except InfiniteLoop:
print("Infinite loop for actor %s" % name)
continue
if result == RETURN:
ecx.out(ecx.returnValue)
elif result == THROW:
ecx.out(1000)
else:
print(text)
raise Exception("Invalid eval result: " + str(result))
actor.name = name
actor.text = text
return actor
testCaseCount = 30
outputFile = open("ActorFuzz.actor.cpp", "wt")
print('// THIS FILE WAS GENERATED BY actorFuzz.py; DO NOT MODIFY IT DIRECTLY\n', file=outputFile)
print('#include "ActorFuzz.h"\n', file=outputFile)
print('#ifndef WIN32\n', file=outputFile)
actors = [randomActor(i) for i in range(testCaseCount)]
for actor in actors:
print(actor.text + "\n", file=outputFile)
print("std::pair<int,int> actorFuzzTests() {\n\tint testsOK = 0;", file=outputFile)
for actor in actors:
print('\ttestsOK += testFuzzActor( &%s, "%s", (vector<int>(),%s) );' % (actor.name, actor.name, ','.join(str(e) for e in actor.ecx.output)),
file=outputFile)
print("\treturn std::make_pair(testsOK, %d);\n}" % len(actors), file=outputFile)
print('#endif // WIN32\n', file=outputFile)
outputFile.close()