| """ | |
| nose's test loader implements the nosetests functionality | |
| """ | |
| from __future__ import generators | |
| import os | |
| import sys | |
| import unittest | |
| from inspect import isfunction, ismethod | |
| from nose_helper.case import FunctionTestCase, MethodTestCase | |
| from nose_helper.failure import Failure | |
| from nose_helper.config import Config | |
| from nose_helper.selector import defaultSelector | |
| from nose_helper.util import cmp_lineno, func_lineno, isclass, isgenerator, ismethod, isunboundmethod | |
| from nose_helper.util import transplant_class, transplant_func | |
| from nose_helper.suite import ContextSuiteFactory, ContextList | |
| op_normpath = os.path.normpath | |
| op_abspath = os.path.abspath | |
| PYTHON_VERSION_MAJOR = sys.version_info[0] | |
| PYTHON_VERSION_MINOR = sys.version_info[1] | |
| from nose_helper.util import unbound_method | |
| import types | |
| class TestLoader(unittest.TestLoader): | |
| """Test loader that extends unittest.TestLoader to support nosetests | |
| """ | |
| config = None | |
| workingDir = None | |
| selector = None | |
| suiteClass = None | |
| def __init__(self): | |
| """Initialize a test loader. | |
| """ | |
| self.config = Config() | |
| self.selector = defaultSelector(self.config) | |
| self.workingDir = op_normpath(op_abspath(self.config.workingDir)) | |
| self.suiteClass = ContextSuiteFactory(config=self.config) | |
| unittest.TestLoader.__init__(self) | |
| def loadTestsFromGenerator(self, generator, module, lineno): | |
| """The generator function may yield either: | |
| * a callable, or | |
| * a function name resolvable within the same module | |
| """ | |
| def generate(g=generator, m=module): | |
| try: | |
| for test in g(): | |
| test_func, arg = self.parseGeneratedTest(test) | |
| if not hasattr(test_func, '__call__'): | |
| test_func = getattr(m, test_func) | |
| test_case = FunctionTestCase(test_func, arg=arg, descriptor=g) | |
| test_case.lineno = lineno | |
| yield test_case | |
| except KeyboardInterrupt: | |
| raise | |
| except: | |
| exc = sys.exc_info() | |
| yield Failure(exc[0], exc[1], exc[2]) | |
| return self.suiteClass(generate, context=generator) | |
| def loadTestsFromModule(self, module, direct = True): | |
| """Load all tests from module and return a suite containing | |
| them. | |
| """ | |
| tests = [] | |
| test_funcs = [] | |
| test_classes = [] | |
| if self.selector.wantModule(module) or direct: | |
| for item in dir(module): | |
| test = getattr(module, item, None) | |
| if isclass(test): | |
| if self.selector.wantClass(test): | |
| test_classes.append(test) | |
| elif isfunction(test) and self.selector.wantFunction(test): | |
| test_funcs.append(test) | |
| if PYTHON_VERSION_MAJOR != 3: | |
| test_classes.sort(lambda a, b: cmp(a.__name__, b.__name__)) | |
| test_funcs.sort(cmp_lineno) | |
| tests = map(lambda t: self.makeTest(t, parent=module), | |
| test_classes + test_funcs) | |
| else: | |
| test_classes.sort(key = lambda a: a.__name__) | |
| test_funcs.sort(key = func_lineno) | |
| tests = [self.makeTest(t, parent=module) for t in | |
| test_classes + test_funcs] | |
| return self.suiteClass(ContextList(tests, context=module)) | |
| def loadTestsFromTestClass(self, cls): | |
| """Load tests from a test class that is *not* a unittest.TestCase | |
| subclass. | |
| """ | |
| def wanted(attr, cls=cls, sel=self.selector): | |
| item = getattr(cls, attr, None) | |
| if isfunction(item): | |
| item = unbound_method(cls, item) | |
| if not ismethod(item): | |
| return False | |
| return sel.wantMethod(item) | |
| cases = [self.makeTest(getattr(cls, case), cls) | |
| for case in filter(wanted, dir(cls))] | |
| return self.suiteClass(ContextList(cases, context=cls)) | |
| def makeTest(self, obj, parent=None): | |
| try: | |
| return self._makeTest(obj, parent) | |
| except (KeyboardInterrupt, SystemExit): | |
| raise | |
| except: | |
| exc = sys.exc_info() | |
| return Failure(exc[0], exc[1], exc[2]) | |
| def _makeTest(self, obj, parent=None): | |
| """Given a test object and its parent, return a test case | |
| or test suite. | |
| """ | |
| import inspect | |
| try: | |
| lineno = inspect.getsourcelines(obj) | |
| except: | |
| lineno = ("", 1) | |
| if isfunction(obj) and parent and not isinstance(parent, types.ModuleType): | |
| obj = unbound_method(parent, obj) | |
| if isinstance(obj, unittest.TestCase): | |
| return obj | |
| elif isclass(obj): | |
| if parent and obj.__module__ != parent.__name__: | |
| obj = transplant_class(obj, parent.__name__) | |
| if issubclass(obj, unittest.TestCase): | |
| return self.loadTestsFromTestCase(obj) | |
| else: | |
| return self.loadTestsFromTestClass(obj) | |
| elif ismethod(obj) or isunboundmethod(obj): | |
| if parent is None: | |
| parent = obj.__class__ | |
| if issubclass(parent, unittest.TestCase): | |
| return parent(obj.__name__) | |
| else: | |
| if PYTHON_VERSION_MAJOR > 2: | |
| setattr(obj, "im_class", parent) | |
| setattr(obj, "im_self", parent) | |
| test_case = MethodTestCase(obj) | |
| test_case.lineno = lineno[1] | |
| return test_case | |
| elif isfunction(obj): | |
| setattr(obj, "lineno", lineno[1]) | |
| if hasattr(obj, "__module__"): | |
| if parent and obj.__module__ != parent.__name__: | |
| obj = transplant_func(obj, parent.__name__) | |
| else: | |
| if parent: | |
| obj = transplant_func(obj, parent.__name__) | |
| else: | |
| obj = transplant_func(obj) | |
| if isgenerator(obj): | |
| return self.loadTestsFromGenerator(obj, parent, lineno[1]) | |
| else: | |
| return FunctionTestCase(obj) | |
| else: | |
| return Failure(TypeError, | |
| "Can't make a test from %s" % obj) | |
| def loadTestsFromTestCase(self, testCaseClass): | |
| """Return a suite of all tests cases contained in testCaseClass""" | |
| try: | |
| # PY-2412 | |
| # because of Twisted overrides runTest function and we don't need to harvest them | |
| import twisted.trial.unittest | |
| if issubclass(testCaseClass, twisted.trial.unittest.TestCase): | |
| testCaseNames = self.getTestCaseNames(testCaseClass) | |
| return self.suiteClass(map(testCaseClass, testCaseNames)) | |
| except ImportError: | |
| pass | |
| if issubclass(testCaseClass, unittest.TestSuite): | |
| raise TypeError("Test cases should not be derived from TestSuite. Maybe you meant to derive from TestCase?") | |
| testCaseNames = self.getTestCaseNames(testCaseClass) | |
| if not testCaseNames and hasattr(testCaseClass, 'runTest'): | |
| testCaseNames = ['runTest'] | |
| return self.suiteClass(map(testCaseClass, testCaseNames)) | |
| def parseGeneratedTest(self, test): | |
| """Given the yield value of a test generator, return a func and args. | |
| """ | |
| if not isinstance(test, tuple): # yield test | |
| test_func, arg = (test, tuple()) | |
| elif len(test) == 1: # yield (test,) | |
| test_func, arg = (test[0], tuple()) | |
| else: # yield test, foo, bar, ... | |
| assert len(test) > 1 # sanity check | |
| test_func, arg = (test[0], test[1:]) | |
| return test_func, arg | |
| defaultLoader = TestLoader() |