Initial commit
This commit is contained in:
16
.hgignore
Normal file
16
.hgignore
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
syntax: glob
|
||||||
|
*.class
|
||||||
|
*.o
|
||||||
|
*.pyc
|
||||||
|
*.sqlite3
|
||||||
|
*.sw[op]
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
bin-debug/*
|
||||||
|
bin-release/*
|
||||||
|
bin/*
|
||||||
|
tags
|
||||||
|
*.beam
|
||||||
|
*.dump
|
||||||
|
env/
|
||||||
|
*egg-info*
|
1
nose_parameterized/__init__.py
Normal file
1
nose_parameterized/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .parameterized import parameterized
|
222
nose_parameterized/parameterized.py
Normal file
222
nose_parameterized/parameterized.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
import re
|
||||||
|
import new
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from nose.tools import nottest
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
def _terrible_magic_get_defining_classes():
|
||||||
|
""" Returns the set of parent classes of the class currently being defined.
|
||||||
|
Will likely only work if called from the ``parameterized`` decorator.
|
||||||
|
This function is entirely @brandon_rhodes's fault, as he suggested
|
||||||
|
the implementation: http://stackoverflow.com/a/8793684/71522
|
||||||
|
"""
|
||||||
|
stack = inspect.stack()
|
||||||
|
frame = stack[3]
|
||||||
|
code_context = frame[4][0].strip()
|
||||||
|
if not code_context.startswith("class "):
|
||||||
|
return []
|
||||||
|
_, parents = code_context.split("(", 1)
|
||||||
|
parents, _ = parents.rsplit(")", 1)
|
||||||
|
return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
|
||||||
|
|
||||||
|
def parameterized(input):
|
||||||
|
""" Parameterize a test case:
|
||||||
|
>>> add1_tests = [(1, 2), (2, 3)]
|
||||||
|
>>> class TestFoo(object):
|
||||||
|
... @parameterized(add1_tests)
|
||||||
|
... def test_add1(self, input, expected):
|
||||||
|
... assert_equal(add1(input), expected)
|
||||||
|
>>> @parameterized(add1_tests)
|
||||||
|
... def test_add1(input, expected):
|
||||||
|
... assert_equal(add1(input), expected)
|
||||||
|
>>>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not hasattr(input, "__iter__"):
|
||||||
|
raise ValueError("expected iterable input; got %r" %(input, ))
|
||||||
|
|
||||||
|
def parameterized_helper(f):
|
||||||
|
attached_instance_method = [False]
|
||||||
|
|
||||||
|
parent_classes = _terrible_magic_get_defining_classes()
|
||||||
|
if any(issubclass(cls, TestCase) for cls in parent_classes):
|
||||||
|
raise Exception("Warning: '@parameterized' tests won't work "
|
||||||
|
"inside subclasses of 'TestCase' - use "
|
||||||
|
"'@parameterized.expand' instead")
|
||||||
|
|
||||||
|
@wraps(f)
|
||||||
|
def parameterized_helper_method(self=None):
|
||||||
|
if self is not None and not attached_instance_method[0]:
|
||||||
|
# confusingly, we need to create a named instance method and
|
||||||
|
# attach that to the class...
|
||||||
|
cls = self.__class__
|
||||||
|
im_f = new.instancemethod(f, None, cls)
|
||||||
|
setattr(cls, f.__name__, im_f)
|
||||||
|
attached_instance_method[0] = True
|
||||||
|
for args in input:
|
||||||
|
if isinstance(args, basestring):
|
||||||
|
args = [args]
|
||||||
|
# ... then pull that named instance method off, turning it into
|
||||||
|
# a bound method ...
|
||||||
|
if self is not None:
|
||||||
|
args = [getattr(self, f.__name__)] + list(args)
|
||||||
|
else:
|
||||||
|
args = [f] + list(args)
|
||||||
|
# ... then yield that as a tuple. If those steps aren't
|
||||||
|
# followed precicely, Nose gets upset and doesn't run the test
|
||||||
|
# or doesn't run setup methods.
|
||||||
|
yield tuple(args)
|
||||||
|
|
||||||
|
f.__name__ = "_helper_for_%s" %(f.__name__, )
|
||||||
|
parameterized_helper_method.parameterized_input = input
|
||||||
|
parameterized_helper_method.parameterized_func = f
|
||||||
|
return parameterized_helper_method
|
||||||
|
|
||||||
|
return parameterized_helper
|
||||||
|
|
||||||
|
def to_safe_name(s):
|
||||||
|
return re.sub("[^a-zA-Z0-9_]", "", s)
|
||||||
|
|
||||||
|
def parameterized_expand_helper(func_name, func, args):
|
||||||
|
def parameterized_expand_helper_helper(self=()):
|
||||||
|
if self != ():
|
||||||
|
self = (self, )
|
||||||
|
return func(*(self + args))
|
||||||
|
parameterized_expand_helper_helper.__name__ = func_name
|
||||||
|
return parameterized_expand_helper_helper
|
||||||
|
|
||||||
|
def parameterized_expand(input):
|
||||||
|
""" A "brute force" method of parameterizing test cases. Creates new test
|
||||||
|
cases and injects them into the namespace that the wrapped function
|
||||||
|
is being defined in. Useful for parameterizing tests in subclasses
|
||||||
|
of 'UnitTest', where Nose test generators don't work.
|
||||||
|
|
||||||
|
>>> @parameterized.expand([("foo", 1, 2)])
|
||||||
|
... def test_add1(name, input, expected):
|
||||||
|
... actual = add1(input)
|
||||||
|
... assert_equal(actual, expected)
|
||||||
|
...
|
||||||
|
>>> locals()
|
||||||
|
... 'test_add1_foo_0': <function ...> ...
|
||||||
|
>>>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def parameterized_expand_wrapper(f):
|
||||||
|
stack = inspect.stack()
|
||||||
|
frame = stack[1]
|
||||||
|
frame_locals = frame[0].f_locals
|
||||||
|
|
||||||
|
base_name = f.__name__
|
||||||
|
for num, args in enumerate(input):
|
||||||
|
name_suffix = "_%s" %(num, )
|
||||||
|
if len(args) > 0 and isinstance(args[0], basestring):
|
||||||
|
name_suffix += "_" + to_safe_name(args[0])
|
||||||
|
name = base_name + name_suffix
|
||||||
|
new_func = parameterized_expand_helper(name, f, args)
|
||||||
|
frame_locals[name] = new_func
|
||||||
|
return nottest(f)
|
||||||
|
return parameterized_expand_wrapper
|
||||||
|
|
||||||
|
parameterized.expand = parameterized_expand
|
||||||
|
|
||||||
|
def assert_contains(haystack, needle):
|
||||||
|
if needle not in haystack:
|
||||||
|
raise AssertionError("%r not in %r" %(needle, haystack))
|
||||||
|
|
||||||
|
def assert_not_contains(haystack, needle):
|
||||||
|
if needle in haystack:
|
||||||
|
raise AssertionError("%r in %r" %(needle, haystack))
|
||||||
|
|
||||||
|
def imported_from_test():
|
||||||
|
""" Returns true if it looks like this module is being imported by unittest
|
||||||
|
or nose. """
|
||||||
|
import re
|
||||||
|
import inspect
|
||||||
|
nose_re = re.compile(r"\bnose\b")
|
||||||
|
unittest_re = re.compile(r"\bunittest2?\b")
|
||||||
|
for frame in inspect.stack():
|
||||||
|
file = frame[1]
|
||||||
|
if nose_re.search(file) or unittest_re.search(file):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
|
||||||
|
try:
|
||||||
|
func()
|
||||||
|
except exc_type as e:
|
||||||
|
if str_contains is not None and str_contains not in str(e):
|
||||||
|
raise AssertionError("%s raised, but %r does not contain %r"
|
||||||
|
%(exc_type, str(e), str_contains))
|
||||||
|
if repr_contains is not None and repr_contains not in repr(e):
|
||||||
|
raise AssertionError("%s raised, but %r does not contain %r"
|
||||||
|
%(exc_type, repr(e), repr_contains))
|
||||||
|
return e
|
||||||
|
else:
|
||||||
|
raise AssertionError("%s not raised" %(exc_type, ))
|
||||||
|
|
||||||
|
|
||||||
|
log_handler = None
|
||||||
|
def setup_logging():
|
||||||
|
""" Configures a log handler which will capure log messages during a test.
|
||||||
|
The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
|
||||||
|
used to make assertions about these logged messages.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
from ensi_common.testing import (
|
||||||
|
setup_logging, teardown_logging, assert_no_errors_logged,
|
||||||
|
assert_logged,
|
||||||
|
)
|
||||||
|
|
||||||
|
class TestWidget(object):
|
||||||
|
def setup(self):
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
assert_no_errors_logged()
|
||||||
|
teardown_logging()
|
||||||
|
|
||||||
|
def test_that_will_fail(self):
|
||||||
|
log.warning("this warning message will trigger a failure")
|
||||||
|
|
||||||
|
def test_that_will_pass(self):
|
||||||
|
log.info("but info messages are ok")
|
||||||
|
assert_logged("info messages are ok")
|
||||||
|
"""
|
||||||
|
|
||||||
|
global log_handler
|
||||||
|
if log_handler is not None:
|
||||||
|
logging.getLogger().removeHandler(log_handler)
|
||||||
|
log_handler = logging.handlers.BufferingHandler(1000)
|
||||||
|
formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
|
||||||
|
log_handler.setFormatter(formatter)
|
||||||
|
logging.getLogger().addHandler(log_handler)
|
||||||
|
|
||||||
|
def teardown_logging():
|
||||||
|
global log_handler
|
||||||
|
if log_handler is not None:
|
||||||
|
logging.getLogger().removeHandler(log_handler)
|
||||||
|
log_handler = None
|
||||||
|
|
||||||
|
def logged_messages():
|
||||||
|
assert log_handler, "setup_logging not called"
|
||||||
|
return [ (log_handler.format(record), record) for record in log_handler.buffer ]
|
||||||
|
|
||||||
|
def assert_no_errors_logged():
|
||||||
|
for _, record in logged_messages():
|
||||||
|
if record.levelno >= logging.WARNING:
|
||||||
|
# Assume that the nose log capture plugin is being used, so it will
|
||||||
|
# show the exception.
|
||||||
|
raise AssertionError("an unexpected error was logged")
|
||||||
|
|
||||||
|
def assert_logged(expected_msg_contents):
|
||||||
|
for msg, _ in logged_messages():
|
||||||
|
if expected_msg_contents in msg:
|
||||||
|
return
|
||||||
|
raise AssertionError("no logged message contains %r"
|
||||||
|
%(expected_msg_contents, ))
|
43
nose_parameterized/test.py
Normal file
43
nose_parameterized/test.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from nose.tools import assert_equal
|
||||||
|
|
||||||
|
from .parameterized import parameterized
|
||||||
|
|
||||||
|
def assert_contains(haystack, needle):
|
||||||
|
if needle not in haystack:
|
||||||
|
raise AssertionError("%r not in %r" %(needle, haystack))
|
||||||
|
|
||||||
|
missing_tests = set([
|
||||||
|
"test_parameterized_naked_function",
|
||||||
|
"test_parameterized_instance_method",
|
||||||
|
"test_parameterized_on_TestCase",
|
||||||
|
])
|
||||||
|
|
||||||
|
@parameterized([(42, )])
|
||||||
|
def test_parameterized_naked_function(foo):
|
||||||
|
missing_tests.remove("test_parameterized_naked_function")
|
||||||
|
|
||||||
|
class TestParameterized(object):
|
||||||
|
@parameterized([(42, )])
|
||||||
|
def test_parameterized_instance_method(self, foo):
|
||||||
|
missing_tests.remove("test_parameterized_instance_method")
|
||||||
|
|
||||||
|
|
||||||
|
def test_warns_on_bad_use_of_parameterized():
|
||||||
|
try:
|
||||||
|
class TestTestCaseWarnsOnBadUseOfParameterized(TestCase):
|
||||||
|
@parameterized([42])
|
||||||
|
def test_should_throw_error(self, param):
|
||||||
|
pass
|
||||||
|
except Exception, e:
|
||||||
|
assert_contains(str(e), "parameterized.expand")
|
||||||
|
else:
|
||||||
|
raise AssertionError("Expected exception not raised")
|
||||||
|
|
||||||
|
|
||||||
|
class TestParamerizedOnTestCase(TestCase):
|
||||||
|
@parameterized.expand([("stuff", )])
|
||||||
|
def test_parameterized_on_TestCase(self, input):
|
||||||
|
assert_equal(input, "stuff")
|
||||||
|
missing_tests.remove("test_parameterized_on_TestCase")
|
18
setup.py
Normal file
18
setup.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
os.chdir(os.path.dirname(sys.argv[0]) or ".")
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="nose-parameterized",
|
||||||
|
version="0.1",
|
||||||
|
url="https://github.com/wolever/nose-parameterized",
|
||||||
|
author="David Wolever",
|
||||||
|
author_email="david@wolever.net",
|
||||||
|
description="Nose decorator for parameterized testing",
|
||||||
|
packages=find_packages(),
|
||||||
|
)
|
Reference in New Issue
Block a user