123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- """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
|