Skip to content

Commit 40ad68c

Browse files
blueyedvinaycalastry
authored andcommitted
unittest: do not use TestCase.debug() with --pdb
Fixes pytest-dev#5991 Fixes pytest-dev#3823 Ref: pytest-dev/pytest-django#772 Ref: pytest-dev#1890 Ref: pytest-dev/pytest-django#782 - inject wrapped testMethod - adjust test_trial_error - add test for `--trace` with unittests
1 parent d8b939e commit 40ad68c

File tree

5 files changed

+82
-38
lines changed

5 files changed

+82
-38
lines changed

changelog/3823.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``--trace`` now works with unittests.

changelog/5991.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix interaction with ``--pdb`` and unittests: do not use unittest's ``TestCase.debug()``.

doc/en/unittest.rst

-11
Original file line numberDiff line numberDiff line change
@@ -238,17 +238,6 @@ was executed ahead of the ``test_method``.
238238

239239
.. _pdb-unittest-note:
240240

241-
.. note::
242-
243-
Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
244-
disable tearDown and cleanup methods for the case that an Exception
245-
occurs. This allows proper post mortem debugging for all applications
246-
which have significant logic in their tearDown machinery. However,
247-
supporting this feature has the following side effect: If people
248-
overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to
249-
to overwrite ``debug`` in the same way (this is also true for standard
250-
unittest).
251-
252241
.. note::
253242

254243
Due to architectural differences between the two frameworks, setup and

src/_pytest/unittest.py

+40-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
""" discovery and running of std-library "unittest" style tests. """
2+
import functools
23
import sys
34
import traceback
45

@@ -107,6 +108,7 @@ class TestCaseFunction(Function):
107108
nofuncargs = True
108109
_excinfo = None
109110
_testcase = None
111+
_need_tearDown = None
110112

111113
def setup(self):
112114
self._testcase = self.parent.obj(self.name)
@@ -115,6 +117,8 @@ def setup(self):
115117
self._request._fillfixtures()
116118

117119
def teardown(self):
120+
if self._need_tearDown:
121+
self._testcase.tearDown()
118122
self._testcase = None
119123
self._obj = None
120124

@@ -187,29 +191,45 @@ def addSuccess(self, testcase):
187191
def stopTest(self, testcase):
188192
pass
189193

190-
def _handle_skip(self):
191-
# implements the skipping machinery (see #2137)
192-
# analog to pythons Lib/unittest/case.py:run
194+
def runtest(self):
193195
testMethod = getattr(self._testcase, self._testcase._testMethodName)
194-
if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr(
195-
testMethod, "__unittest_skip__", False
196-
):
197-
# If the class or method was skipped.
198-
skip_why = getattr(
199-
self._testcase.__class__, "__unittest_skip_why__", ""
200-
) or getattr(testMethod, "__unittest_skip_why__", "")
201-
self._testcase._addSkip(self, self._testcase, skip_why)
202-
return True
203-
return False
204196

205-
def runtest(self):
206-
if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
197+
class _GetOutOf_testPartExecutor(KeyboardInterrupt):
198+
"""Helper exception to get out of unittests's testPartExecutor."""
199+
200+
unittest = sys.modules.get("unittest")
201+
202+
reraise = ()
203+
if unittest:
204+
reraise += (unittest.SkipTest,)
205+
206+
@functools.wraps(testMethod)
207+
def wrapped_testMethod(*args, **kwargs):
208+
try:
209+
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
210+
except reraise:
211+
raise
212+
except Exception as exc:
213+
expecting_failure_method = getattr(
214+
testMethod, "__unittest_expecting_failure__", False
215+
)
216+
expecting_failure_class = getattr(
217+
self, "__unittest_expecting_failure__", False
218+
)
219+
expecting_failure = expecting_failure_class or expecting_failure_method
220+
self._need_tearDown = True
221+
222+
if expecting_failure:
223+
raise
224+
225+
raise _GetOutOf_testPartExecutor(exc)
226+
227+
self._testcase._wrapped_testMethod = wrapped_testMethod
228+
self._testcase._testMethodName = "_wrapped_testMethod"
229+
try:
207230
self._testcase(result=self)
208-
else:
209-
# disables tearDown and cleanups for post mortem debugging (see #1890)
210-
if self._handle_skip():
211-
return
212-
self._testcase.debug()
231+
except _GetOutOf_testPartExecutor as exc:
232+
raise exc.args[0] from exc.args[0]
213233

214234
def _prunetraceback(self, excinfo):
215235
Function._prunetraceback(self, excinfo)

testing/test_unittest.py

+40-7
Original file line numberDiff line numberDiff line change
@@ -537,24 +537,28 @@ def f(_):
537537
)
538538
result.stdout.fnmatch_lines(
539539
[
540-
"test_trial_error.py::TC::test_four FAILED",
540+
"test_trial_error.py::TC::test_four SKIPPED",
541541
"test_trial_error.py::TC::test_four ERROR",
542542
"test_trial_error.py::TC::test_one FAILED",
543543
"test_trial_error.py::TC::test_three FAILED",
544-
"test_trial_error.py::TC::test_two FAILED",
544+
"test_trial_error.py::TC::test_two SKIPPED",
545+
"test_trial_error.py::TC::test_two ERROR",
545546
"*ERRORS*",
546547
"*_ ERROR at teardown of TC.test_four _*",
548+
"NOTE: Incompatible Exception Representation, displaying natively:",
549+
"*DelayedCalls*",
550+
"*_ ERROR at teardown of TC.test_two _*",
551+
"NOTE: Incompatible Exception Representation, displaying natively:",
547552
"*DelayedCalls*",
548553
"*= FAILURES =*",
549-
"*_ TC.test_four _*",
550-
"*NameError*crash*",
554+
# "*_ TC.test_four _*",
555+
# "*NameError*crash*",
551556
"*_ TC.test_one _*",
552557
"*NameError*crash*",
553558
"*_ TC.test_three _*",
559+
"NOTE: Incompatible Exception Representation, displaying natively:",
554560
"*DelayedCalls*",
555-
"*_ TC.test_two _*",
556-
"*NameError*crash*",
557-
"*= 4 failed, 1 error in *",
561+
"*= 2 failed, 2 skipped, 2 errors in *",
558562
]
559563
)
560564

@@ -1096,3 +1100,32 @@ def test_should_not_run(self):
10961100
)
10971101
result = testdir.runpytest()
10981102
result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"])
1103+
1104+
1105+
def test_trace(testdir, monkeypatch):
1106+
calls = []
1107+
1108+
def check_call(*args, **kwargs):
1109+
calls.append((args, kwargs))
1110+
assert args == ("runcall",)
1111+
1112+
class _pdb:
1113+
def runcall(*args, **kwargs):
1114+
calls.append((args, kwargs))
1115+
1116+
return _pdb
1117+
1118+
monkeypatch.setattr("_pytest.debugging.pytestPDB._init_pdb", check_call)
1119+
1120+
p1 = testdir.makepyfile(
1121+
"""
1122+
import unittest
1123+
1124+
class MyTestCase(unittest.TestCase):
1125+
def test(self):
1126+
self.assertEqual('foo', 'foo')
1127+
"""
1128+
)
1129+
result = testdir.runpytest("--trace", str(p1))
1130+
assert len(calls) == 2
1131+
assert result.ret == 0

0 commit comments

Comments
 (0)