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 b817017
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 39 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
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 b817017

Please sign in to comment.