Skip to content

Instantly share code, notes, and snippets.

@lmergner
Last active December 18, 2015 23:48
Show Gist options
  • Save lmergner/5863649 to your computer and use it in GitHub Desktop.
Save lmergner/5863649 to your computer and use it in GitHub Desktop.
Fuzzy testing argparse subparser subcommands Looking for advice and feedback.
#!/usr/local/env python
#-*- coding: utf-8 -*-
"""
Argparse Testing Utilities
==========================
Because we want some random strings suitable for testing the
input and output of :func:argparse.parser().
Why? I keep slightly modifying my argparse options, and I want
a quick (test-oriented) way to check that those modifications
continue to fail or succeed when I expect them too. In addition,
it would be useful to try to mimic the range of potential inputs
that might be passed via the command line: does my code still
fail or handle unexpected inputs. This module is a first attempt
to automate that process.
Caveats: I am not a programmer by trade or training.
:copyright: (c) 2013 Luke Thomas Mergner
:license: GNU GPL <http://www.gnu.org/licenses/gpl.html>
"""
from argparse import Namespace
import random
import unittest
# Import your application
from application import *
class Subcommand(object):
''':class:`~dotfiler.tests.nsutils.Subcommand`
Fuzzy testing generator for argparse.
>>> com = Subcommand('command')
>>> com.req_positional = ['file', 'path']
:param name: the title of the subcommand.
:param func: optional function, usually to handle the subparser
Subcommand has a :func:`make_demo` that can be rewritten/overridden
in order to quickly create the commands.
'''
commands = []
namespaces = {}
argument_strings = {}
def __init__(self, cmd, **kwargs):
self.name = cmd
self.req_positional = [] # String
self.opt_positional = []
self.bflags = [] # True, False
self.dflags = [] # String
self.func = None # which function handles the subcommands
# set attrs at init instead of self.make_demo
for kw in kwargs:
if hasattr(self, kw):
setattr(self, kw, kwargs[kw])
# set as class vars
self.__class__.commands.append(self)
self.__class__.namespaces[self.name] = []
self.__class__.argument_strings[self.name] = []
def __iter__(self):
for k in self.__dict__:
if k[:1] is not "_":
yield k
def __getitem__(self, attr):
if hasattr(self, attr):
return getattr(self, attr)
def __repr__(self):
return self.name
def make_demo(self):
self.opt_positional = ['context', 'path']
self.req_positional = ['blarging']
self.bflags = ['--fav', '--verbose']
self.dflags = ['--quiet', '--flag']
def generate_random_namespaces(self):
nsl = []
for x in range(10): # Make 10 Namespace objects for each subcommand
names = Namespace()
if len(self.bflags) > 0:
for flag in self.bflags:
names.__dict__[flag[2:]] = random.choice([True, False])
if len(self.dflags) > 0:
for flag in self.dflags:
names.__dict__[flag[2:]] = self.word(random.randint(1, 10))
if len(self.req_positional) > 0:
for positions in self.req_positional:
names.__dict__[positions] = self.word(random.randint(1, 10))
if len(self.opt_positional) > 0:
for positions in random.sample(self.opt_positional, random.randint(1, len(self.opt_positional)-1)):
names.__dict__[positions] = self.word(random.randint(1, 10))
if self.func is not None:
names.__dict__['func'] = self.func
nsl.append(names)
self.__class__.namespaces[self.name].extend(nsl)
self.names = nsl
def generate_random_arguments(self):
arg_list = []
for x in range(10):
args = []
if len(self.bflags) > 0:
for bf in random.sample(self.bflags, random.randint(1, len(self.bflags))):
args.append(bf)
if len(self.dflags) > 0:
for df in random.sample(self.dflags, random.randint(1, len(self.dflags))):
args.append(df)
args.append(self.word(random.randint(1, 10)))
if len(self.req_positional) > 0:
for req in self.req_positional:
args.append(self.word(random.randint(1, 10)))
if len(self.opt_positional) > 0:
for opt in random.sample(self.opt_positional, random.randint(1, len(self.opt_positional))):
args.append(self.word(random.randint(1, 10)))
arg_list.append(' '.join(args))
self.__class__.argument_strings[self.name].extend(arg_list)
def word(self, length):
'''Returns a unicode string, but only of English characters.
'''
word = ''
for i in range(length):
word += random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-()[].')
return unicode(word)
class ArgsFixture(unittest.TestCase):
""" A unittest fixture that sets up a Subcommand object"""
def setUp(self):
# My application uses a central class to dispatch
# arguments via functions. So to test the cli, we
# need a copy of the object
application = MainClass()
# Let's setup the Subcommands by name, for me these represent
# the subprocessor names, but they could be anything
[Subcommand(x) for x in ['init', 'add', 'del', 'list']]
# Here we're mapping the app functions to argument name
funcs = {
'init': application.init,
'add' : application.add,
'del' : application.delete,
'list': application.list_files
}
# Let's setup all the values we want to test
# sub.make_demo() is specific to my app
for sub in Subcommand.commands:
sub.func = funcs[sub.name]
sub.make_demo()
sub.generate_random_namespaces()
sub.generate_random_arguments()
def tearDown(self):
mapping = None
dots = None
class ArgsTestCase(ArgsFixture):
def test_fuzzy(self):
print Subcommand.namespaces
print Subcommand.argument_strings
if __name__ == '__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment