1"""TestSuite""" 2 3import sys 4 5from . import case 6from . import util 7 8__unittest = True 9 10 11def _call_if_exists(parent, attr): 12 func = getattr(parent, attr, lambda: None) 13 func() 14 15 16class BaseTestSuite(object): 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 92class TestSuite(BaseTestSuite): 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 328class _ErrorHolder(object): 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 366def _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 375class _DebugResult(object): 376 "Used by the TestSuite to hold previous class when running in debug." 377 _previousTestClass = None 378 _moduleSetUpFailed = False 379 shouldStop = False 380