1 """TestSuite"""
2
3 import sys
4
5 from . import case
6 from . import util
7
8 __unittest = True
9
10
11 def _call_if_exists(parent, attr):
12 func = getattr(parent, attr, lambda: None)
13 func()
14
15
16 class ESC[4;38;5;81mBaseTestSuite(ESC[4;38;5;149mobject):
17 """A simple test suite that doesn't provide class or module shared fixtures.
18 """
19 _cleanup = True
20
21 def __init__(self, tests=()):
22 self._tests = []
23 self._removed_tests = 0
24 self.addTests(tests)
25
26 def __repr__(self):
27 return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
28
29 def __eq__(self, other):
30 if not isinstance(other, self.__class__):
31 return NotImplemented
32 return list(self) == list(other)
33
34 def __iter__(self):
35 return iter(self._tests)
36
37 def countTestCases(self):
38 cases = self._removed_tests
39 for test in self:
40 if test:
41 cases += test.countTestCases()
42 return cases
43
44 def addTest(self, test):
45 # sanity checks
46 if not callable(test):
47 raise TypeError("{} is not callable".format(repr(test)))
48 if isinstance(test, type) and issubclass(test,
49 (case.TestCase, TestSuite)):
50 raise TypeError("TestCases and TestSuites must be instantiated "
51 "before passing them to addTest()")
52 self._tests.append(test)
53
54 def addTests(self, tests):
55 if isinstance(tests, str):
56 raise TypeError("tests must be an iterable of tests, not a string")
57 for test in tests:
58 self.addTest(test)
59
60 def run(self, result):
61 for index, test in enumerate(self):
62 if result.shouldStop:
63 break
64 test(result)
65 if self._cleanup:
66 self._removeTestAtIndex(index)
67 return result
68
69 def _removeTestAtIndex(self, index):
70 """Stop holding a reference to the TestCase at index."""
71 try:
72 test = self._tests[index]
73 except TypeError:
74 # support for suite implementations that have overridden self._tests
75 pass
76 else:
77 # Some unittest tests add non TestCase/TestSuite objects to
78 # the suite.
79 if hasattr(test, 'countTestCases'):
80 self._removed_tests += test.countTestCases()
81 self._tests[index] = None
82
83 def __call__(self, *args, **kwds):
84 return self.run(*args, **kwds)
85
86 def debug(self):
87 """Run the tests without collecting errors in a TestResult"""
88 for test in self:
89 test.debug()
90
91
92 class ESC[4;38;5;81mTestSuite(ESC[4;38;5;149mBaseTestSuite):
93 """A test suite is a composite test consisting of a number of TestCases.
94
95 For use, create an instance of TestSuite, then add test case instances.
96 When all tests have been added, the suite can be passed to a test
97 runner, such as TextTestRunner. It will run the individual test cases
98 in the order in which they were added, aggregating the results. When
99 subclassing, do not forget to call the base class constructor.
100 """
101
102 def run(self, result, debug=False):
103 topLevel = False
104 if getattr(result, '_testRunEntered', False) is False:
105 result._testRunEntered = topLevel = True
106
107 for index, test in enumerate(self):
108 if result.shouldStop:
109 break
110
111 if _isnotsuite(test):
112 self._tearDownPreviousClass(test, result)
113 self._handleModuleFixture(test, result)
114 self._handleClassSetUp(test, result)
115 result._previousTestClass = test.__class__
116
117 if (getattr(test.__class__, '_classSetupFailed', False) or
118 getattr(result, '_moduleSetUpFailed', False)):
119 continue
120
121 if not debug:
122 test(result)
123 else:
124 test.debug()
125
126 if self._cleanup:
127 self._removeTestAtIndex(index)
128
129 if topLevel:
130 self._tearDownPreviousClass(None, result)
131 self._handleModuleTearDown(result)
132 result._testRunEntered = False
133 return result
134
135 def debug(self):
136 """Run the tests without collecting errors in a TestResult"""
137 debug = _DebugResult()
138 self.run(debug, True)
139
140 ################################
141
142 def _handleClassSetUp(self, test, result):
143 previousClass = getattr(result, '_previousTestClass', None)
144 currentClass = test.__class__
145 if currentClass == previousClass:
146 return
147 if result._moduleSetUpFailed:
148 return
149 if getattr(currentClass, "__unittest_skip__", False):
150 return
151
152 failed = False
153 try:
154 currentClass._classSetupFailed = False
155 except TypeError:
156 # test may actually be a function
157 # so its class will be a builtin-type
158 pass
159
160 setUpClass = getattr(currentClass, 'setUpClass', None)
161 doClassCleanups = getattr(currentClass, 'doClassCleanups', None)
162 if setUpClass is not None:
163 _call_if_exists(result, '_setupStdout')
164 try:
165 try:
166 setUpClass()
167 except Exception as e:
168 if isinstance(result, _DebugResult):
169 raise
170 failed = True
171 try:
172 currentClass._classSetupFailed = True
173 except TypeError:
174 pass
175 className = util.strclass(currentClass)
176 self._createClassOrModuleLevelException(result, e,
177 'setUpClass',
178 className)
179 if failed and doClassCleanups is not None:
180 doClassCleanups()
181 for exc_info in currentClass.tearDown_exceptions:
182 self._createClassOrModuleLevelException(
183 result, exc_info[1], 'setUpClass', className,
184 info=exc_info)
185 finally:
186 _call_if_exists(result, '_restoreStdout')
187
188 def _get_previous_module(self, result):
189 previousModule = None
190 previousClass = getattr(result, '_previousTestClass', None)
191 if previousClass is not None:
192 previousModule = previousClass.__module__
193 return previousModule
194
195
196 def _handleModuleFixture(self, test, result):
197 previousModule = self._get_previous_module(result)
198 currentModule = test.__class__.__module__
199 if currentModule == previousModule:
200 return
201
202 self._handleModuleTearDown(result)
203
204
205 result._moduleSetUpFailed = False
206 try:
207 module = sys.modules[currentModule]
208 except KeyError:
209 return
210 setUpModule = getattr(module, 'setUpModule', None)
211 if setUpModule is not None:
212 _call_if_exists(result, '_setupStdout')
213 try:
214 try:
215 setUpModule()
216 except Exception as e:
217 if isinstance(result, _DebugResult):
218 raise
219 result._moduleSetUpFailed = True
220 self._createClassOrModuleLevelException(result, e,
221 'setUpModule',
222 currentModule)
223 if result._moduleSetUpFailed:
224 try:
225 case.doModuleCleanups()
226 except Exception as e:
227 self._createClassOrModuleLevelException(result, e,
228 'setUpModule',
229 currentModule)
230 finally:
231 _call_if_exists(result, '_restoreStdout')
232
233 def _createClassOrModuleLevelException(self, result, exc, method_name,
234 parent, info=None):
235 errorName = f'{method_name} ({parent})'
236 self._addClassOrModuleLevelException(result, exc, errorName, info)
237
238 def _addClassOrModuleLevelException(self, result, exception, errorName,
239 info=None):
240 error = _ErrorHolder(errorName)
241 addSkip = getattr(result, 'addSkip', None)
242 if addSkip is not None and isinstance(exception, case.SkipTest):
243 addSkip(error, str(exception))
244 else:
245 if not info:
246 result.addError(error, sys.exc_info())
247 else:
248 result.addError(error, info)
249
250 def _handleModuleTearDown(self, result):
251 previousModule = self._get_previous_module(result)
252 if previousModule is None:
253 return
254 if result._moduleSetUpFailed:
255 return
256
257 try:
258 module = sys.modules[previousModule]
259 except KeyError:
260 return
261
262 _call_if_exists(result, '_setupStdout')
263 try:
264 tearDownModule = getattr(module, 'tearDownModule', None)
265 if tearDownModule is not None:
266 try:
267 tearDownModule()
268 except Exception as e:
269 if isinstance(result, _DebugResult):
270 raise
271 self._createClassOrModuleLevelException(result, e,
272 'tearDownModule',
273 previousModule)
274 try:
275 case.doModuleCleanups()
276 except Exception as e:
277 if isinstance(result, _DebugResult):
278 raise
279 self._createClassOrModuleLevelException(result, e,
280 'tearDownModule',
281 previousModule)
282 finally:
283 _call_if_exists(result, '_restoreStdout')
284
285 def _tearDownPreviousClass(self, test, result):
286 previousClass = getattr(result, '_previousTestClass', None)
287 currentClass = test.__class__
288 if currentClass == previousClass or previousClass is None:
289 return
290 if getattr(previousClass, '_classSetupFailed', False):
291 return
292 if getattr(result, '_moduleSetUpFailed', False):
293 return
294 if getattr(previousClass, "__unittest_skip__", False):
295 return
296
297 tearDownClass = getattr(previousClass, 'tearDownClass', None)
298 doClassCleanups = getattr(previousClass, 'doClassCleanups', None)
299 if tearDownClass is None and doClassCleanups is None:
300 return
301
302 _call_if_exists(result, '_setupStdout')
303 try:
304 if tearDownClass is not None:
305 try:
306 tearDownClass()
307 except Exception as e:
308 if isinstance(result, _DebugResult):
309 raise
310 className = util.strclass(previousClass)
311 self._createClassOrModuleLevelException(result, e,
312 'tearDownClass',
313 className)
314 if doClassCleanups is not None:
315 doClassCleanups()
316 for exc_info in previousClass.tearDown_exceptions:
317 if isinstance(result, _DebugResult):
318 raise exc_info[1]
319 className = util.strclass(previousClass)
320 self._createClassOrModuleLevelException(result, exc_info[1],
321 'tearDownClass',
322 className,
323 info=exc_info)
324 finally:
325 _call_if_exists(result, '_restoreStdout')
326
327
328 class ESC[4;38;5;81m_ErrorHolder(ESC[4;38;5;149mobject):
329 """
330 Placeholder for a TestCase inside a result. As far as a TestResult
331 is concerned, this looks exactly like a unit test. Used to insert
332 arbitrary errors into a test suite run.
333 """
334 # Inspired by the ErrorHolder from Twisted:
335 # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
336
337 # attribute used by TestResult._exc_info_to_string
338 failureException = None
339
340 def __init__(self, description):
341 self.description = description
342
343 def id(self):
344 return self.description
345
346 def shortDescription(self):
347 return None
348
349 def __repr__(self):
350 return "<ErrorHolder description=%r>" % (self.description,)
351
352 def __str__(self):
353 return self.id()
354
355 def run(self, result):
356 # could call result.addError(...) - but this test-like object
357 # shouldn't be run anyway
358 pass
359
360 def __call__(self, result):
361 return self.run(result)
362
363 def countTestCases(self):
364 return 0
365
366 def _isnotsuite(test):
367 "A crude way to tell apart testcases and suites with duck-typing"
368 try:
369 iter(test)
370 except TypeError:
371 return True
372 return False
373
374
375 class ESC[4;38;5;81m_DebugResult(ESC[4;38;5;149mobject):
376 "Used by the TestSuite to hold previous class when running in debug."
377 _previousTestClass = None
378 _moduleSetUpFailed = False
379 shouldStop = False