suite.py 13 KB

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