blob: cde5d385ed1f290f0c3cf040d12f3bd974898e18 [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
Michael Foord42ec7cb2011-03-17 13:44:18 -040011def _call_if_exists(parent, attr):
12 func = getattr(parent, attr, lambda: None)
13 func()
14
15
Benjamin Peterson847a4112010-03-14 15:04:17 +000016class BaseTestSuite(object):
17 """A simple test suite that doesn't provide class or module shared fixtures.
Benjamin Petersonbed7d042009-07-19 21:01:52 +000018 """
19 def __init__(self, tests=()):
20 self._tests = []
21 self.addTests(tests)
22
23 def __repr__(self):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +000024 return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
Benjamin Petersonbed7d042009-07-19 21:01:52 +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 def __iter__(self):
35 return iter(self._tests)
36
37 def countTestCases(self):
38 cases = 0
39 for test in self:
40 cases += test.countTestCases()
41 return cases
42
43 def addTest(self, test):
44 # sanity checks
Florent Xicluna5d1155c2011-10-28 14:45:05 +020045 if not callable(test):
R. David Murray67d1bbd2010-01-29 17:55:47 +000046 raise TypeError("{} is not callable".format(repr(test)))
Benjamin Petersonbed7d042009-07-19 21:01:52 +000047 if isinstance(test, type) and issubclass(test,
48 (case.TestCase, TestSuite)):
49 raise TypeError("TestCases and TestSuites must be instantiated "
50 "before passing them to addTest()")
51 self._tests.append(test)
52
53 def addTests(self, tests):
54 if isinstance(tests, str):
55 raise TypeError("tests must be an iterable of tests, not a string")
56 for test in tests:
57 self.addTest(test)
58
59 def run(self, result):
60 for test in self:
61 if result.shouldStop:
62 break
63 test(result)
64 return result
65
66 def __call__(self, *args, **kwds):
67 return self.run(*args, **kwds)
68
69 def debug(self):
70 """Run the tests without collecting errors in a TestResult"""
71 for test in self:
72 test.debug()
Benjamin Peterson847a4112010-03-14 15:04:17 +000073
74
75class TestSuite(BaseTestSuite):
76 """A test suite is a composite test consisting of a number of TestCases.
77
78 For use, create an instance of TestSuite, then add test case instances.
79 When all tests have been added, the suite can be passed to a test
80 runner, such as TextTestRunner. It will run the individual test cases
81 in the order in which they were added, aggregating the results. When
82 subclassing, do not forget to call the base class constructor.
83 """
84
Michael Foordbbea35f2010-11-01 21:09:03 +000085 def run(self, result, debug=False):
86 topLevel = False
87 if getattr(result, '_testRunEntered', False) is False:
88 result._testRunEntered = topLevel = True
Benjamin Peterson847a4112010-03-14 15:04:17 +000089
Benjamin Peterson847a4112010-03-14 15:04:17 +000090 for test in self:
91 if result.shouldStop:
92 break
93
94 if _isnotsuite(test):
95 self._tearDownPreviousClass(test, result)
96 self._handleModuleFixture(test, result)
97 self._handleClassSetUp(test, result)
98 result._previousTestClass = test.__class__
99
100 if (getattr(test.__class__, '_classSetupFailed', False) or
101 getattr(result, '_moduleSetUpFailed', False)):
102 continue
103
Michael Foordbbea35f2010-11-01 21:09:03 +0000104 if not debug:
Benjamin Peterson847a4112010-03-14 15:04:17 +0000105 test(result)
Michael Foordb8748742010-06-10 16:16:08 +0000106 else:
107 test.debug()
Benjamin Peterson847a4112010-03-14 15:04:17 +0000108
Michael Foordbbea35f2010-11-01 21:09:03 +0000109 if topLevel:
110 self._tearDownPreviousClass(None, result)
111 self._handleModuleTearDown(result)
Michael Foordcca5be22010-12-19 04:07:28 +0000112 result._testRunEntered = False
Michael Foordbbea35f2010-11-01 21:09:03 +0000113 return result
114
115 def debug(self):
116 """Run the tests without collecting errors in a TestResult"""
117 debug = _DebugResult()
118 self.run(debug, True)
119
120 ################################
121
Benjamin Peterson847a4112010-03-14 15:04:17 +0000122 def _handleClassSetUp(self, test, result):
123 previousClass = getattr(result, '_previousTestClass', None)
124 currentClass = test.__class__
125 if currentClass == previousClass:
126 return
127 if result._moduleSetUpFailed:
128 return
129 if getattr(currentClass, "__unittest_skip__", False):
130 return
131
Michael Foord73162192010-05-08 17:10:05 +0000132 try:
133 currentClass._classSetupFailed = False
134 except TypeError:
135 # test may actually be a function
136 # so its class will be a builtin-type
137 pass
Benjamin Peterson847a4112010-03-14 15:04:17 +0000138
139 setUpClass = getattr(currentClass, 'setUpClass', None)
140 if setUpClass is not None:
Michael Foord42ec7cb2011-03-17 13:44:18 -0400141 _call_if_exists(result, '_setupStdout')
Benjamin Peterson847a4112010-03-14 15:04:17 +0000142 try:
143 setUpClass()
Michael Foord520ed0a2010-06-05 21:12:23 +0000144 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000145 if isinstance(result, _DebugResult):
146 raise
Benjamin Peterson847a4112010-03-14 15:04:17 +0000147 currentClass._classSetupFailed = True
Michael Foord520ed0a2010-06-05 21:12:23 +0000148 className = util.strclass(currentClass)
149 errorName = 'setUpClass (%s)' % className
150 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord42ec7cb2011-03-17 13:44:18 -0400151 finally:
152 _call_if_exists(result, '_restoreStdout')
Benjamin Peterson847a4112010-03-14 15:04:17 +0000153
154 def _get_previous_module(self, result):
155 previousModule = None
156 previousClass = getattr(result, '_previousTestClass', None)
157 if previousClass is not None:
158 previousModule = previousClass.__module__
159 return previousModule
160
161
162 def _handleModuleFixture(self, test, result):
163 previousModule = self._get_previous_module(result)
164 currentModule = test.__class__.__module__
165 if currentModule == previousModule:
166 return
167
168 self._handleModuleTearDown(result)
169
170
171 result._moduleSetUpFailed = False
172 try:
173 module = sys.modules[currentModule]
174 except KeyError:
175 return
176 setUpModule = getattr(module, 'setUpModule', None)
177 if setUpModule is not None:
Michael Foord42ec7cb2011-03-17 13:44:18 -0400178 _call_if_exists(result, '_setupStdout')
Benjamin Peterson847a4112010-03-14 15:04:17 +0000179 try:
180 setUpModule()
Michael Foord520ed0a2010-06-05 21:12:23 +0000181 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000182 if isinstance(result, _DebugResult):
183 raise
Benjamin Peterson847a4112010-03-14 15:04:17 +0000184 result._moduleSetUpFailed = True
Michael Foord520ed0a2010-06-05 21:12:23 +0000185 errorName = 'setUpModule (%s)' % currentModule
186 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord42ec7cb2011-03-17 13:44:18 -0400187 finally:
188 _call_if_exists(result, '_restoreStdout')
Michael Foord520ed0a2010-06-05 21:12:23 +0000189
190 def _addClassOrModuleLevelException(self, result, exception, errorName):
191 error = _ErrorHolder(errorName)
192 addSkip = getattr(result, 'addSkip', None)
193 if addSkip is not None and isinstance(exception, case.SkipTest):
194 addSkip(error, str(exception))
195 else:
196 result.addError(error, sys.exc_info())
Benjamin Peterson847a4112010-03-14 15:04:17 +0000197
198 def _handleModuleTearDown(self, result):
199 previousModule = self._get_previous_module(result)
200 if previousModule is None:
201 return
202 if result._moduleSetUpFailed:
203 return
204
205 try:
206 module = sys.modules[previousModule]
207 except KeyError:
208 return
209
210 tearDownModule = getattr(module, 'tearDownModule', None)
211 if tearDownModule is not None:
Michael Foord42ec7cb2011-03-17 13:44:18 -0400212 _call_if_exists(result, '_setupStdout')
Benjamin Peterson847a4112010-03-14 15:04:17 +0000213 try:
214 tearDownModule()
Michael Foord520ed0a2010-06-05 21:12:23 +0000215 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000216 if isinstance(result, _DebugResult):
217 raise
Michael Foord520ed0a2010-06-05 21:12:23 +0000218 errorName = 'tearDownModule (%s)' % previousModule
219 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord42ec7cb2011-03-17 13:44:18 -0400220 finally:
221 _call_if_exists(result, '_restoreStdout')
Benjamin Peterson847a4112010-03-14 15:04:17 +0000222
223 def _tearDownPreviousClass(self, test, result):
224 previousClass = getattr(result, '_previousTestClass', None)
225 currentClass = test.__class__
226 if currentClass == previousClass:
227 return
228 if getattr(previousClass, '_classSetupFailed', False):
229 return
230 if getattr(result, '_moduleSetUpFailed', False):
231 return
232 if getattr(previousClass, "__unittest_skip__", False):
233 return
234
235 tearDownClass = getattr(previousClass, 'tearDownClass', None)
236 if tearDownClass is not None:
Michael Foord42ec7cb2011-03-17 13:44:18 -0400237 _call_if_exists(result, '_setupStdout')
Benjamin Peterson847a4112010-03-14 15:04:17 +0000238 try:
239 tearDownClass()
Michael Foord520ed0a2010-06-05 21:12:23 +0000240 except Exception as e:
Michael Foordb8748742010-06-10 16:16:08 +0000241 if isinstance(result, _DebugResult):
242 raise
Michael Foord520ed0a2010-06-05 21:12:23 +0000243 className = util.strclass(previousClass)
244 errorName = 'tearDownClass (%s)' % className
245 self._addClassOrModuleLevelException(result, e, errorName)
Michael Foord42ec7cb2011-03-17 13:44:18 -0400246 finally:
247 _call_if_exists(result, '_restoreStdout')
Benjamin Peterson847a4112010-03-14 15:04:17 +0000248
249
250class _ErrorHolder(object):
251 """
252 Placeholder for a TestCase inside a result. As far as a TestResult
253 is concerned, this looks exactly like a unit test. Used to insert
254 arbitrary errors into a test suite run.
255 """
256 # Inspired by the ErrorHolder from Twisted:
257 # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
258
259 # attribute used by TestResult._exc_info_to_string
260 failureException = None
261
262 def __init__(self, description):
263 self.description = description
264
265 def id(self):
266 return self.description
267
268 def shortDescription(self):
269 return None
270
271 def __repr__(self):
272 return "<ErrorHolder description=%r>" % (self.description,)
273
274 def __str__(self):
275 return self.id()
276
277 def run(self, result):
278 # could call result.addError(...) - but this test-like object
279 # shouldn't be run anyway
280 pass
281
282 def __call__(self, result):
283 return self.run(result)
284
285 def countTestCases(self):
286 return 0
287
288def _isnotsuite(test):
289 "A crude way to tell apart testcases and suites with duck-typing"
290 try:
291 iter(test)
292 except TypeError:
293 return True
294 return False
Michael Foordb8748742010-06-10 16:16:08 +0000295
296
297class _DebugResult(object):
298 "Used by the TestSuite to hold previous class when running in debug."
299 _previousTestClass = None
300 _moduleSetUpFailed = False
301 shouldStop = False