summaryrefslogtreecommitdiffstats
path: root/Lib/unittest
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/unittest')
-rw-r--r--Lib/unittest/__init__.py3
-rw-r--r--Lib/unittest/case.py11
-rw-r--r--Lib/unittest/result.py2
-rw-r--r--Lib/unittest/suite.py199
4 files changed, 205 insertions, 10 deletions
diff --git a/Lib/unittest/__init__.py b/Lib/unittest/__init__.py
index 4a308fa..06fe55d 100644
--- a/Lib/unittest/__init__.py
+++ b/Lib/unittest/__init__.py
@@ -51,13 +51,12 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite',
# Expose obsolete functions for backwards compatibility
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
-__all__.append('_TextTestResult')
from .result import TestResult
from .case import (TestCase, FunctionTestCase, SkipTest, skip, skipIf,
skipUnless, expectedFailure)
-from .suite import TestSuite
+from .suite import BaseTestSuite, TestSuite
from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,
findTestCases)
from .main import TestProgram, main
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index 19b196c..0bfcc75 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -153,6 +153,9 @@ class TestCase(object):
longMessage = False
+ # Attribute used by TestSuite for classSetUp
+
+ _classSetupFailed = False
def __init__(self, methodName='runTest'):
"""Create an instance of the class that will use the named test
@@ -211,6 +214,14 @@ class TestCase(object):
"Hook method for deconstructing the test fixture after testing it."
pass
+ @classmethod
+ def setUpClass(cls):
+ "Hook method for setting up class fixture before running tests in the class."
+
+ @classmethod
+ def tearDownClass(cls):
+ "Hook method for deconstructing the class fixture after running all tests in the class."
+
def countTestCases(self):
return 1
diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py
index 22e825a..746967e 100644
--- a/Lib/unittest/result.py
+++ b/Lib/unittest/result.py
@@ -16,6 +16,8 @@ class TestResult(object):
contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
formatted traceback of the error that occurred.
"""
+ _previousTestClass = None
+ _moduleSetUpFailed = False
def __init__(self, stream=None, descriptions=None, verbosity=None):
self.failures = []
self.errors = []
diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py
index 60e9b6c..cccc7ef 100644
--- a/Lib/unittest/suite.py
+++ b/Lib/unittest/suite.py
@@ -1,17 +1,13 @@
"""TestSuite"""
+import sys
+
from . import case
from . import util
-class TestSuite(object):
- """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.
+class BaseTestSuite(object):
+ """A simple test suite that doesn't provide class or module shared fixtures.
"""
def __init__(self, tests=()):
self._tests = []
@@ -70,3 +66,190 @@ class TestSuite(object):
"""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):
+ self._wrapped_run(result)
+ self._tearDownPreviousClass(None, result)
+ self._handleModuleTearDown(result)
+ return result
+
+ ################################
+ # private methods
+ def _wrapped_run(self, result):
+ for test in 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 hasattr(test, '_wrapped_run'):
+ test._wrapped_run(result)
+ else:
+ test(result)
+
+ 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
+
+ currentClass._classSetupFailed = False
+
+ setUpClass = getattr(currentClass, 'setUpClass', None)
+ if setUpClass is not None:
+ try:
+ setUpClass()
+ except:
+ currentClass._classSetupFailed = True
+ self._addClassSetUpError(result, currentClass)
+
+ 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:
+ try:
+ setUpModule()
+ except:
+ result._moduleSetUpFailed = True
+ error = _ErrorHolder('setUpModule (%s)' % currentModule)
+ result.addError(error, sys.exc_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
+
+ tearDownModule = getattr(module, 'tearDownModule', None)
+ if tearDownModule is not None:
+ try:
+ tearDownModule()
+ except:
+ error = _ErrorHolder('tearDownModule (%s)' % previousModule)
+ result.addError(error, sys.exc_info())
+
+ def _tearDownPreviousClass(self, test, result):
+ previousClass = getattr(result, '_previousTestClass', None)
+ currentClass = test.__class__
+ if currentClass == previousClass:
+ 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)
+ if tearDownClass is not None:
+ try:
+ tearDownClass()
+ except:
+ self._addClassTearDownError(result)
+
+ def _addClassTearDownError(self, result):
+ className = util.strclass(result._previousTestClass)
+ error = _ErrorHolder('classTearDown (%s)' % className)
+ result.addError(error, sys.exc_info())
+
+ def _addClassSetUpError(self, result, klass):
+ className = util.strclass(klass)
+ error = _ErrorHolder('classSetUp (%s)' % className)
+ result.addError(error, sys.exc_info())
+
+
+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