unittest: do not use TestCase.debug() with `--pdb`
Fixes https://github.com/pytest-dev/pytest/issues/5991 Fixes https://github.com/pytest-dev/pytest/issues/3823 Ref: https://github.com/pytest-dev/pytest-django/issues/772 Ref: https://github.com/pytest-dev/pytest/pull/1890 Ref: https://github.com/pytest-dev/pytest-django/pull/782 - inject wrapped testMethod - adjust test_trial_error - add test for `--trace` with unittests
This commit is contained in:
parent
710e3c40e0
commit
04f27d4eb4
|
@ -0,0 +1 @@
|
||||||
|
``--trace`` now works with unittests.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix interaction with ``--pdb`` and unittests: do not use unittest's ``TestCase.debug()``.
|
|
@ -238,17 +238,6 @@ was executed ahead of the ``test_method``.
|
||||||
|
|
||||||
.. _pdb-unittest-note:
|
.. _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::
|
.. note::
|
||||||
|
|
||||||
Due to architectural differences between the two frameworks, setup and
|
Due to architectural differences between the two frameworks, setup and
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
""" discovery and running of std-library "unittest" style tests. """
|
""" discovery and running of std-library "unittest" style tests. """
|
||||||
|
import functools
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
@ -107,6 +108,7 @@ class TestCaseFunction(Function):
|
||||||
nofuncargs = True
|
nofuncargs = True
|
||||||
_excinfo = None
|
_excinfo = None
|
||||||
_testcase = None
|
_testcase = None
|
||||||
|
_need_tearDown = None
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self._testcase = self.parent.obj(self.name)
|
self._testcase = self.parent.obj(self.name)
|
||||||
|
@ -115,6 +117,8 @@ class TestCaseFunction(Function):
|
||||||
self._request._fillfixtures()
|
self._request._fillfixtures()
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
|
if self._need_tearDown:
|
||||||
|
self._testcase.tearDown()
|
||||||
self._testcase = None
|
self._testcase = None
|
||||||
self._obj = None
|
self._obj = None
|
||||||
|
|
||||||
|
@ -187,29 +191,45 @@ class TestCaseFunction(Function):
|
||||||
def stopTest(self, testcase):
|
def stopTest(self, testcase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _handle_skip(self):
|
|
||||||
# implements the skipping machinery (see #2137)
|
|
||||||
# analog to pythons Lib/unittest/case.py:run
|
|
||||||
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):
|
def runtest(self):
|
||||||
if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
|
testMethod = getattr(self._testcase, self._testcase._testMethodName)
|
||||||
|
|
||||||
|
class _GetOutOf_testPartExecutor(KeyboardInterrupt):
|
||||||
|
"""Helper exception to get out of unittests's testPartExecutor."""
|
||||||
|
|
||||||
|
unittest = sys.modules.get("unittest")
|
||||||
|
|
||||||
|
reraise = ()
|
||||||
|
if unittest:
|
||||||
|
reraise += (unittest.SkipTest,)
|
||||||
|
|
||||||
|
@functools.wraps(testMethod)
|
||||||
|
def wrapped_testMethod(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
||||||
|
except reraise:
|
||||||
|
raise
|
||||||
|
except Exception as exc:
|
||||||
|
expecting_failure_method = getattr(
|
||||||
|
testMethod, "__unittest_expecting_failure__", False
|
||||||
|
)
|
||||||
|
expecting_failure_class = getattr(
|
||||||
|
self, "__unittest_expecting_failure__", False
|
||||||
|
)
|
||||||
|
expecting_failure = expecting_failure_class or expecting_failure_method
|
||||||
|
self._need_tearDown = True
|
||||||
|
|
||||||
|
if expecting_failure:
|
||||||
|
raise
|
||||||
|
|
||||||
|
raise _GetOutOf_testPartExecutor(exc)
|
||||||
|
|
||||||
|
self._testcase._wrapped_testMethod = wrapped_testMethod
|
||||||
|
self._testcase._testMethodName = "_wrapped_testMethod"
|
||||||
|
try:
|
||||||
self._testcase(result=self)
|
self._testcase(result=self)
|
||||||
else:
|
except _GetOutOf_testPartExecutor as exc:
|
||||||
# disables tearDown and cleanups for post mortem debugging (see #1890)
|
raise exc.args[0] from exc.args[0]
|
||||||
if self._handle_skip():
|
|
||||||
return
|
|
||||||
self._testcase.debug()
|
|
||||||
|
|
||||||
def _prunetraceback(self, excinfo):
|
def _prunetraceback(self, excinfo):
|
||||||
Function._prunetraceback(self, excinfo)
|
Function._prunetraceback(self, excinfo)
|
||||||
|
|
|
@ -537,24 +537,28 @@ class TestTrialUnittest:
|
||||||
)
|
)
|
||||||
result.stdout.fnmatch_lines(
|
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_four ERROR",
|
||||||
"test_trial_error.py::TC::test_one FAILED",
|
"test_trial_error.py::TC::test_one FAILED",
|
||||||
"test_trial_error.py::TC::test_three 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*",
|
"*ERRORS*",
|
||||||
"*_ ERROR at teardown of TC.test_four _*",
|
"*_ 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*",
|
"*DelayedCalls*",
|
||||||
"*= FAILURES =*",
|
"*= FAILURES =*",
|
||||||
"*_ TC.test_four _*",
|
# "*_ TC.test_four _*",
|
||||||
"*NameError*crash*",
|
# "*NameError*crash*",
|
||||||
"*_ TC.test_one _*",
|
"*_ TC.test_one _*",
|
||||||
"*NameError*crash*",
|
"*NameError*crash*",
|
||||||
"*_ TC.test_three _*",
|
"*_ TC.test_three _*",
|
||||||
|
"NOTE: Incompatible Exception Representation, displaying natively:",
|
||||||
"*DelayedCalls*",
|
"*DelayedCalls*",
|
||||||
"*_ TC.test_two _*",
|
"*= 2 failed, 2 skipped, 2 errors in *",
|
||||||
"*NameError*crash*",
|
|
||||||
"*= 4 failed, 1 error in *",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1096,3 +1100,32 @@ def test_exit_outcome(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"])
|
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
|
||||||
|
|
Loading…
Reference in New Issue