unittest.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # -*- coding: utf-8 -*-
  2. """ discovery and running of std-library "unittest" style tests. """
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. import sys
  7. import traceback
  8. import _pytest._code
  9. import pytest
  10. from _pytest.compat import getimfunc
  11. from _pytest.config import hookimpl
  12. from _pytest.outcomes import fail
  13. from _pytest.outcomes import skip
  14. from _pytest.outcomes import xfail
  15. from _pytest.python import Class
  16. from _pytest.python import Function
  17. def pytest_pycollect_makeitem(collector, name, obj):
  18. # has unittest been imported and is obj a subclass of its TestCase?
  19. try:
  20. if not issubclass(obj, sys.modules["unittest"].TestCase):
  21. return
  22. except Exception:
  23. return
  24. # yes, so let's collect it
  25. return UnitTestCase(name, parent=collector)
  26. class UnitTestCase(Class):
  27. # marker for fixturemanger.getfixtureinfo()
  28. # to declare that our children do not support funcargs
  29. nofuncargs = True
  30. def collect(self):
  31. from unittest import TestLoader
  32. cls = self.obj
  33. if not getattr(cls, "__test__", True):
  34. return
  35. skipped = getattr(cls, "__unittest_skip__", False)
  36. if not skipped:
  37. self._inject_setup_teardown_fixtures(cls)
  38. self._inject_setup_class_fixture()
  39. self.session._fixturemanager.parsefactories(self, unittest=True)
  40. loader = TestLoader()
  41. foundsomething = False
  42. for name in loader.getTestCaseNames(self.obj):
  43. x = getattr(self.obj, name)
  44. if not getattr(x, "__test__", True):
  45. continue
  46. funcobj = getimfunc(x)
  47. yield TestCaseFunction(name, parent=self, callobj=funcobj)
  48. foundsomething = True
  49. if not foundsomething:
  50. runtest = getattr(self.obj, "runTest", None)
  51. if runtest is not None:
  52. ut = sys.modules.get("twisted.trial.unittest", None)
  53. if ut is None or runtest != ut.TestCase.runTest:
  54. yield TestCaseFunction("runTest", parent=self)
  55. def _inject_setup_teardown_fixtures(self, cls):
  56. """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding
  57. teardown functions (#517)"""
  58. class_fixture = _make_xunit_fixture(
  59. cls, "setUpClass", "tearDownClass", scope="class", pass_self=False
  60. )
  61. if class_fixture:
  62. cls.__pytest_class_setup = class_fixture
  63. method_fixture = _make_xunit_fixture(
  64. cls, "setup_method", "teardown_method", scope="function", pass_self=True
  65. )
  66. if method_fixture:
  67. cls.__pytest_method_setup = method_fixture
  68. def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self):
  69. setup = getattr(obj, setup_name, None)
  70. teardown = getattr(obj, teardown_name, None)
  71. if setup is None and teardown is None:
  72. return None
  73. @pytest.fixture(scope=scope, autouse=True)
  74. def fixture(self, request):
  75. if getattr(self, "__unittest_skip__", None):
  76. reason = self.__unittest_skip_why__
  77. pytest.skip(reason)
  78. if setup is not None:
  79. if pass_self:
  80. setup(self, request.function)
  81. else:
  82. setup()
  83. yield
  84. if teardown is not None:
  85. if pass_self:
  86. teardown(self, request.function)
  87. else:
  88. teardown()
  89. return fixture
  90. class TestCaseFunction(Function):
  91. nofuncargs = True
  92. _excinfo = None
  93. _testcase = None
  94. def setup(self):
  95. self._testcase = self.parent.obj(self.name)
  96. self._fix_unittest_skip_decorator()
  97. self._obj = getattr(self._testcase, self.name)
  98. if hasattr(self, "_request"):
  99. self._request._fillfixtures()
  100. def _fix_unittest_skip_decorator(self):
  101. """
  102. The @unittest.skip decorator calls functools.wraps(self._testcase)
  103. The call to functools.wraps() fails unless self._testcase
  104. has a __name__ attribute. This is usually automatically supplied
  105. if the test is a function or method, but we need to add manually
  106. here.
  107. See issue #1169
  108. """
  109. if sys.version_info[0] == 2:
  110. setattr(self._testcase, "__name__", self.name)
  111. def teardown(self):
  112. self._testcase = None
  113. self._obj = None
  114. def startTest(self, testcase):
  115. pass
  116. def _addexcinfo(self, rawexcinfo):
  117. # unwrap potential exception info (see twisted trial support below)
  118. rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
  119. try:
  120. excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
  121. # invoke the attributes to trigger storing the traceback
  122. # trial causes some issue there
  123. excinfo.value
  124. excinfo.traceback
  125. except TypeError:
  126. try:
  127. try:
  128. values = traceback.format_exception(*rawexcinfo)
  129. values.insert(
  130. 0,
  131. "NOTE: Incompatible Exception Representation, "
  132. "displaying natively:\n\n",
  133. )
  134. fail("".join(values), pytrace=False)
  135. except (fail.Exception, KeyboardInterrupt):
  136. raise
  137. except: # noqa
  138. fail(
  139. "ERROR: Unknown Incompatible Exception "
  140. "representation:\n%r" % (rawexcinfo,),
  141. pytrace=False,
  142. )
  143. except KeyboardInterrupt:
  144. raise
  145. except fail.Exception:
  146. excinfo = _pytest._code.ExceptionInfo.from_current()
  147. self.__dict__.setdefault("_excinfo", []).append(excinfo)
  148. def addError(self, testcase, rawexcinfo):
  149. self._addexcinfo(rawexcinfo)
  150. def addFailure(self, testcase, rawexcinfo):
  151. self._addexcinfo(rawexcinfo)
  152. def addSkip(self, testcase, reason):
  153. try:
  154. skip(reason)
  155. except skip.Exception:
  156. self._skipped_by_mark = True
  157. self._addexcinfo(sys.exc_info())
  158. def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
  159. try:
  160. xfail(str(reason))
  161. except xfail.Exception:
  162. self._addexcinfo(sys.exc_info())
  163. def addUnexpectedSuccess(self, testcase, reason=""):
  164. self._unexpectedsuccess = reason
  165. def addSuccess(self, testcase):
  166. pass
  167. def stopTest(self, testcase):
  168. pass
  169. def _handle_skip(self):
  170. # implements the skipping machinery (see #2137)
  171. # analog to pythons Lib/unittest/case.py:run
  172. testMethod = getattr(self._testcase, self._testcase._testMethodName)
  173. if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr(
  174. testMethod, "__unittest_skip__", False
  175. ):
  176. # If the class or method was skipped.
  177. skip_why = getattr(
  178. self._testcase.__class__, "__unittest_skip_why__", ""
  179. ) or getattr(testMethod, "__unittest_skip_why__", "")
  180. try: # PY3, unittest2 on PY2
  181. self._testcase._addSkip(self, self._testcase, skip_why)
  182. except TypeError: # PY2
  183. if sys.version_info[0] != 2:
  184. raise
  185. self._testcase._addSkip(self, skip_why)
  186. return True
  187. return False
  188. def runtest(self):
  189. if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
  190. self._testcase(result=self)
  191. else:
  192. # disables tearDown and cleanups for post mortem debugging (see #1890)
  193. if self._handle_skip():
  194. return
  195. self._testcase.debug()
  196. def _prunetraceback(self, excinfo):
  197. Function._prunetraceback(self, excinfo)
  198. traceback = excinfo.traceback.filter(
  199. lambda x: not x.frame.f_globals.get("__unittest")
  200. )
  201. if traceback:
  202. excinfo.traceback = traceback
  203. @hookimpl(tryfirst=True)
  204. def pytest_runtest_makereport(item, call):
  205. if isinstance(item, TestCaseFunction):
  206. if item._excinfo:
  207. call.excinfo = item._excinfo.pop(0)
  208. try:
  209. del call.result
  210. except AttributeError:
  211. pass
  212. # twisted trial support
  213. @hookimpl(hookwrapper=True)
  214. def pytest_runtest_protocol(item):
  215. if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
  216. ut = sys.modules["twisted.python.failure"]
  217. Failure__init__ = ut.Failure.__init__
  218. check_testcase_implements_trial_reporter()
  219. def excstore(
  220. self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
  221. ):
  222. if exc_value is None:
  223. self._rawexcinfo = sys.exc_info()
  224. else:
  225. if exc_type is None:
  226. exc_type = type(exc_value)
  227. self._rawexcinfo = (exc_type, exc_value, exc_tb)
  228. try:
  229. Failure__init__(
  230. self, exc_value, exc_type, exc_tb, captureVars=captureVars
  231. )
  232. except TypeError:
  233. Failure__init__(self, exc_value, exc_type, exc_tb)
  234. ut.Failure.__init__ = excstore
  235. yield
  236. ut.Failure.__init__ = Failure__init__
  237. else:
  238. yield
  239. def check_testcase_implements_trial_reporter(done=[]):
  240. if done:
  241. return
  242. from zope.interface import classImplements
  243. from twisted.trial.itrial import IReporter
  244. classImplements(TestCaseFunction, IReporter)
  245. done.append(1)