""" 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()