Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/_pytest/unittest.py : 33%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1""" discovery and running of std-library "unittest" style tests. """
2import sys
3import traceback
4import types
5from typing import Any
6from typing import Callable
7from typing import Generator
8from typing import Iterable
9from typing import List
10from typing import Optional
11from typing import Tuple
12from typing import Union
14import _pytest._code
15import pytest
16from _pytest.compat import getimfunc
17from _pytest.compat import is_async_function
18from _pytest.compat import TYPE_CHECKING
19from _pytest.config import hookimpl
20from _pytest.fixtures import FixtureRequest
21from _pytest.nodes import Collector
22from _pytest.nodes import Item
23from _pytest.outcomes import exit
24from _pytest.outcomes import fail
25from _pytest.outcomes import skip
26from _pytest.outcomes import xfail
27from _pytest.python import Class
28from _pytest.python import Function
29from _pytest.python import PyCollector
30from _pytest.runner import CallInfo
31from _pytest.skipping import skipped_by_mark_key
32from _pytest.skipping import unexpectedsuccess_key
34if TYPE_CHECKING:
35 import unittest
36 from typing import Type
38 from _pytest.fixtures import _Scope
40 _SysExcInfoType = Union[
41 Tuple[Type[BaseException], BaseException, types.TracebackType],
42 Tuple[None, None, None],
43 ]
46def pytest_pycollect_makeitem(
47 collector: PyCollector, name: str, obj: object
48) -> Optional["UnitTestCase"]:
49 # has unittest been imported and is obj a subclass of its TestCase?
50 try:
51 ut = sys.modules["unittest"]
52 # Type ignored because `ut` is an opaque module.
53 if not issubclass(obj, ut.TestCase): # type: ignore
54 return None
55 except Exception:
56 return None
57 # yes, so let's collect it
58 item = UnitTestCase.from_parent(collector, name=name, obj=obj) # type: UnitTestCase
59 return item
62class UnitTestCase(Class):
63 # marker for fixturemanger.getfixtureinfo()
64 # to declare that our children do not support funcargs
65 nofuncargs = True
67 def collect(self) -> Iterable[Union[Item, Collector]]:
68 from unittest import TestLoader
70 cls = self.obj
71 if not getattr(cls, "__test__", True):
72 return
74 skipped = _is_skipped(cls)
75 if not skipped:
76 self._inject_setup_teardown_fixtures(cls)
77 self._inject_setup_class_fixture()
79 self.session._fixturemanager.parsefactories(self, unittest=True)
80 loader = TestLoader()
81 foundsomething = False
82 for name in loader.getTestCaseNames(self.obj):
83 x = getattr(self.obj, name)
84 if not getattr(x, "__test__", True):
85 continue
86 funcobj = getimfunc(x)
87 yield TestCaseFunction.from_parent(self, name=name, callobj=funcobj)
88 foundsomething = True
90 if not foundsomething:
91 runtest = getattr(self.obj, "runTest", None)
92 if runtest is not None:
93 ut = sys.modules.get("twisted.trial.unittest", None)
94 # Type ignored because `ut` is an opaque module.
95 if ut is None or runtest != ut.TestCase.runTest: # type: ignore
96 yield TestCaseFunction.from_parent(self, name="runTest")
98 def _inject_setup_teardown_fixtures(self, cls: type) -> None:
99 """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding
100 teardown functions (#517)"""
101 class_fixture = _make_xunit_fixture(
102 cls, "setUpClass", "tearDownClass", scope="class", pass_self=False
103 )
104 if class_fixture:
105 cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined]
107 method_fixture = _make_xunit_fixture(
108 cls, "setup_method", "teardown_method", scope="function", pass_self=True
109 )
110 if method_fixture:
111 cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined]
114def _make_xunit_fixture(
115 obj: type, setup_name: str, teardown_name: str, scope: "_Scope", pass_self: bool
116):
117 setup = getattr(obj, setup_name, None)
118 teardown = getattr(obj, teardown_name, None)
119 if setup is None and teardown is None:
120 return None
122 @pytest.fixture(scope=scope, autouse=True)
123 def fixture(self, request: FixtureRequest) -> Generator[None, None, None]:
124 if _is_skipped(self):
125 reason = self.__unittest_skip_why__
126 pytest.skip(reason)
127 if setup is not None:
128 if pass_self:
129 setup(self, request.function)
130 else:
131 setup()
132 yield
133 if teardown is not None:
134 if pass_self:
135 teardown(self, request.function)
136 else:
137 teardown()
139 return fixture
142class TestCaseFunction(Function):
143 nofuncargs = True
144 _excinfo = None # type: Optional[List[_pytest._code.ExceptionInfo]]
145 _testcase = None # type: Optional[unittest.TestCase]
147 def setup(self) -> None:
148 # a bound method to be called during teardown() if set (see 'runtest()')
149 self._explicit_tearDown = None # type: Optional[Callable[[], None]]
150 assert self.parent is not None
151 self._testcase = self.parent.obj(self.name) # type: ignore[attr-defined]
152 self._obj = getattr(self._testcase, self.name)
153 if hasattr(self, "_request"):
154 self._request._fillfixtures()
156 def teardown(self) -> None:
157 if self._explicit_tearDown is not None:
158 self._explicit_tearDown()
159 self._explicit_tearDown = None
160 self._testcase = None
161 self._obj = None
163 def startTest(self, testcase: "unittest.TestCase") -> None:
164 pass
166 def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None:
167 # unwrap potential exception info (see twisted trial support below)
168 rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
169 try:
170 excinfo = _pytest._code.ExceptionInfo(rawexcinfo) # type: ignore[arg-type]
171 # invoke the attributes to trigger storing the traceback
172 # trial causes some issue there
173 excinfo.value
174 excinfo.traceback
175 except TypeError:
176 try:
177 try:
178 values = traceback.format_exception(*rawexcinfo)
179 values.insert(
180 0,
181 "NOTE: Incompatible Exception Representation, "
182 "displaying natively:\n\n",
183 )
184 fail("".join(values), pytrace=False)
185 except (fail.Exception, KeyboardInterrupt):
186 raise
187 except BaseException:
188 fail(
189 "ERROR: Unknown Incompatible Exception "
190 "representation:\n%r" % (rawexcinfo,),
191 pytrace=False,
192 )
193 except KeyboardInterrupt:
194 raise
195 except fail.Exception:
196 excinfo = _pytest._code.ExceptionInfo.from_current()
197 self.__dict__.setdefault("_excinfo", []).append(excinfo)
199 def addError(
200 self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
201 ) -> None:
202 try:
203 if isinstance(rawexcinfo[1], exit.Exception):
204 exit(rawexcinfo[1].msg)
205 except TypeError:
206 pass
207 self._addexcinfo(rawexcinfo)
209 def addFailure(
210 self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
211 ) -> None:
212 self._addexcinfo(rawexcinfo)
214 def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None:
215 try:
216 skip(reason)
217 except skip.Exception:
218 self._store[skipped_by_mark_key] = True
219 self._addexcinfo(sys.exc_info())
221 def addExpectedFailure(
222 self,
223 testcase: "unittest.TestCase",
224 rawexcinfo: "_SysExcInfoType",
225 reason: str = "",
226 ) -> None:
227 try:
228 xfail(str(reason))
229 except xfail.Exception:
230 self._addexcinfo(sys.exc_info())
232 def addUnexpectedSuccess(
233 self, testcase: "unittest.TestCase", reason: str = ""
234 ) -> None:
235 self._store[unexpectedsuccess_key] = reason
237 def addSuccess(self, testcase: "unittest.TestCase") -> None:
238 pass
240 def stopTest(self, testcase: "unittest.TestCase") -> None:
241 pass
243 def _expecting_failure(self, test_method) -> bool:
244 """Return True if the given unittest method (or the entire class) is marked
245 with @expectedFailure"""
246 expecting_failure_method = getattr(
247 test_method, "__unittest_expecting_failure__", False
248 )
249 expecting_failure_class = getattr(self, "__unittest_expecting_failure__", False)
250 return bool(expecting_failure_class or expecting_failure_method)
252 def runtest(self) -> None:
253 from _pytest.debugging import maybe_wrap_pytest_function_for_tracing
255 assert self._testcase is not None
257 maybe_wrap_pytest_function_for_tracing(self)
259 # let the unittest framework handle async functions
260 if is_async_function(self.obj):
261 # Type ignored because self acts as the TestResult, but is not actually one.
262 self._testcase(result=self) # type: ignore[arg-type]
263 else:
264 # when --pdb is given, we want to postpone calling tearDown() otherwise
265 # when entering the pdb prompt, tearDown() would have probably cleaned up
266 # instance variables, which makes it difficult to debug
267 # arguably we could always postpone tearDown(), but this changes the moment where the
268 # TestCase instance interacts with the results object, so better to only do it
269 # when absolutely needed
270 if self.config.getoption("usepdb") and not _is_skipped(self.obj):
271 self._explicit_tearDown = self._testcase.tearDown
272 setattr(self._testcase, "tearDown", lambda *args: None)
274 # we need to update the actual bound method with self.obj, because
275 # wrap_pytest_function_for_tracing replaces self.obj by a wrapper
276 setattr(self._testcase, self.name, self.obj)
277 try:
278 self._testcase(result=self) # type: ignore[arg-type]
279 finally:
280 delattr(self._testcase, self.name)
282 def _prunetraceback(self, excinfo: _pytest._code.ExceptionInfo) -> None:
283 Function._prunetraceback(self, excinfo)
284 traceback = excinfo.traceback.filter(
285 lambda x: not x.frame.f_globals.get("__unittest")
286 )
287 if traceback:
288 excinfo.traceback = traceback
291@hookimpl(tryfirst=True)
292def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None:
293 if isinstance(item, TestCaseFunction):
294 if item._excinfo:
295 call.excinfo = item._excinfo.pop(0)
296 try:
297 del call.result
298 except AttributeError:
299 pass
301 unittest = sys.modules.get("unittest")
302 if (
303 unittest
304 and call.excinfo
305 and isinstance(call.excinfo.value, unittest.SkipTest) # type: ignore[attr-defined]
306 ):
307 excinfo = call.excinfo
308 # let's substitute the excinfo with a pytest.skip one
309 call2 = CallInfo[None].from_call(
310 lambda: pytest.skip(str(excinfo.value)), call.when
311 )
312 call.excinfo = call2.excinfo
315# twisted trial support
318@hookimpl(hookwrapper=True)
319def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
320 if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
321 ut = sys.modules["twisted.python.failure"] # type: Any
322 Failure__init__ = ut.Failure.__init__
323 check_testcase_implements_trial_reporter()
325 def excstore(
326 self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
327 ):
328 if exc_value is None:
329 self._rawexcinfo = sys.exc_info()
330 else:
331 if exc_type is None:
332 exc_type = type(exc_value)
333 self._rawexcinfo = (exc_type, exc_value, exc_tb)
334 try:
335 Failure__init__(
336 self, exc_value, exc_type, exc_tb, captureVars=captureVars
337 )
338 except TypeError:
339 Failure__init__(self, exc_value, exc_type, exc_tb)
341 ut.Failure.__init__ = excstore
342 yield
343 ut.Failure.__init__ = Failure__init__
344 else:
345 yield
348def check_testcase_implements_trial_reporter(done: List[int] = []) -> None:
349 if done:
350 return
351 from zope.interface import classImplements
352 from twisted.trial.itrial import IReporter
354 classImplements(TestCaseFunction, IReporter)
355 done.append(1)
358def _is_skipped(obj) -> bool:
359 """Return True if the given object has been marked with @unittest.skip"""
360 return bool(getattr(obj, "__unittest_skip__", False))