Skip to content

Commit b62bf5a

Browse files
committed
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 d08c96e commit b62bf5a

File tree

6 files changed

+63
-26
lines changed

6 files changed

+63
-26
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-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Do not use ``TestCase.debug()`` for unittests with ``--pdb``.
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

+9
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

@@ -188,6 +189,14 @@ def stopTest(self, testcase):
188189
pass
189190

190191
def runtest(self):
192+
testMethod = getattr(self._testcase, self._testcase._testMethodName)
193+
194+
@functools.wraps(testMethod)
195+
def wrapped_testMethod(*args, **kwargs):
196+
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
197+
198+
self._testcase._wrapped_testMethod = wrapped_testMethod
199+
self._testcase._testMethodName = "_wrapped_testMethod"
191200
self._testcase(result=self)
192201

193202
def _prunetraceback(self, excinfo):

testing/test_pdb.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -177,25 +177,36 @@ def flush(child):
177177
child.wait()
178178
assert not child.isalive()
179179

180-
@pytest.mark.xfail(reason="running .debug() all the time is bad (#5991)")
181180
def test_pdb_unittest_postmortem(self, testdir):
182181
p1 = testdir.makepyfile(
183182
"""
184183
import unittest
184+
185+
teardown_called = 0
186+
185187
class Blub(unittest.TestCase):
186188
def tearDown(self):
187-
self.filename = None
188-
def test_false(self):
189+
global teardown_called
190+
teardown_called += 1
191+
192+
def test_error(self):
193+
assert teardown_called == 0
189194
self.filename = 'debug' + '.me'
190195
assert 0
196+
197+
def test_check(self):
198+
assert teardown_called == 1
191199
"""
192200
)
193-
child = testdir.spawn_pytest("--pdb %s" % p1)
201+
child = testdir.spawn_pytest(
202+
"--pdb {p1}::Blub::test_error {p1}::Blub::test_check".format(p1=p1)
203+
)
194204
child.expect("Pdb")
195-
child.sendline("p self.filename")
196-
child.sendeof()
205+
child.sendline("p 'filename=' + self.filename")
206+
child.expect("'filename=debug.me'")
207+
child.sendline("c")
197208
rest = child.read().decode("utf8")
198-
assert "debug.me" in rest
209+
assert "= 1 failed, 1 passed in" in rest
199210
self.flush(child)
200211

201212
def test_pdb_unittest_skip(self, testdir):

testing/test_unittest.py

+34-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,26 @@ 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):
1106+
p1 = testdir.makepyfile(
1107+
"""
1108+
import unittest
1109+
1110+
class MyTestCase(unittest.TestCase):
1111+
def test(self):
1112+
self.assertEqual('foo', 'foo')
1113+
"""
1114+
)
1115+
result = testdir.runpytest("--trace", str(p1))
1116+
result.stdout.fnmatch_lines(
1117+
[
1118+
"self = <test_trace.MyTestCase testMethod=_wrapped_testMethod>",
1119+
" def test(self):",
1120+
"> self.assertEqual('foo', 'foo')",
1121+
"test_trace.py:5: ",
1122+
"* in trace_dispatch",
1123+
]
1124+
)
1125+
assert result.ret == ExitCode.TESTS_FAILED

0 commit comments

Comments
 (0)