S3 Fuzzer: Change how random data works

Remove SpecialVariables dict subclass in favor of RepeatExpandingFormatter
string.Formatter subclass.
This commit is contained in:
Kyle Marsh 2011-08-18 12:34:56 -07:00
parent bfca00ac4c
commit e12f124686
2 changed files with 69 additions and 83 deletions

View file

@ -140,60 +140,50 @@ def test_descend_nonexistant_child():
assert_raises(KeyError, descend_graph, graph, 'nonexistant_child_node', prng) assert_raises(KeyError, descend_graph, graph, 'nonexistant_child_node', prng)
def test_SpecialVariables_dict(): def test_expand_random_printable():
prng = random.Random(1) prng = random.Random(1)
testdict = {'foo': 'bar'} got = expand({}, '{random 10-15 printable}', prng)
tester = SpecialVariables(testdict, prng) eq(got, '[/pNI$;92@')
eq(tester['foo'], 'bar')
eq(tester['random 10-15 printable'], '[/pNI$;92@')
def test_SpecialVariables_binary(): def test_expand_random_binary():
prng = random.Random(1) prng = random.Random(1)
tester = SpecialVariables({}, prng) got = expand({}, '{random 10-15 binary}', prng)
eq(got, '\xdfj\xf1\xd80>a\xcd\xc4\xbb')
eq(tester['random 10-15 binary'], '\xdfj\xf1\xd80>a\xcd\xc4\xbb')
def test_SpeicalVariables_random_no_args(): def test_expand_random_no_args():
prng = random.Random(1) prng = random.Random(1)
tester = SpecialVariables({}, prng)
for _ in xrange(1000): for _ in xrange(1000):
val = tester['random'] got = expand({}, '{random}', prng)
val = val.replace('{{', '{').replace('}}','}') assert_true(0 <= len(got) <= 1000)
assert_true(0 <= len(val) <= 1000) assert_true(reduce(lambda x, y: x and y, [x in string.printable for x in got]))
assert_true(reduce(lambda x, y: x and y, [x in string.printable for x in val]))
def test_SpeicalVariables_random_no_charset(): def test_expand_random_no_charset():
prng = random.Random(1) prng = random.Random(1)
tester = SpecialVariables({}, prng)
for _ in xrange(1000): for _ in xrange(1000):
val = tester['random 10-30'] got = expand({}, '{random 10-30}', prng)
val = val.replace('{{', '{').replace('}}','}') assert_true(10 <= len(got) <= 30)
assert_true(10 <= len(val) <= 30) assert_true(reduce(lambda x, y: x and y, [x in string.printable for x in got]))
assert_true(reduce(lambda x, y: x and y, [x in string.printable for x in val]))
def test_SpeicalVariables_random_exact_length(): def test_expand_random_exact_length():
prng = random.Random(1) prng = random.Random(1)
tester = SpecialVariables({}, prng)
for _ in xrange(1000): for _ in xrange(1000):
val = tester['random 10 digits'] got = expand({}, '{random 10 digits}', prng)
assert_true(len(val) == 10) assert_true(len(got) == 10)
assert_true(reduce(lambda x, y: x and y, [x in string.digits for x in val])) assert_true(reduce(lambda x, y: x and y, [x in string.digits for x in got]))
def test_SpecialVariables_random_errors(): def test_expand_random_bad_charset():
prng = random.Random(1) prng = random.Random(1)
tester = SpecialVariables({}, prng) assert_raises(KeyError, expand, {}, '{random 10-30 foo}', prng)
assert_raises(KeyError, lambda x: tester[x], 'random 10-30 foo')
assert_raises(ValueError, lambda x: tester[x], 'random printable') def test_expand_random_missing_length():
prng = random.Random(1)
assert_raises(ValueError, expand, {}, '{random printable}', prng)
def test_assemble_decision(): def test_assemble_decision():
@ -210,54 +200,60 @@ def test_assemble_decision():
def test_expand_escape(): def test_expand_escape():
prng = random.Random(1)
decision = dict( decision = dict(
foo='{{bar}}', foo='{{bar}}',
) )
got = expand(decision, '{foo}') got = expand(decision, '{foo}', prng)
eq(got, '{bar}') eq(got, '{bar}')
def test_expand_indirect(): def test_expand_indirect():
prng = random.Random(1)
decision = dict( decision = dict(
foo='{bar}', foo='{bar}',
bar='quux', bar='quux',
) )
got = expand(decision, '{foo}') got = expand(decision, '{foo}', prng)
eq(got, 'quux') eq(got, 'quux')
def test_expand_indirect_double(): def test_expand_indirect_double():
prng = random.Random(1)
decision = dict( decision = dict(
foo='{bar}', foo='{bar}',
bar='{quux}', bar='{quux}',
quux='thud', quux='thud',
) )
got = expand(decision, '{foo}') got = expand(decision, '{foo}', prng)
eq(got, 'thud') eq(got, 'thud')
def test_expand_recursive(): def test_expand_recursive():
prng = random.Random(1)
decision = dict( decision = dict(
foo='{foo}', foo='{foo}',
) )
e = assert_raises(RecursionError, expand, decision, '{foo}') e = assert_raises(RecursionError, expand, decision, '{foo}', prng)
eq(str(e), "Runaway recursion in string formatting: 'foo'") eq(str(e), "Runaway recursion in string formatting: 'foo'")
def test_expand_recursive_mutual(): def test_expand_recursive_mutual():
prng = random.Random(1)
decision = dict( decision = dict(
foo='{bar}', foo='{bar}',
bar='{foo}', bar='{foo}',
) )
e = assert_raises(RecursionError, expand, decision, '{foo}') e = assert_raises(RecursionError, expand, decision, '{foo}', prng)
eq(str(e), "Runaway recursion in string formatting: 'foo'") eq(str(e), "Runaway recursion in string formatting: 'foo'")
def test_expand_recursive_not_too_eager(): def test_expand_recursive_not_too_eager():
prng = random.Random(1)
decision = dict( decision = dict(
foo='bar', foo='bar',
) )
got = expand(decision, 100*'{foo}') got = expand(decision, 100*'{foo}', prng)
eq(got, 100*'bar') eq(got, 100*'bar')
@ -356,15 +352,14 @@ def test_expand_headers():
graph = build_graph() graph = build_graph()
prng = random.Random(1) prng = random.Random(1)
decision = descend_graph(graph, 'node1', prng) decision = descend_graph(graph, 'node1', prng)
special_decision = SpecialVariables(decision, prng) expanded_headers = expand_headers(decision, prng)
expanded_headers = expand_headers(special_decision)
for header, value in expanded_headers: for header, value in expanded_headers:
if header == 'my-header': if header == 'my-header':
assert_true(value in ['h1', 'h2', 'h3']) assert_true(value in ['h1', 'h2', 'h3'])
elif header.startswith('random-header-'): elif header.startswith('random-header-'):
assert_true(20 <= len(value) <= 30) assert_true(20 <= len(value) <= 30)
assert_true(string.strip(value, SpecialVariables.charsets['punctuation']) is '') assert_true(string.strip(value, RepeatExpandingFormatter.charsets['punctuation']) is '')
else: else:
raise DecisionGraphError('unexpected header found: "%s"' % header) raise DecisionGraphError('unexpected header found: "%s"' % header)

View file

@ -117,42 +117,23 @@ def make_choice(choices, prng):
return prng.choice(weighted_choices) return prng.choice(weighted_choices)
def expand_headers(decision): def expand_headers(decision, prng):
expanded_headers = [] expanded_headers = []
for header in decision['headers']: for header in decision['headers']:
h = expand(decision, header[0]) h = expand(decision, header[0], prng)
v = expand(decision, header[1]) v = expand(decision, header[1], prng)
expanded_headers.append([h, v]) expanded_headers.append([h, v])
return expanded_headers return expanded_headers
def expand(decision, value): def expand(decision, value, prng):
c = itertools.count() c = itertools.count()
fmt = RepeatExpandingFormatter() fmt = RepeatExpandingFormatter(prng)
new = fmt.vformat(value, [], decision) new = fmt.vformat(value, [], decision)
return new return new
class RepeatExpandingFormatter(string.Formatter): class RepeatExpandingFormatter(string.Formatter):
def __init__(self, _recursion=0):
super(RepeatExpandingFormatter, self).__init__()
# this class assumes it is always instantiated once per
# formatting; use that to detect runaway recursion
self._recursion = _recursion
def get_value(self, key, args, kwargs):
val = super(RepeatExpandingFormatter, self).get_value(key, args, kwargs)
if self._recursion > 5:
raise RecursionError(key)
fmt = self.__class__(_recursion=self._recursion+1)
# must use vformat not **kwargs so our SpecialVariables is not
# downgraded to just a dict
n = fmt.vformat(val, args, kwargs)
return n
class SpecialVariables(dict):
charsets = { charsets = {
'printable': string.printable, 'printable': string.printable,
'punctuation': string.punctuation, 'punctuation': string.punctuation,
@ -160,21 +141,29 @@ class SpecialVariables(dict):
'digits': string.digits 'digits': string.digits
} }
def __init__(self, orig_dict, prng): def __init__(self, prng, _recursion=0):
super(SpecialVariables, self).__init__(orig_dict) super(RepeatExpandingFormatter, self).__init__()
# this class assumes it is always instantiated once per
# formatting; use that to detect runaway recursion
self.prng = prng self.prng = prng
self._recursion = _recursion
def get_value(self, key, args, kwargs):
def __getitem__(self, key):
fields = key.split(None, 1) fields = key.split(None, 1)
fn = getattr(self, 'special_{name}'.format(name=fields[0]), None) fn = getattr(self, 'special_{name}'.format(name=fields[0]), None)
if fn is None: if fn is not None:
return super(SpecialVariables, self).__getitem__(key) if len(fields) == 1:
fields.append('')
if len(fields) == 1: return fn(fields[1])
fields.append('')
return fn(fields[1])
val = super(RepeatExpandingFormatter, self).get_value(key, args, kwargs)
if self._recursion > 5:
raise RecursionError(key)
fmt = self.__class__(self.prng, _recursion=self._recursion+1)
# must use vformat not **kwargs so our SpecialVariables is not
# downgraded to just a dict
n = fmt.vformat(val, args, kwargs)
return n
def special_random(self, args): def special_random(self, args):
arg_list = args.split() arg_list = args.split()
@ -199,12 +188,10 @@ class SpecialVariables(dict):
num_bytes = length + 8 num_bytes = length + 8
tmplist = [self.prng.getrandbits(64) for _ in xrange(num_bytes / 8)] tmplist = [self.prng.getrandbits(64) for _ in xrange(num_bytes / 8)]
tmpstring = struct.pack((num_bytes / 8) * 'Q', *tmplist) tmpstring = struct.pack((num_bytes / 8) * 'Q', *tmplist)
tmpstring = tmpstring[0:length] return tmpstring[0:length]
else: else:
charset = self.charsets[charset_arg] charset = self.charsets[charset_arg]
tmpstring = ''.join([self.prng.choice(charset) for _ in xrange(length)]) # Won't scale nicely return ''.join([self.prng.choice(charset) for _ in xrange(length)]) # Won't scale nicely
return tmpstring.replace('{', '{{').replace('}', '}}')
def parse_options(): def parse_options():
@ -262,9 +249,13 @@ def _main():
prng = random.Random(request_seed) prng = random.Random(request_seed)
decision = assemble_decision(decision_graph, prng) decision = assemble_decision(decision_graph, prng)
decision.update(constants) decision.update(constants)
request = expand_decision(decision, prng)
response = s3_connection.make_request(request['method'], request['path'], data=request['body'], headers=request['headers'], override_num_retries=0) method = expand(decision, decision['method'], prng)
path = expand(decision, decision['path'], prng)
body = expand(decision, decision['body'], prng)
headers = expand_headers(decision, prng)
response = s3_connection.make_request(method, path, data=body, headers=headers, override_num_retries=0)
if response.status == 500 or response.status == 503: if response.status == 500 or response.status == 503:
print 'Request generated with seed %d failed:\n%s' % (request_seed, request) print 'Request generated with seed %d failed:\n%s' % (request_seed, request)