blob: 77ce089187305b74630d40b7a0ce75e80ae54aa0 [file] [log] [blame]
Benjamin Petersonbed7d042009-07-19 21:01:52 +00001"""TestSuite"""
2
Benjamin Peterson847a4112010-03-14 15:04:17 +00003import sys
4
Benjamin Petersonbed7d042009-07-19 21:01:52 +00005from . import case
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +00006from . import util
Benjamin Petersonbed7d042009-07-19 21:01:52 +00007
Benjamin Petersondccc1fc2010-03-22 00:15:53 +00008__unittest = True
9
Benjamin Petersonbed7d042009-07-19 21:01:52 +000010
Benjamin Peterson847a4112010-03-14 15:04:17 +000011class BaseTestSuite(object):
12 """A simple test suite that doesn't provide class or module shared fixtures.
Benjamin Petersonbed7d042009-07-19 21:01:52 +000013 """
14 def __init__(self, tests=()):
15 self._tests = []
16 self.addTests(tests)
17
18 def __repr__(self):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000019 return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
Benjamin Petersonbed7d042009-07-19 21:01:52 +000020
21 def __eq__(self, other):
22 if not isinstance(other, self.__class__):
23 return NotImplemented
24 return list(self) == list(other)
25
26 def __ne__(self, other):
27 return not self == other
28
29 def __iter__(self):
30 return iter(self._tests)
31
32 def countTestCases(self):
33 cases = 0
34 for test in self:
35 cases += test.countTestCases()
36 return cases
37
38 def addTest(self, test):
39 # sanity checks
40 if not hasattr(test, '__call__'):
R. David Murray67d1bbd2010-01-29 17:55:47 +000041 raise TypeError("{} is not callable".format(repr(test)))
Benjamin Petersonbed7d042009-07-19 21:01:52 +000042 if isinstance(test, type) and issubclass(test,
43 (case.TestCase, TestSuite)):
44 raise TypeError("TestCases and TestSuites must be instantiated "
45 "before passing them to addTest()")
46 self._tests.append(test)
47
48 def addTests(self, tests):
49 if isinstance(tests, str):
50 raise TypeError("tests must be an iterable of tests, not a string")
51 for test in tests:
52 self.addTest(test)
53
54 def run(self, result):
55 for test in self:
56 if result.shouldStop:
57 break
58 test(result)
59 return result
60
61 def __call__(self, *args, **kwds):
62 return self.run(*args, **kwds)
63
64 def debug(self):
65 """Run the tests without collecting errors in a TestResult"""
66 for test in self:
67 test.debug()
Benjamin Peterson847a4112010-03-14 15:04:17 +000068
69
70class TestSuite(BaseTestSuite):
71 """A test suite is a composite test consisting of a number of TestCases.
72
73 For use, create an instance of TestSuite, then add test case instances.
74 When all tests have been added, the suite can be passed to a test
75 runner, such as TextTestRunner. It will run the individual test cases
76 in the order in which they were added, aggregating the results. When
77 subclassing, do not forget to call the base class constructor.
78 """
79
Michael Foordbbea35f2010-11-01 21:09:03 +000080 def run(self, result, debug=False):
81 topLevel = False
82 if getattr(result, '_testRunEntered', False) is False:
83 result._testRunEntered = topLevel = True
Benjamin Peterson847a4112010-03-14 15:04:17 +000084
Benjamin Peterson847a4112010-03-14 15:04:17 +000085 for test in self:
86 if result.shouldStop:
87 break
88
89 if _isnotsuite(test):
90 self._tearDownPreviousClass(test, result)
91 self._handleModuleFixture(test, result)
92 self._handleClassSetUp(test, result)
93 result._previousTestClass = test.__class__
94
95 if (getattr(test.__class__, '_classSetupFailed', False) or
96 getattr(result, '_moduleSetUpFailed', False)):
97 continue
98
Michael Foordbbea35f2010-11-01 21:09:03 +000099 if not debug:
Benjamin Peterson847a4112010-03-14 15:04:17 +0000100 test(result)
Michael Foordb8748742010-06-10 16:16:08 +0000101 else:
102 test.debug()
Benjamin Peterson847a4112010-03-14 15:04:17 +0000103
Michael Foordbbea35f2010-11-01 21:09:03 +0000104 if topLevel:
105 self._tearDownPreviousClass(None, result)
106 self._handleModuleTearDown(result)
Michael Foordcca5be22010-12-19 04:07:28 +0000107 result._testRunEntered = False
Michael Foordbbea35f2010-11-01 21:09:03 +0000108 return result
109
110 def debug(self):
111 """Run the tests without collecting errors in a TestResult"""
112 debug = _DebugResult()
113 self.run(debug, True)
114
115 ################################
116
Benjamin Peterson847a4112010-03-14 15:04:17 +0000117 def _handleClassSetUp(self, test, result):
118 previousClass = getattr(result, '_previousTestClass', None)
119 currentClass = test.__class__
120 if currentClass == previousClass:
121 return
122 if result._moduleSetUpFailed:
123 return
124 if getattr(currentClass, "__unittest_skip__", False):
125 return
126
Michael Foord73162192010-05-08 17:10:05 +0000127 try:
128 currentClass._classSetupFailed = False
129 except TypeError:
130 # test may actually be a function
131 # so its class will be a builtin-type
132 pass
Benjamin Peterson847a4112010-03-14 15:04:17 +0000133
134 setUpClass = getattr(currentClass, 'setUpClass', None)
135 if setUpClass is not None:
136 try:
137 setUpClass()
Michael Foord520ed0a2010-06-05 21:12:23 +0000138 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000139 if isinstance(result, _DebugResult):
140 raise
Benjamin Peterson847a4112010-03-14 15:04:17 +0000141 currentClass._classSetupFailed = True
Michael Foord520ed0a2010-06-05 21:12:23 +0000142 className = util.strclass(currentClass)
143 errorName = 'setUpClass (%s)' % className
144 self._addClassOrModuleLevelException(result, e, errorName)
Benjamin Peterson847a4112010-03-14 15:04:17 +0000145
146 def _get_previous_module(self, result):
147 previousModule = None
148 previousClass = getattr(result, '_previousTestClass', None)
149 if previousClass is not None:
150 previousModule = previousClass.__module__
151 return previousModule
152
153
154 def _handleModuleFixture(self, test, result):
155 previousModule = self._get_previous_module(result)
156 currentModule = test.__class__.__module__
157 if currentModule == previousModule:
158 return
159
160 self._handleModuleTearDown(result)
161
162
163 result._moduleSetUpFailed = False
164 try:
165 module = sys.modules[currentModule]
166 except KeyError:
167 return
168 setUpModule = getattr(module, 'setUpModule', None)
169 if setUpModule is not None:
170 try:
171 setUpModule()
Michael Foord520ed0a2010-06-05 21:12:23 +0000172 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000173 if isinstance(result, _DebugResult):
174 raise
Benjamin Peterson847a4112010-03-14 15:04:17 +0000175 result._moduleSetUpFailed = True
Michael Foord520ed0a2010-06-05 21:12:23 +0000176 errorName = 'setUpModule (%s)' % currentModule
177 self._addClassOrModuleLevelException(result, e, errorName)
178
179 def _addClassOrModuleLevelException(self, result, exception, errorName):
180 error = _ErrorHolder(errorName)
181 addSkip = getattr(result, 'addSkip', None)
182 if addSkip is not None and isinstance(exception, case.SkipTest):
183 addSkip(error, str(exception))
184 else:
185 result.addError(error, sys.exc_info())
Benjamin Peterson847a4112010-03-14 15:04:17 +0000186
187 def _handleModuleTearDown(self, result):
188 previousModule = self._get_previous_module(result)
189 if previousModule is None:
190 return
191 if result._moduleSetUpFailed:
192 return
193
194 try:
195 module = sys.modules[previousModule]
196 except KeyError:
197 return
198
199 tearDownModule = getattr(module, 'tearDownModule', None)
200 if tearDownModule is not None:
201 try:
202 tearDownModule()
Michael Foord520ed0a2010-06-05 21:12:23 +0000203 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000204 if isinstance(result, _DebugResult):
205 raise
Michael Foord520ed0a2010-06-05 21:12:23 +0000206 errorName = 'tearDownModule (%s)' % previousModule
207 self._addClassOrModuleLevelException(result, e, errorName)
Benjamin Peterson847a4112010-03-14 15:04:17 +0000208
209 def _tearDownPreviousClass(self, test, result):
210 previousClass = getattr(result, '_previousTestClass', None)
211 currentClass = test.__class__
212 if currentClass == previousClass:
213 return
214 if getattr(previousClass, '_classSetupFailed', False):
215 return
216 if getattr(result, '_moduleSetUpFailed', False):
217 return
218 if getattr(previousClass, "__unittest_skip__", False):
219 return
220
221 tearDownClass = getattr(previousClass, 'tearDownClass', None)
222 if tearDownClass is not None:
223 try:
224 tearDownClass()
Michael Foord520ed0a2010-06-05 21:12:23 +0000225 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000226 if isinstance(result, _DebugResult):
227 raise
Michael Foord520ed0a2010-06-05 21:12:23 +0000228 className = util.strclass(previousClass)
229 errorName = 'tearDownClass (%s)' % className
230 self._addClassOrModuleLevelException(result, e, errorName)
Benjamin Peterson847a4112010-03-14 15:04:17 +0000231
Benjamin Peterson847a4112010-03-14 15:04:17 +0000232
233
234class _ErrorHolder(object):
235 """
236 Placeholder for a TestCase inside a result. As far as a TestResult
237 is concerned, this looks exactly like a unit test. Used to insert
238 arbitrary errors into a test suite run.
239 """
240 # Inspired by the ErrorHolder from Twisted:
241 # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
242
243 # attribute used by TestResult._exc_info_to_string
244 failureException = None
245
246 def __init__(self, description):
247 self.description = description
248
249 def id(self):
250 return self.description
251
252 def shortDescription(self):
253 return None
254
255 def __repr__(self):
256 return "<ErrorHolder description=%r>" % (self.description,)
257
258 def __str__(self):
259 return self.id()
260
261 def run(self, result):
262 # could call result.addError(...) - but this test-like object
263 # shouldn't be run anyway
264 pass
265
266 def __call__(self, result):
267 return self.run(result)
268
269 def countTestCases(self):
270 return 0
271
272def _isnotsuite(test):
273 "A crude way to tell apart testcases and suites with duck-typing"
274 try:
275 iter(test)
276 except TypeError:
277 return True
278 return False
Michael Foordb8748742010-06-10 16:16:08 +0000279
280
281class _DebugResult(object):
282 "Used by the TestSuite to hold previous class when running in debug."
283 _previousTestClass = None
284 _moduleSetUpFailed = False
285 shouldStop = False