Skip to content

Commit

Permalink
unittest: do not use TestCase.debug() with --pdb
Browse files Browse the repository at this point in the history
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
  • Loading branch information
blueyed committed Nov 9, 2019
1 parent 245e1f1 commit 11677ed
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 45 deletions.
1 change: 1 addition & 0 deletions changelog/3823.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``--trace`` now works with unittests.
1 change: 1 addition & 0 deletions changelog/5991.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix interaction with ``--pdb`` and unittests: do not use unittest's ``TestCase.debug()``.
11 changes: 0 additions & 11 deletions doc/en/unittest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,17 +238,6 @@ was executed ahead of the ``test_method``.

.. _pdb-unittest-note:

.. note::

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

.. note::

Due to architectural differences between the two frameworks, setup and
Expand Down
30 changes: 9 additions & 21 deletions src/_pytest/unittest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" discovery and running of std-library "unittest" style tests. """
import functools
import sys
import traceback

Expand Down Expand Up @@ -187,29 +188,16 @@ def addSuccess(self, testcase):
def stopTest(self, testcase):
pass

def _handle_skip(self):
# implements the skipping machinery (see #2137)
# analog to pythons Lib/unittest/case.py:run
def runtest(self):
testMethod = getattr(self._testcase, self._testcase._testMethodName)
if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr(
testMethod, "__unittest_skip__", False
):
# If the class or method was skipped.
skip_why = getattr(
self._testcase.__class__, "__unittest_skip_why__", ""
) or getattr(testMethod, "__unittest_skip_why__", "")
self._testcase._addSkip(self, self._testcase, skip_why)
return True
return False

def runtest(self):
if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
self._testcase(result=self)
else:
# disables tearDown and cleanups for post mortem debugging (see #1890)
if self._handle_skip():
return
self._testcase.debug()
@functools.wraps(testMethod)
def wrapped_testMethod(*args, **kwargs):
self.ihook.pytest_pyfunc_call(pyfuncitem=self)

self._testcase._wrapped_testMethod = wrapped_testMethod
self._testcase._testMethodName = "_wrapped_testMethod"
self._testcase(result=self)

def _prunetraceback(self, excinfo):
Function._prunetraceback(self, excinfo)
Expand Down
27 changes: 21 additions & 6 deletions testing/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,35 @@ def test_pdb_unittest_postmortem(self, testdir):
p1 = testdir.makepyfile(
"""
import unittest
teardown_called = 0
class Blub(unittest.TestCase):
def tearDown(self):
self.filename = None
def test_false(self):
global teardown_called
teardown_called += 1
def test_error(self):
assert teardown_called == 0
self.filename = 'debug' + '.me'
assert 0
def test_check(self):
assert teardown_called == 1
"""
)
child = testdir.spawn_pytest("--pdb %s" % p1)
child = testdir.spawn_pytest(
"--pdb {p1}::Blub::test_error {p1}::Blub::test_check".format(p1=p1)
)
child.expect("Pdb")
child.sendline("p self.filename")
child.sendeof()
child.sendline("p 'filename=' + self.filename")
child.expect("'filename=debug.me'")
child.sendline("c")
rest = child.read().decode("utf8")
assert "debug.me" in rest
assert (
"= \x1b[31m\x1b[1m1 failed\x1b[0m, \x1b[32m1 passed\x1b[0m\x1b[31m in"
in rest
)
self.flush(child)

def test_pdb_unittest_skip(self, testdir):
Expand Down
47 changes: 40 additions & 7 deletions testing/test_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,24 +537,28 @@ def f(_):
)
result.stdout.fnmatch_lines(
[
"test_trial_error.py::TC::test_four FAILED",
"test_trial_error.py::TC::test_four SKIPPED",
"test_trial_error.py::TC::test_four ERROR",
"test_trial_error.py::TC::test_one FAILED",
"test_trial_error.py::TC::test_three FAILED",
"test_trial_error.py::TC::test_two FAILED",
"test_trial_error.py::TC::test_two SKIPPED",
"test_trial_error.py::TC::test_two ERROR",
"*ERRORS*",
"*_ ERROR at teardown of TC.test_four _*",
"NOTE: Incompatible Exception Representation, displaying natively:",
"*DelayedCalls*",
"*_ ERROR at teardown of TC.test_two _*",
"NOTE: Incompatible Exception Representation, displaying natively:",
"*DelayedCalls*",
"*= FAILURES =*",
"*_ TC.test_four _*",
"*NameError*crash*",
# "*_ TC.test_four _*",
# "*NameError*crash*",
"*_ TC.test_one _*",
"*NameError*crash*",
"*_ TC.test_three _*",
"NOTE: Incompatible Exception Representation, displaying natively:",
"*DelayedCalls*",
"*_ TC.test_two _*",
"*NameError*crash*",
"*= 4 failed, 1 error in *",
"*= 2 failed, 2 skipped, 2 errors in *",
]
)

Expand Down Expand Up @@ -1096,3 +1100,32 @@ def test_should_not_run(self):
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"])


def test_trace(testdir, monkeypatch):
calls = []

def check_call(*args, **kwargs):
calls.append((args, kwargs))
assert args == ("runcall",)

class _pdb:
def runcall(*args, **kwargs):
calls.append((args, kwargs))

return _pdb

monkeypatch.setattr("_pytest.debugging.pytestPDB._init_pdb", check_call)

p1 = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
def test(self):
self.assertEqual('foo', 'foo')
"""
)
result = testdir.runpytest("--trace", str(p1))
assert len(calls) == 2
assert result.ret == 0

0 comments on commit 11677ed

Please sign in to comment.