S3 Fuzzer: Added SpecialVariables dict subclass

Helper class to catch sentinal keys and turn them into random values.  This
will be used to generate garbage data when expanding a decision.

Also add unit tests for expand_decision and assemble_decision
This commit is contained in:
Kyle Marsh 2011-08-09 15:44:25 -07:00
parent a9a41a2891
commit 7d9ec02686
2 changed files with 92 additions and 4 deletions

View file

@ -23,7 +23,7 @@ def build_graph():
graph = {} graph = {}
graph['start'] = { graph['start'] = {
'set': {}, 'set': {},
'choices': ['node1'] 'choices': ['node2']
} }
graph['leaf'] = { graph['leaf'] = {
'set': { 'set': {
@ -38,6 +38,14 @@ def build_graph():
}, },
'choices': ['leaf'] 'choices': ['leaf']
} }
graph['node2'] = {
'set': {
'randkey': 'value-{random 10-15 printable}',
'path': '/{bucket_readable}',
'indirect_key1': '{key1}'
},
'choices': ['leaf']
}
graph['bad_node'] = { graph['bad_node'] = {
'set': { 'set': {
'key1': 'value1' 'key1': 'value1'
@ -62,6 +70,7 @@ def test_descend_leaf_node():
eq(decision['key2'], 'value2') eq(decision['key2'], 'value2')
e = assert_raises(KeyError, lambda x: decision[x], 'key3') e = assert_raises(KeyError, lambda x: decision[x], 'key3')
def test_descend_node(): def test_descend_node():
graph = build_graph() graph = build_graph()
prng = random.Random(1) prng = random.Random(1)
@ -71,8 +80,45 @@ def test_descend_node():
eq(decision['key2'], 'value2') eq(decision['key2'], 'value2')
eq(decision['key3'], 'value3') eq(decision['key3'], 'value3')
def test_descend_bad_node(): def test_descend_bad_node():
graph = build_graph() graph = build_graph()
prng = random.Random(1) prng = random.Random(1)
assert_raises(KeyError, descend_graph, graph, 'bad_node', prng) assert_raises(KeyError, descend_graph, graph, 'bad_node', prng)
def test_SpecialVariables_dict():
prng = random.Random(1)
testdict = {'foo': 'bar'}
tester = SpecialVariables(testdict, prng)
eq(tester['foo'], 'bar')
eq(tester['random 10-15 printable'], '[/pNI$;92@') #FIXME: how should I test pseudorandom content?
def test_assemble_decision():
graph = build_graph()
prng = random.Random(1)
decision = assemble_decision(graph, prng)
eq(decision['key1'], 'value1')
eq(decision['key2'], 'value2')
eq(decision['randkey'], 'value-{random 10-15 printable}')
eq(decision['indirect_key1'], '{key1}')
eq(decision['path'], '/{bucket_readable}')
assert_raises(KeyError, lambda x: decision[x], 'key3')
def test_expand_decision():
graph = build_graph()
prng = random.Random(1)
decision = assemble_decision(graph, prng)
decision.update({'bucket_readable': 'my-readable-bucket'})
request = expand_decision(decision, prng)
eq(request['key1'], 'value1')
eq(request['indirect_key1'], 'value1')
eq(request['path'], '/my-readable-bucket')
eq(request['randkey'], 'value-?') #FIXME: again, how to handle the pseudorandom content?
assert_raises(KeyError, lambda x: decision[x], 'key3')

View file

@ -22,14 +22,16 @@ def descend_graph(decision_graph, node_name, prng):
the node's "set" list, pick a choice from the "choice" list, and the node's "set" list, pick a choice from the "choice" list, and
recurse. Finally, return dictionary of values recurse. Finally, return dictionary of values
""" """
node = decision_graph[node_name]
try: try:
choice = prng.choice(decision_graph[node_name]['choices']) #TODO: Give weights to each choice
choice = prng.choice(node['choices'])
decision = descend_graph(decision_graph, choice, prng) decision = descend_graph(decision_graph, choice, prng)
except IndexError: except IndexError:
decision = {} decision = {}
node = decision_graph[node_name] #TODO: Add in headers
for key in node['set']: for key in node['set']:
if decision.has_key(key): if decision.has_key(key):
raise KeyError("Node %s tried to set '%s', but that key was already set by a lower node!" %(node_name, key)) raise KeyError("Node %s tried to set '%s', but that key was already set by a lower node!" %(node_name, key))
@ -45,6 +47,46 @@ def expand_decision(decision, prng):
raise NotImplementedError raise NotImplementedError
class SpecialVariables(dict):
charsets = {
'printable': string.printable,
'punctuation': string.punctuation,
'whitespace': string.whitespace
}
def __init__(self, orig_dict, prng):
self.update(orig_dict)
self.prng = prng
def __getitem__(self, key):
fields = key.split(None, 1)
fn = getattr(self, 'special_{name}'.format(name=fields[0]), None)
if fn is None:
return super(SpecialVariables, self).__getitem__(key)
if len(fields) == 1:
fields.apppend('')
return fn(fields[1])
def special_random(self, args):
arg_list = args.split()
try:
size_min, size_max = [int(x) for x in arg_list[0].split('-')]
except IndexError:
size_min = 0
size_max = 1000
try:
charset = self.charsets[arg_list[1]]
except IndexError:
charset = self.charsets['printable']
length = self.prng.randint(size_min, size_max)
return ''.join([self.prng.choice(charset) for _ in xrange(length)]) # Won't scale nicely
def parse_options(): def parse_options():
parser = OptionParser() parser = OptionParser()
parser.add_option('-O', '--outfile', help='write output to FILE. Defaults to STDOUT', metavar='FILE') parser.add_option('-O', '--outfile', help='write output to FILE. Defaults to STDOUT', metavar='FILE')