forked from TrueCloudLab/s3-tests
S3 Fuzzer: Change how random data works
Remove SpecialVariables dict subclass in favor of RepeatExpandingFormatter string.Formatter subclass.
This commit is contained in:
parent
bfca00ac4c
commit
e12f124686
2 changed files with 69 additions and 83 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue