blob: 633af5cb08552b3a2c4ed4326658d653b04f9718 [file] [log] [blame]
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +00001"""TestSuite"""
2
Michael Foord5ffa3252010-03-07 22:04:55 +00003import sys
4
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +00005from . import case
Michael Foorde91ea562009-09-13 19:07:03 +00006from . import util
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +00007
Michael Foordb1aa30f2010-03-22 00:06:30 +00008__unittest = True
9
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +000010
Michael Foord8faa2072011-03-17 12:48:56 -040011def _call_if_exists(parent, attr):
12 func = getattr(parent, attr, lambda: None)
13 func()
14
15
Michael Foord5ffa3252010-03-07 22:04:55 +000016class BaseTestSuite(object):
17 """A simple test suite that doesn't provide class or module shared fixtures.
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +000018 """
19 def __init__(self, tests=()):
20 self._tests = []
21 self.addTests(tests)
22
23 def __repr__(self):
Michael Foorde91ea562009-09-13 19:07:03 +000024 return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +000025
26 def __eq__(self, other):
27 if not isinstance(other, self.__class__):
28 return NotImplemented
29 return list(self) == list(other)
30
31 def __ne__(self, other):
32 return not self == other
33
34 # Can't guarantee hash invariant, so flag as unhashable
35 __hash__ = None
36
37 def __iter__(self):
38 return iter(self._tests)
39
40 def countTestCases(self):
41 cases = 0
42 for test in self:
43 cases += test.countTestCases()
44 return cases
45
46 def addTest(self, test):
47 # sanity checks
48 if not hasattr(test, '__call__'):
R. David Murrayb5d74002010-01-28 21:16:33 +000049 raise TypeError("{} is not callable".format(repr(test)))
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +000050 if isinstance(test, type) and issubclass(test,
51 (case.TestCase, TestSuite)):
52 raise TypeError("TestCases and TestSuites must be instantiated "
53 "before passing them to addTest()")
54 self._tests.append(test)
55
56 def addTests(self, tests):
57 if isinstance(tests, basestring):
58 raise TypeError("tests must be an iterable of tests, not a string")
59 for test in tests:
60 self.addTest(test)
61
62 def run(self, result):
63 for test in self:
64 if result.shouldStop:
65 break
66 test(result)
67 return result
68
69 def __call__(self, *args, **kwds):
70 return self.run(*args, **kwds)
71
72 def debug(self):
73 """Run the tests without collecting errors in a TestResult"""
74 for test in self:
75 test.debug()
Michael Foord5ffa3252010-03-07 22:04:55 +000076
77
78class TestSuite(BaseTestSuite):
79 """A test suite is a composite test consisting of a number of TestCases.
80
81 For use, create an instance of TestSuite, then add test case instances.
82 When all tests have been added, the suite can be passed to a test
83 runner, such as TextTestRunner. It will run the individual test cases
84 in the order in which they were added, aggregating the results. When
85 subclassing, do not forget to call the base class constructor.
86 """
87
Michael Foorde5dc24e2010-11-01 22:11:53 +000088 def run(self, result, debug=False):
89 topLevel = False
90 if getattr(result, '_testRunEntered', False) is False:
91 result._testRunEntered = topLevel = True
Michael Foord5ffa3252010-03-07 22:04:55 +000092
Michael Foord5ffa3252010-03-07 22:04:55 +000093 for test in self:
94 if result.shouldStop:
95 break
96
97 if _isnotsuite(test):
98 self._tearDownPreviousClass(test, result)
99 self._handleModuleFixture(test, result)
100 self._handleClassSetUp(test, result)
101 result._previousTestClass = test.__class__
102
103 if (getattr(test.__class__, '_classSetupFailed', False) or
104 getattr(result, '_moduleSetUpFailed', False)):
105 continue
106
Michael Foorde5dc24e2010-11-01 22:11:53 +0000107 if not debug:
Michael Foord5ffa3252010-03-07 22:04:55 +0000108 test(result)
Michael Foord0fedb282010-06-08 22:44:52 +0000109 else:
110 test.debug()
Michael Foord5ffa3252010-03-07 22:04:55 +0000111
Michael Foorde5dc24e2010-11-01 22:11:53 +0000112 if topLevel:
113 self._tearDownPreviousClass(None, result)
114 self._handleModuleTearDown(result)
Michael Foord5657ff82010-12-19 14:12:23 +0000115 result._testRunEntered = False
Michael Foorde5dc24e2010-11-01 22:11:53 +0000116 return result
117
118 def debug(self):
119 """Run the tests without collecting errors in a TestResult"""
120 debug = _DebugResult()
121 self.run(debug, True)
122
123 ################################
124
Michael Foord5ffa3252010-03-07 22:04:55 +0000125 def _handleClassSetUp(self, test, result):
126 previousClass = getattr(result, '_previousTestClass', None)
127 currentClass = test.__class__
128 if currentClass == previousClass:
129 return
130 if result._moduleSetUpFailed:
131 return
132 if getattr(currentClass, "__unittest_skip__", False):
133 return
134
Michael Foord9c164af2010-05-08 17:06:25 +0000135 try:
136 currentClass._classSetupFailed = False
137 except TypeError:
138 # test may actually be a function
139 # so its class will be a builtin-type
140 pass
Michael Foord5ffa3252010-03-07 22:04:55 +0000141
142 setUpClass = getattr(currentClass, 'setUpClass', None)
143 if setUpClass is not None:
Michael Foord8faa2072011-03-17 12:48:56 -0400144 _call_if_exists(result, '_setupStdout')
Michael Foord5ffa3252010-03-07 22:04:55 +0000145 try:
146 setUpClass()
Michael Foord20e287c2010-06-05 19:38:42 +0000147 except Exception as e:
Michael Foord0fedb282010-06-08 22:44:52 +0000148 if isinstance(result, _DebugResult):
149 raise
Michael Foord5ffa3252010-03-07 22:04:55 +0000150 currentClass._classSetupFailed = True
Michael Foord20e287c2010-06-05 19:38:42 +0000151 className = util.strclass(currentClass)
Michael Foorddb919f02010-06-05 20:59:00 +0000152 errorName = 'setUpClass (%s)' % className
Michael Foord20e287c2010-06-05 19:38:42 +0000153 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord8faa2072011-03-17 12:48:56 -0400154 finally:
155 _call_if_exists(result, '_restoreStdout')
Michael Foord5ffa3252010-03-07 22:04:55 +0000156
157 def _get_previous_module(self, result):
158 previousModule = None
159 previousClass = getattr(result, '_previousTestClass', None)
160 if previousClass is not None:
161 previousModule = previousClass.__module__
162 return previousModule
163
164
165 def _handleModuleFixture(self, test, result):
166 previousModule = self._get_previous_module(result)
167 currentModule = test.__class__.__module__
168 if currentModule == previousModule:
169 return
170
171 self._handleModuleTearDown(result)
172
Michael Foord5ffa3252010-03-07 22:04:55 +0000173 result._moduleSetUpFailed = False
174 try:
175 module = sys.modules[currentModule]
176 except KeyError:
177 return
178 setUpModule = getattr(module, 'setUpModule', None)
179 if setUpModule is not None:
Michael Foord8faa2072011-03-17 12:48:56 -0400180 _call_if_exists(result, '_setupStdout')
Michael Foord5ffa3252010-03-07 22:04:55 +0000181 try:
182 setUpModule()
Michael Foord20e287c2010-06-05 19:38:42 +0000183 except Exception, e:
Michael Foord0fedb282010-06-08 22:44:52 +0000184 if isinstance(result, _DebugResult):
185 raise
Michael Foord5ffa3252010-03-07 22:04:55 +0000186 result._moduleSetUpFailed = True
Michael Foord20e287c2010-06-05 19:38:42 +0000187 errorName = 'setUpModule (%s)' % currentModule
188 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord8faa2072011-03-17 12:48:56 -0400189 finally:
190 _call_if_exists(result, '_restoreStdout')
Michael Foord20e287c2010-06-05 19:38:42 +0000191
192 def _addClassOrModuleLevelException(self, result, exception, errorName):
193 error = _ErrorHolder(errorName)
194 addSkip = getattr(result, 'addSkip', None)
195 if addSkip is not None and isinstance(exception, case.SkipTest):
196 addSkip(error, str(exception))
197 else:
198 result.addError(error, sys.exc_info())
Michael Foord5ffa3252010-03-07 22:04:55 +0000199
200 def _handleModuleTearDown(self, result):
201 previousModule = self._get_previous_module(result)
202 if previousModule is None:
203 return
204 if result._moduleSetUpFailed:
205 return
206
207 try:
208 module = sys.modules[previousModule]
209 except KeyError:
210 return
211
212 tearDownModule = getattr(module, 'tearDownModule', None)
213 if tearDownModule is not None:
Michael Foord8faa2072011-03-17 12:48:56 -0400214 _call_if_exists(result, '_setupStdout')
Michael Foord5ffa3252010-03-07 22:04:55 +0000215 try:
216 tearDownModule()
Michael Foord20e287c2010-06-05 19:38:42 +0000217 except Exception as e:
Michael Foord0fedb282010-06-08 22:44:52 +0000218 if isinstance(result, _DebugResult):
219 raise
Michael Foord20e287c2010-06-05 19:38:42 +0000220 errorName = 'tearDownModule (%s)' % previousModule
221 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord8faa2072011-03-17 12:48:56 -0400222 finally:
223 _call_if_exists(result, '_restoreStdout')
Michael Foord5ffa3252010-03-07 22:04:55 +0000224
225 def _tearDownPreviousClass(self, test, result):
226 previousClass = getattr(result, '_previousTestClass', None)
227 currentClass = test.__class__
228 if currentClass == previousClass:
229 return
230 if getattr(previousClass, '_classSetupFailed', False):
231 return
232 if getattr(result, '_moduleSetUpFailed', False):
233 return
234 if getattr(previousClass, "__unittest_skip__", False):
235 return
236
237 tearDownClass = getattr(previousClass, 'tearDownClass', None)
238 if tearDownClass is not None:
Michael Foord8faa2072011-03-17 12:48:56 -0400239 _call_if_exists(result, '_setupStdout')
Michael Foord5ffa3252010-03-07 22:04:55 +0000240 try:
241 tearDownClass()
Michael Foord20e287c2010-06-05 19:38:42 +0000242 except Exception, e:
Michael Foord0fedb282010-06-08 22:44:52 +0000243 if isinstance(result, _DebugResult):
244 raise
Michael Foord20e287c2010-06-05 19:38:42 +0000245 className = util.strclass(previousClass)
Michael Foorddb919f02010-06-05 20:59:00 +0000246 errorName = 'tearDownClass (%s)' % className
Michael Foord20e287c2010-06-05 19:38:42 +0000247 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord8faa2072011-03-17 12:48:56 -0400248 finally:
249 _call_if_exists(result, '_restoreStdout')
Michael Foord5ffa3252010-03-07 22:04:55 +0000250
251
252class _ErrorHolder(object):
253 """
254 Placeholder for a TestCase inside a result. As far as a TestResult
255 is concerned, this looks exactly like a unit test. Used to insert
256 arbitrary errors into a test suite run.
257 """
258 # Inspired by the ErrorHolder from Twisted:
259 # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
260
261 # attribute used by TestResult._exc_info_to_string
262 failureException = None
263
264 def __init__(self, description):
265 self.description = description
266
267 def id(self):
268 return self.description
269
270 def shortDescription(self):
271 return None
272
273 def __repr__(self):
274 return "<ErrorHolder description=%r>" % (self.description,)
275
276 def __str__(self):
277 return self.id()
278
279 def run(self, result):
280 # could call result.addError(...) - but this test-like object
281 # shouldn't be run anyway
282 pass
283
284 def __call__(self, result):
285 return self.run(result)
286
287 def countTestCases(self):
288 return 0
289
290def _isnotsuite(test):
291 "A crude way to tell apart testcases and suites with duck-typing"
292 try:
293 iter(test)
294 except TypeError:
295 return True
296 return False
Michael Foord0fedb282010-06-08 22:44:52 +0000297
298
299class _DebugResult(object):
300 "Used by the TestSuite to hold previous class when running in debug."
301 _previousTestClass = None
302 _moduleSetUpFailed = False
303 shouldStop = False