Last active
December 18, 2015 23:48
-
-
Save lmergner/5863649 to your computer and use it in GitHub Desktop.
Fuzzy testing argparse subparser subcommands Looking for advice and feedback.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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