Source code for nose2.plugins.loader.generators

"""
Load tests from generators.

This plugin implements :func:`loadTestFromTestCase`,
:func:`loadTestsFromName` and :func:`loadTestFromModule` to enable
loading tests from generators.

Generators may be functions or methods in test cases. In either case,
they must yield a callable and arguments for that callable once for
each test they generate. The callable and arguments may all be in one
tuple, or the arguments may be grouped into a separate tuple::

  def test_gen():
      yield check, 1, 2
      yield check, (1, 2)

To address a particular generated test via a command-line test name,
append a colon (':') followed by the index (*starting from 1*) of the
generated case you want to execute.

"""
# This module contains some code copied from unittest2 and other code
# developed in reference to unittest2.
# unittest2 is Copyright (c) 2001-2010 Python Software Foundation; All
# Rights Reserved. See: http://docs.python.org/license.html

import functools
import inspect
import logging
import sys
import types
import unittest

from nose2 import exceptions, util
from nose2.events import Plugin

log = logging.getLogger(__name__)
__unittest = True


[docs] class Generators(Plugin): """Loader plugin that loads generator tests""" alwaysOn = True configSection = "generators" def registerInSubprocess(self, event): event.pluginClasses.append(self.__class__) def unpack(self, generator): for index, func_args in enumerate(generator): try: func, args = func_args if not isinstance(args, tuple): args = (args,) yield index, (func, args) except ValueError: func, args = func_args[0], func_args[1:] yield index, (func, args)
[docs] def loadTestsFromTestCase(self, event): """Load generator tests from test case""" log.debug("loadTestsFromTestCase %s", event.testCase) testCaseClass = event.testCase for name, method in util.iter_attrs(testCaseClass): if not inspect.isgeneratorfunction(method): continue if not name.startswith(self.session.testMethodPrefix): continue instance = testCaseClass(name) event.extraTests.extend( self._testsFromGenerator(event, name, method(instance), testCaseClass) )
def loadTestsFromTestClass(self, event): testCaseClass = event.testCase for name, method in util.iter_attrs(testCaseClass): if not inspect.isgeneratorfunction(method): continue if not name.startswith(self.session.testMethodPrefix): continue instance = testCaseClass() event.extraTests.extend( self._testsFromGeneratorMethod(event, name, method, instance) )
[docs] def getTestCaseNames(self, event): """Get generator test case names from test case class""" log.debug("getTestCaseNames %s", event.testCase) for name, method in util.iter_attrs(event.testCase): if not inspect.isgeneratorfunction(method): continue if not event.isTestMethod(name): continue event.excludedNames.append(name)
def getTestMethodNames(self, event): return self.getTestCaseNames(event)
[docs] def loadTestsFromName(self, event): """Load tests from generator named on command line""" original_name = name = event.name module = event.module try: result = util.test_from_name(name, module) except (AttributeError, ImportError): event.handled = True return event.loader.failedLoadTests(name, sys.exc_info()) if result is None: # we can't find it - let the default case handle it return parent, obj, name, index = result if not inspect.isgeneratorfunction(obj): return if ( index is None and not isinstance(parent, type) and not isinstance(obj, types.FunctionType) ): log.debug("Don't know how to load generator tests from %s", obj) return if ( parent and isinstance(parent, type) and issubclass(parent, unittest.TestCase) ): # generator method in test case instance = parent(obj.__name__) tests = list( self._testsFromGenerator(event, obj.__name__, obj(instance), parent) ) elif parent and isinstance(parent, type): # generator method in test class method = obj instance = parent() tests = list(self._testsFromGeneratorMethod(event, name, method, instance)) else: # generator func tests = list(self._testsFromGeneratorFunc(event, obj)) if index is not None: try: tests = [tests[index - 1]] except IndexError: raise exceptions.TestNotFoundError(original_name) suite = event.loader.suiteClass() suite.addTests(tests) event.handled = True return suite
[docs] def loadTestsFromModule(self, event): """Load tests from generator functions in a module""" module = event.module def is_test(obj): return obj.__name__.startswith( self.session.testMethodPrefix ) and inspect.isgeneratorfunction(obj) tests = [] for _, obj in util.iter_attrs(module): if isinstance(obj, types.FunctionType) and is_test(obj): tests.extend(self._testsFromGeneratorFunc(event, obj)) event.extraTests.extend(tests)
def _testsFromGenerator(self, event, name, generator, testCaseClass): try: for index, (func, args) in self.unpack(generator): method_name = util.name_from_args(name, index, args) setattr(testCaseClass, method_name, None) instance = testCaseClass(method_name) delattr(testCaseClass, method_name) def method(func=func, args=args): return func(*args) method = functools.update_wrapper(method, func) setattr(instance, method_name, method) yield instance except Exception as e: test_name = f"{testCaseClass.__module__}.{testCaseClass.__name__}.{name}" yield event.loader.failedLoadTests(test_name, e) def _testsFromGeneratorFunc(self, event, obj): extras = list(obj()) name = f"{obj.__module__}.{obj.__name__}" args = {} setUp = getattr(obj, "setUp", None) tearDown = getattr(obj, "tearDown", None) if setUp is not None: args["setUp"] = setUp if tearDown is not None: args["tearDown"] = tearDown def createTest(name): return util.transplant_class(GeneratorFunctionCase, obj.__module__)( name, **args ) yield from self._testsFromGenerator(event, name, extras, createTest) def _testsFromGeneratorMethod(self, event, name, method, instance): extras = list(method(instance)) name = "{}.{}.{}".format( instance.__class__.__module__, instance.__class__.__name__, method.__name__, ) args = {} setUp = getattr(instance, "setUp", None) tearDown = getattr(instance, "tearDown", None) if setUp is not None: args["setUp"] = setUp if tearDown is not None: args["tearDown"] = tearDown def createTest(name): return util.transplant_class( GeneratorMethodCase(instance.__class__), instance.__class__.__module__ )(name, **args) yield from self._testsFromGenerator(event, name, extras, createTest)
class GeneratorFunctionCase(unittest.FunctionTestCase): def __init__(self, name, **args): self._funcName = name unittest.FunctionTestCase.__init__(self, None, **args) _testFunc = property( lambda self: getattr(self, self._funcName), lambda self, func: None ) def __repr__(self): return self._funcName id = __str__ = __repr__ def GeneratorMethodCase(cls): class _GeneratorMethodCase(GeneratorFunctionCase): if util.has_class_fixtures(cls): @classmethod def setUpClass(klass): if hasattr(cls, "setUpClass"): cls.setUpClass() @classmethod def tearDownClass(klass): if hasattr(cls, "tearDownClass"): cls.tearDownClass() # XXX retain original class name _GeneratorMethodCase.__name__ = cls.__name__ return _GeneratorMethodCase