suite.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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. def __init__(self, tests=()):
  13. self._tests = []
  14. self.addTests(tests)
  15. def __repr__(self):
  16. return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
  17. def __eq__(self, other):
  18. if not isinstance(other, self.__class__):
  19. return NotImplemented
  20. return list(self) == list(other)
  21. def __ne__(self, other):
  22. return not self == other
  23. # Can't guarantee hash invariant, so flag as unhashable
  24. __hash__ = None
  25. def __iter__(self):
  26. return iter(self._tests)
  27. def countTestCases(self):
  28. cases = 0
  29. for test in self:
  30. cases += test.countTestCases()
  31. return cases
  32. def addTest(self, test):
  33. # sanity checks
  34. if not hasattr(test, '__call__'):
  35. raise TypeError("{} is not callable".format(repr(test)))
  36. if isinstance(test, type) and issubclass(test,
  37. (case.TestCase, TestSuite)):
  38. raise TypeError("TestCases and TestSuites must be instantiated "
  39. "before passing them to addTest()")
  40. self._tests.append(test)
  41. def addTests(self, tests):
  42. if isinstance(tests, basestring):
  43. raise TypeError("tests must be an iterable of tests, not a string")
  44. for test in tests:
  45. self.addTest(test)
  46. def run(self, result):
  47. for test in self:
  48. if result.shouldStop:
  49. break
  50. test(result)
  51. return result
  52. def __call__(self, *args, **kwds):
  53. return self.run(*args, **kwds)
  54. def debug(self):
  55. """Run the tests without collecting errors in a TestResult"""
  56. for test in self:
  57. test.debug()
  58. class TestSuite(BaseTestSuite):
  59. """A test suite is a composite test consisting of a number of TestCases.
  60. For use, create an instance of TestSuite, then add test case instances.
  61. When all tests have been added, the suite can be passed to a test
  62. runner, such as TextTestRunner. It will run the individual test cases
  63. in the order in which they were added, aggregating the results. When
  64. subclassing, do not forget to call the base class constructor.
  65. """
  66. def run(self, result, debug=False):
  67. topLevel = False
  68. if getattr(result, '_testRunEntered', False) is False:
  69. result._testRunEntered = topLevel = True
  70. for test in self:
  71. if result.shouldStop:
  72. break
  73. if _isnotsuite(test):
  74. self._tearDownPreviousClass(test, result)
  75. self._handleModuleFixture(test, result)
  76. self._handleClassSetUp(test, result)
  77. result._previousTestClass = test.__class__
  78. if (getattr(test.__class__, '_classSetupFailed', False) or
  79. getattr(result, '_moduleSetUpFailed', False)):
  80. continue
  81. if not debug:
  82. test(result)
  83. else:
  84. test.debug()
  85. if topLevel:
  86. self._tearDownPreviousClass(None, result)
  87. self._handleModuleTearDown(result)
  88. result._testRunEntered = False
  89. return result
  90. def debug(self):
  91. """Run the tests without collecting errors in a TestResult"""
  92. debug = _DebugResult()
  93. self.run(debug, True)
  94. ################################
  95. def _handleClassSetUp(self, test, result):
  96. previousClass = getattr(result, '_previousTestClass', None)
  97. currentClass = test.__class__
  98. if currentClass == previousClass:
  99. return
  100. if result._moduleSetUpFailed:
  101. return
  102. if getattr(currentClass, "__unittest_skip__", False):
  103. return
  104. try:
  105. currentClass._classSetupFailed = False
  106. except TypeError:
  107. # test may actually be a function
  108. # so its class will be a builtin-type
  109. pass
  110. setUpClass = getattr(currentClass, 'setUpClass', None)
  111. if setUpClass is not None:
  112. _call_if_exists(result, '_setupStdout')
  113. try:
  114. setUpClass()
  115. except Exception as e:
  116. if isinstance(result, _DebugResult):
  117. raise
  118. currentClass._classSetupFailed = True
  119. className = util.strclass(currentClass)
  120. errorName = 'setUpClass (%s)' % className
  121. self._addClassOrModuleLevelException(result, e, errorName)
  122. finally:
  123. _call_if_exists(result, '_restoreStdout')
  124. def _get_previous_module(self, result):
  125. previousModule = None
  126. previousClass = getattr(result, '_previousTestClass', None)
  127. if previousClass is not None:
  128. previousModule = previousClass.__module__
  129. return previousModule
  130. def _handleModuleFixture(self, test, result):
  131. previousModule = self._get_previous_module(result)
  132. currentModule = test.__class__.__module__
  133. if currentModule == previousModule:
  134. return
  135. self._handleModuleTearDown(result)
  136. result._moduleSetUpFailed = False
  137. try:
  138. module = sys.modules[currentModule]
  139. except KeyError:
  140. return
  141. setUpModule = getattr(module, 'setUpModule', None)
  142. if setUpModule is not None:
  143. _call_if_exists(result, '_setupStdout')
  144. try:
  145. setUpModule()
  146. except Exception, e:
  147. if isinstance(result, _DebugResult):
  148. raise
  149. result._moduleSetUpFailed = True
  150. errorName = 'setUpModule (%s)' % currentModule
  151. self._addClassOrModuleLevelException(result, e, errorName)
  152. finally:
  153. _call_if_exists(result, '_restoreStdout')
  154. def _addClassOrModuleLevelException(self, result, exception, errorName):
  155. error = _ErrorHolder(errorName)
  156. addSkip = getattr(result, 'addSkip', None)
  157. if addSkip is not None and isinstance(exception, case.SkipTest):
  158. addSkip(error, str(exception))
  159. else:
  160. result.addError(error, sys.exc_info())
  161. def _handleModuleTearDown(self, result):
  162. previousModule = self._get_previous_module(result)
  163. if previousModule is None:
  164. return
  165. if result._moduleSetUpFailed:
  166. return
  167. try:
  168. module = sys.modules[previousModule]
  169. except KeyError:
  170. return
  171. tearDownModule = getattr(module, 'tearDownModule', None)
  172. if tearDownModule is not None:
  173. _call_if_exists(result, '_setupStdout')
  174. try:
  175. tearDownModule()
  176. except Exception as e:
  177. if isinstance(result, _DebugResult):
  178. raise
  179. errorName = 'tearDownModule (%s)' % previousModule
  180. self._addClassOrModuleLevelException(result, e, errorName)
  181. finally:
  182. _call_if_exists(result, '_restoreStdout')
  183. def _tearDownPreviousClass(self, test, result):
  184. previousClass = getattr(result, '_previousTestClass', None)
  185. currentClass = test.__class__
  186. if currentClass == previousClass:
  187. return
  188. if getattr(previousClass, '_classSetupFailed', False):
  189. return
  190. if getattr(result, '_moduleSetUpFailed', False):
  191. return
  192. if getattr(previousClass, "__unittest_skip__", False):
  193. return
  194. tearDownClass = getattr(previousClass, 'tearDownClass', None)
  195. if tearDownClass is not None:
  196. _call_if_exists(result, '_setupStdout')
  197. try:
  198. tearDownClass()
  199. except Exception, e:
  200. if isinstance(result, _DebugResult):
  201. raise
  202. className = util.strclass(previousClass)
  203. errorName = 'tearDownClass (%s)' % className
  204. self._addClassOrModuleLevelException(result, e, errorName)
  205. finally:
  206. _call_if_exists(result, '_restoreStdout')
  207. class _ErrorHolder(object):
  208. """
  209. Placeholder for a TestCase inside a result. As far as a TestResult
  210. is concerned, this looks exactly like a unit test. Used to insert
  211. arbitrary errors into a test suite run.
  212. """
  213. # Inspired by the ErrorHolder from Twisted:
  214. # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
  215. # attribute used by TestResult._exc_info_to_string
  216. failureException = None
  217. def __init__(self, description):
  218. self.description = description
  219. def id(self):
  220. return self.description
  221. def shortDescription(self):
  222. return None
  223. def __repr__(self):
  224. return "<ErrorHolder description=%r>" % (self.description,)
  225. def __str__(self):
  226. return self.id()
  227. def run(self, result):
  228. # could call result.addError(...) - but this test-like object
  229. # shouldn't be run anyway
  230. pass
  231. def __call__(self, result):
  232. return self.run(result)
  233. def countTestCases(self):
  234. return 0
  235. def _isnotsuite(test):
  236. "A crude way to tell apart testcases and suites with duck-typing"
  237. try:
  238. iter(test)
  239. except TypeError:
  240. return True
  241. return False
  242. class _DebugResult(object):
  243. "Used by the TestSuite to hold previous class when running in debug."
  244. _previousTestClass = None
  245. _moduleSetUpFailed = False
  246. shouldStop = False