diff --git a/IPython/testing/_paramtestpy2.py b/IPython/testing/_paramtestpy2.py new file mode 100644 index 0000000..07e3a9a --- /dev/null +++ b/IPython/testing/_paramtestpy2.py @@ -0,0 +1,89 @@ +"""Implementation of the parametric test support for Python 2.x +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib +import unittest +from compiler.consts import CO_GENERATOR + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +def isgenerator(func): + try: + return func.func_code.co_flags & CO_GENERATOR != 0 + except AttributeError: + return False + +class ParametricTestCase(unittest.TestCase): + """Write parametric tests in normal unittest testcase form. + + Limitations: the last iteration misses printing out a newline when running + in verbose mode. + """ + def run_parametric(self, result, testMethod): + # But if we have a test generator, we iterate it ourselves + testgen = testMethod() + while True: + try: + # Initialize test + result.startTest(self) + + # SetUp + try: + self.setUp() + except KeyboardInterrupt: + raise + except: + result.addError(self, self._exc_info()) + return + # Test execution + ok = False + try: + testgen.next() + ok = True + except StopIteration: + # We stop the loop + break + except self.failureException: + result.addFailure(self, self._exc_info()) + except KeyboardInterrupt: + raise + except: + result.addError(self, self._exc_info()) + # TearDown + try: + self.tearDown() + except KeyboardInterrupt: + raise + except: + result.addError(self, self._exc_info()) + ok = False + if ok: result.addSuccess(self) + + finally: + result.stopTest(self) + + def run(self, result=None): + if result is None: + result = self.defaultTestResult() + testMethod = getattr(self, self._testMethodName) + # For normal tests, we just call the base class and return that + if isgenerator(testMethod): + return self.run_parametric(result, testMethod) + else: + return super(ParametricTestCase, self).run(result) + + +def parametric(func): + """Decorator to make a simple function into a normal test via unittest.""" + + class Tester(ParametricTestCase): + test = staticmethod(func) + + Tester.__name__ = func.__name__ + + return Tester diff --git a/IPython/testing/_paramtestpy3.py b/IPython/testing/_paramtestpy3.py new file mode 100644 index 0000000..0e16a36 --- /dev/null +++ b/IPython/testing/_paramtestpy3.py @@ -0,0 +1,62 @@ +"""Implementation of the parametric test support for Python 3.x. + +Thanks for the py3 version to Robert Collins, from the Testing in Python +mailing list. +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib +import unittest +from unittest import TestSuite + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + + +def isgenerator(func): + return hasattr(func,'_generator') + + +class IterCallableSuite(TestSuite): + def __init__(self, iterator, adapter): + self._iter = iterator + self._adapter = adapter + def __iter__(self): + yield self._adapter(self._iter.__next__) + +class ParametricTestCase(unittest.TestCase): + """Write parametric tests in normal unittest testcase form. + + Limitations: the last iteration misses printing out a newline when + running in verbose mode. + """ + + def run(self, result=None): + testMethod = getattr(self, self._testMethodName) + # For normal tests, we just call the base class and return that + if isgenerator(testMethod): + def adapter(next_test): + return unittest.FunctionTestCase(next_test, + self.setUp, + self.tearDown) + + return IterCallableSuite(testMethod(),adapter).run(result) + else: + return super(ParametricTestCase, self).run(result) + + +def parametric(func): + """Decorator to make a simple function into a normal test via +unittest.""" + # Hack, until I figure out how to write isgenerator() for python3!! + func._generator = True + + class Tester(ParametricTestCase): + test = staticmethod(func) + + Tester.__name__ = func.__name__ + + return Tester diff --git a/IPython/testing/nosepatch.py b/IPython/testing/nosepatch.py new file mode 100644 index 0000000..1065a3e --- /dev/null +++ b/IPython/testing/nosepatch.py @@ -0,0 +1,53 @@ +"""Monkeypatch nose to accept any callable as a method. + +By default, nose's ismethod() fails for static methods. +Once this is fixed in upstream nose we can disable it. + +Note: merely importing this module causes the monkeypatch to be applied.""" + +import unittest +import nose.loader +from inspect import ismethod, isfunction + +def getTestCaseNames(self, testCaseClass): + """Override to select with selector, unless + config.getTestCaseNamesCompat is True + """ + if self.config.getTestCaseNamesCompat: + return unittest.TestLoader.getTestCaseNames(self, testCaseClass) + + def wanted(attr, cls=testCaseClass, sel=self.selector): + item = getattr(cls, attr, None) + # MONKEYPATCH: replace this: + #if not ismethod(item): + # return False + # return sel.wantMethod(item) + # With: + if ismethod(item): + return sel.wantMethod(item) + # static method or something. If this is a static method, we + # can't get the class information, and we have to treat it + # as a function. Thus, we will miss things like class + # attributes for test selection + if isfunction(item): + return sel.wantFunction(item) + return False + # END MONKEYPATCH + + cases = filter(wanted, dir(testCaseClass)) + for base in testCaseClass.__bases__: + for case in self.getTestCaseNames(base): + if case not in cases: + cases.append(case) + # add runTest if nothing else picked + if not cases and hasattr(testCaseClass, 'runTest'): + cases = ['runTest'] + if self.sortTestMethodsUsing: + cases.sort(self.sortTestMethodsUsing) + return cases + + +########################################################################## +# Apply monkeypatch here +nose.loader.TestLoader.getTestCaseNames = getTestCaseNames +##########################################################################