mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 11:49:12 +00:00 
			
		
		
		
	* Functions registered with addModuleCleanup() were not called unless the user defines tearDownModule() in their test module. * Functions registered with addClassCleanup() were not called if tearDownClass is set to None. * Buffering in TestResult did not work with functions registered with addClassCleanup() and addModuleCleanup(). * Errors in functions registered with addClassCleanup() and addModuleCleanup() were not handled correctly in buffered and debug modes. * Errors in setUpModule() and functions registered with addModuleCleanup() were reported in wrong order. * And several lesser bugs.
		
			
				
	
	
		
			379 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""TestSuite"""
 | 
						|
 | 
						|
import sys
 | 
						|
 | 
						|
from . import case
 | 
						|
from . import util
 | 
						|
 | 
						|
__unittest = True
 | 
						|
 | 
						|
 | 
						|
def _call_if_exists(parent, attr):
 | 
						|
    func = getattr(parent, attr, lambda: None)
 | 
						|
    func()
 | 
						|
 | 
						|
 | 
						|
class BaseTestSuite(object):
 | 
						|
    """A simple test suite that doesn't provide class or module shared fixtures.
 | 
						|
    """
 | 
						|
    _cleanup = True
 | 
						|
 | 
						|
    def __init__(self, tests=()):
 | 
						|
        self._tests = []
 | 
						|
        self._removed_tests = 0
 | 
						|
        self.addTests(tests)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        if not isinstance(other, self.__class__):
 | 
						|
            return NotImplemented
 | 
						|
        return list(self) == list(other)
 | 
						|
 | 
						|
    def __iter__(self):
 | 
						|
        return iter(self._tests)
 | 
						|
 | 
						|
    def countTestCases(self):
 | 
						|
        cases = self._removed_tests
 | 
						|
        for test in self:
 | 
						|
            if test:
 | 
						|
                cases += test.countTestCases()
 | 
						|
        return cases
 | 
						|
 | 
						|
    def addTest(self, test):
 | 
						|
        # sanity checks
 | 
						|
        if not callable(test):
 | 
						|
            raise TypeError("{} is not callable".format(repr(test)))
 | 
						|
        if isinstance(test, type) and issubclass(test,
 | 
						|
                                                 (case.TestCase, TestSuite)):
 | 
						|
            raise TypeError("TestCases and TestSuites must be instantiated "
 | 
						|
                            "before passing them to addTest()")
 | 
						|
        self._tests.append(test)
 | 
						|
 | 
						|
    def addTests(self, tests):
 | 
						|
        if isinstance(tests, str):
 | 
						|
            raise TypeError("tests must be an iterable of tests, not a string")
 | 
						|
        for test in tests:
 | 
						|
            self.addTest(test)
 | 
						|
 | 
						|
    def run(self, result):
 | 
						|
        for index, test in enumerate(self):
 | 
						|
            if result.shouldStop:
 | 
						|
                break
 | 
						|
            test(result)
 | 
						|
            if self._cleanup:
 | 
						|
                self._removeTestAtIndex(index)
 | 
						|
        return result
 | 
						|
 | 
						|
    def _removeTestAtIndex(self, index):
 | 
						|
        """Stop holding a reference to the TestCase at index."""
 | 
						|
        try:
 | 
						|
            test = self._tests[index]
 | 
						|
        except TypeError:
 | 
						|
            # support for suite implementations that have overridden self._tests
 | 
						|
            pass
 | 
						|
        else:
 | 
						|
            # Some unittest tests add non TestCase/TestSuite objects to
 | 
						|
            # the suite.
 | 
						|
            if hasattr(test, 'countTestCases'):
 | 
						|
                self._removed_tests += test.countTestCases()
 | 
						|
            self._tests[index] = None
 | 
						|
 | 
						|
    def __call__(self, *args, **kwds):
 | 
						|
        return self.run(*args, **kwds)
 | 
						|
 | 
						|
    def debug(self):
 | 
						|
        """Run the tests without collecting errors in a TestResult"""
 | 
						|
        for test in self:
 | 
						|
            test.debug()
 | 
						|
 | 
						|
 | 
						|
class TestSuite(BaseTestSuite):
 | 
						|
    """A test suite is a composite test consisting of a number of TestCases.
 | 
						|
 | 
						|
    For use, create an instance of TestSuite, then add test case instances.
 | 
						|
    When all tests have been added, the suite can be passed to a test
 | 
						|
    runner, such as TextTestRunner. It will run the individual test cases
 | 
						|
    in the order in which they were added, aggregating the results. When
 | 
						|
    subclassing, do not forget to call the base class constructor.
 | 
						|
    """
 | 
						|
 | 
						|
    def run(self, result, debug=False):
 | 
						|
        topLevel = False
 | 
						|
        if getattr(result, '_testRunEntered', False) is False:
 | 
						|
            result._testRunEntered = topLevel = True
 | 
						|
 | 
						|
        for index, test in enumerate(self):
 | 
						|
            if result.shouldStop:
 | 
						|
                break
 | 
						|
 | 
						|
            if _isnotsuite(test):
 | 
						|
                self._tearDownPreviousClass(test, result)
 | 
						|
                self._handleModuleFixture(test, result)
 | 
						|
                self._handleClassSetUp(test, result)
 | 
						|
                result._previousTestClass = test.__class__
 | 
						|
 | 
						|
                if (getattr(test.__class__, '_classSetupFailed', False) or
 | 
						|
                    getattr(result, '_moduleSetUpFailed', False)):
 | 
						|
                    continue
 | 
						|
 | 
						|
            if not debug:
 | 
						|
                test(result)
 | 
						|
            else:
 | 
						|
                test.debug()
 | 
						|
 | 
						|
            if self._cleanup:
 | 
						|
                self._removeTestAtIndex(index)
 | 
						|
 | 
						|
        if topLevel:
 | 
						|
            self._tearDownPreviousClass(None, result)
 | 
						|
            self._handleModuleTearDown(result)
 | 
						|
            result._testRunEntered = False
 | 
						|
        return result
 | 
						|
 | 
						|
    def debug(self):
 | 
						|
        """Run the tests without collecting errors in a TestResult"""
 | 
						|
        debug = _DebugResult()
 | 
						|
        self.run(debug, True)
 | 
						|
 | 
						|
    ################################
 | 
						|
 | 
						|
    def _handleClassSetUp(self, test, result):
 | 
						|
        previousClass = getattr(result, '_previousTestClass', None)
 | 
						|
        currentClass = test.__class__
 | 
						|
        if currentClass == previousClass:
 | 
						|
            return
 | 
						|
        if result._moduleSetUpFailed:
 | 
						|
            return
 | 
						|
        if getattr(currentClass, "__unittest_skip__", False):
 | 
						|
            return
 | 
						|
 | 
						|
        failed = False
 | 
						|
        try:
 | 
						|
            currentClass._classSetupFailed = False
 | 
						|
        except TypeError:
 | 
						|
            # test may actually be a function
 | 
						|
            # so its class will be a builtin-type
 | 
						|
            pass
 | 
						|
 | 
						|
        setUpClass = getattr(currentClass, 'setUpClass', None)
 | 
						|
        doClassCleanups = getattr(currentClass, 'doClassCleanups', None)
 | 
						|
        if setUpClass is not None:
 | 
						|
            _call_if_exists(result, '_setupStdout')
 | 
						|
            try:
 | 
						|
                try:
 | 
						|
                    setUpClass()
 | 
						|
                except Exception as e:
 | 
						|
                    if isinstance(result, _DebugResult):
 | 
						|
                        raise
 | 
						|
                    failed = True
 | 
						|
                    try:
 | 
						|
                        currentClass._classSetupFailed = True
 | 
						|
                    except TypeError:
 | 
						|
                        pass
 | 
						|
                    className = util.strclass(currentClass)
 | 
						|
                    self._createClassOrModuleLevelException(result, e,
 | 
						|
                                                            'setUpClass',
 | 
						|
                                                            className)
 | 
						|
                if failed and doClassCleanups is not None:
 | 
						|
                    doClassCleanups()
 | 
						|
                    for exc_info in currentClass.tearDown_exceptions:
 | 
						|
                        self._createClassOrModuleLevelException(
 | 
						|
                                result, exc_info[1], 'setUpClass', className,
 | 
						|
                                info=exc_info)
 | 
						|
            finally:
 | 
						|
                _call_if_exists(result, '_restoreStdout')
 | 
						|
 | 
						|
    def _get_previous_module(self, result):
 | 
						|
        previousModule = None
 | 
						|
        previousClass = getattr(result, '_previousTestClass', None)
 | 
						|
        if previousClass is not None:
 | 
						|
            previousModule = previousClass.__module__
 | 
						|
        return previousModule
 | 
						|
 | 
						|
 | 
						|
    def _handleModuleFixture(self, test, result):
 | 
						|
        previousModule = self._get_previous_module(result)
 | 
						|
        currentModule = test.__class__.__module__
 | 
						|
        if currentModule == previousModule:
 | 
						|
            return
 | 
						|
 | 
						|
        self._handleModuleTearDown(result)
 | 
						|
 | 
						|
 | 
						|
        result._moduleSetUpFailed = False
 | 
						|
        try:
 | 
						|
            module = sys.modules[currentModule]
 | 
						|
        except KeyError:
 | 
						|
            return
 | 
						|
        setUpModule = getattr(module, 'setUpModule', None)
 | 
						|
        if setUpModule is not None:
 | 
						|
            _call_if_exists(result, '_setupStdout')
 | 
						|
            try:
 | 
						|
                try:
 | 
						|
                    setUpModule()
 | 
						|
                except Exception as e:
 | 
						|
                    if isinstance(result, _DebugResult):
 | 
						|
                        raise
 | 
						|
                    result._moduleSetUpFailed = True
 | 
						|
                    self._createClassOrModuleLevelException(result, e,
 | 
						|
                                                            'setUpModule',
 | 
						|
                                                            currentModule)
 | 
						|
                if result._moduleSetUpFailed:
 | 
						|
                    try:
 | 
						|
                        case.doModuleCleanups()
 | 
						|
                    except Exception as e:
 | 
						|
                        self._createClassOrModuleLevelException(result, e,
 | 
						|
                                                                'setUpModule',
 | 
						|
                                                                currentModule)
 | 
						|
            finally:
 | 
						|
                _call_if_exists(result, '_restoreStdout')
 | 
						|
 | 
						|
    def _createClassOrModuleLevelException(self, result, exc, method_name,
 | 
						|
                                           parent, info=None):
 | 
						|
        errorName = f'{method_name} ({parent})'
 | 
						|
        self._addClassOrModuleLevelException(result, exc, errorName, info)
 | 
						|
 | 
						|
    def _addClassOrModuleLevelException(self, result, exception, errorName,
 | 
						|
                                        info=None):
 | 
						|
        error = _ErrorHolder(errorName)
 | 
						|
        addSkip = getattr(result, 'addSkip', None)
 | 
						|
        if addSkip is not None and isinstance(exception, case.SkipTest):
 | 
						|
            addSkip(error, str(exception))
 | 
						|
        else:
 | 
						|
            if not info:
 | 
						|
                result.addError(error, sys.exc_info())
 | 
						|
            else:
 | 
						|
                result.addError(error, info)
 | 
						|
 | 
						|
    def _handleModuleTearDown(self, result):
 | 
						|
        previousModule = self._get_previous_module(result)
 | 
						|
        if previousModule is None:
 | 
						|
            return
 | 
						|
        if result._moduleSetUpFailed:
 | 
						|
            return
 | 
						|
 | 
						|
        try:
 | 
						|
            module = sys.modules[previousModule]
 | 
						|
        except KeyError:
 | 
						|
            return
 | 
						|
 | 
						|
        _call_if_exists(result, '_setupStdout')
 | 
						|
        try:
 | 
						|
            tearDownModule = getattr(module, 'tearDownModule', None)
 | 
						|
            if tearDownModule is not None:
 | 
						|
                try:
 | 
						|
                    tearDownModule()
 | 
						|
                except Exception as e:
 | 
						|
                    if isinstance(result, _DebugResult):
 | 
						|
                        raise
 | 
						|
                    self._createClassOrModuleLevelException(result, e,
 | 
						|
                                                            'tearDownModule',
 | 
						|
                                                            previousModule)
 | 
						|
            try:
 | 
						|
                case.doModuleCleanups()
 | 
						|
            except Exception as e:
 | 
						|
                if isinstance(result, _DebugResult):
 | 
						|
                    raise
 | 
						|
                self._createClassOrModuleLevelException(result, e,
 | 
						|
                                                        'tearDownModule',
 | 
						|
                                                        previousModule)
 | 
						|
        finally:
 | 
						|
            _call_if_exists(result, '_restoreStdout')
 | 
						|
 | 
						|
    def _tearDownPreviousClass(self, test, result):
 | 
						|
        previousClass = getattr(result, '_previousTestClass', None)
 | 
						|
        currentClass = test.__class__
 | 
						|
        if currentClass == previousClass or previousClass is None:
 | 
						|
            return
 | 
						|
        if getattr(previousClass, '_classSetupFailed', False):
 | 
						|
            return
 | 
						|
        if getattr(result, '_moduleSetUpFailed', False):
 | 
						|
            return
 | 
						|
        if getattr(previousClass, "__unittest_skip__", False):
 | 
						|
            return
 | 
						|
 | 
						|
        tearDownClass = getattr(previousClass, 'tearDownClass', None)
 | 
						|
        doClassCleanups = getattr(previousClass, 'doClassCleanups', None)
 | 
						|
        if tearDownClass is None and doClassCleanups is None:
 | 
						|
            return
 | 
						|
 | 
						|
        _call_if_exists(result, '_setupStdout')
 | 
						|
        try:
 | 
						|
            if tearDownClass is not None:
 | 
						|
                try:
 | 
						|
                    tearDownClass()
 | 
						|
                except Exception as e:
 | 
						|
                    if isinstance(result, _DebugResult):
 | 
						|
                        raise
 | 
						|
                    className = util.strclass(previousClass)
 | 
						|
                    self._createClassOrModuleLevelException(result, e,
 | 
						|
                                                            'tearDownClass',
 | 
						|
                                                            className)
 | 
						|
            if doClassCleanups is not None:
 | 
						|
                doClassCleanups()
 | 
						|
                for exc_info in previousClass.tearDown_exceptions:
 | 
						|
                    if isinstance(result, _DebugResult):
 | 
						|
                        raise exc_info[1]
 | 
						|
                    className = util.strclass(previousClass)
 | 
						|
                    self._createClassOrModuleLevelException(result, exc_info[1],
 | 
						|
                                                            'tearDownClass',
 | 
						|
                                                            className,
 | 
						|
                                                            info=exc_info)
 | 
						|
        finally:
 | 
						|
            _call_if_exists(result, '_restoreStdout')
 | 
						|
 | 
						|
 | 
						|
class _ErrorHolder(object):
 | 
						|
    """
 | 
						|
    Placeholder for a TestCase inside a result. As far as a TestResult
 | 
						|
    is concerned, this looks exactly like a unit test. Used to insert
 | 
						|
    arbitrary errors into a test suite run.
 | 
						|
    """
 | 
						|
    # Inspired by the ErrorHolder from Twisted:
 | 
						|
    # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
 | 
						|
 | 
						|
    # attribute used by TestResult._exc_info_to_string
 | 
						|
    failureException = None
 | 
						|
 | 
						|
    def __init__(self, description):
 | 
						|
        self.description = description
 | 
						|
 | 
						|
    def id(self):
 | 
						|
        return self.description
 | 
						|
 | 
						|
    def shortDescription(self):
 | 
						|
        return None
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<ErrorHolder description=%r>" % (self.description,)
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return self.id()
 | 
						|
 | 
						|
    def run(self, result):
 | 
						|
        # could call result.addError(...) - but this test-like object
 | 
						|
        # shouldn't be run anyway
 | 
						|
        pass
 | 
						|
 | 
						|
    def __call__(self, result):
 | 
						|
        return self.run(result)
 | 
						|
 | 
						|
    def countTestCases(self):
 | 
						|
        return 0
 | 
						|
 | 
						|
def _isnotsuite(test):
 | 
						|
    "A crude way to tell apart testcases and suites with duck-typing"
 | 
						|
    try:
 | 
						|
        iter(test)
 | 
						|
    except TypeError:
 | 
						|
        return True
 | 
						|
    return False
 | 
						|
 | 
						|
 | 
						|
class _DebugResult(object):
 | 
						|
    "Used by the TestSuite to hold previous class when running in debug."
 | 
						|
    _previousTestClass = None
 | 
						|
    _moduleSetUpFailed = False
 | 
						|
    shouldStop = False
 |