From 11677ed7f802e65b31a03cbde61668f332c25b8f Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 18 Oct 2019 18:26:03 +0200 Subject: [PATCH] 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 --- changelog/3823.bugfix.rst | 1 + changelog/5991.bugfix.rst | 1 + doc/en/unittest.rst | 11 --------- src/_pytest/unittest.py | 30 ++++++++----------------- testing/test_pdb.py | 27 +++++++++++++++++----- testing/test_unittest.py | 47 +++++++++++++++++++++++++++++++++------ 6 files changed, 72 insertions(+), 45 deletions(-) create mode 100644 changelog/3823.bugfix.rst create mode 100644 changelog/5991.bugfix.rst diff --git a/changelog/3823.bugfix.rst b/changelog/3823.bugfix.rst new file mode 100644 index 00000000000..a653fecdd92 --- /dev/null +++ b/changelog/3823.bugfix.rst @@ -0,0 +1 @@ +``--trace`` now works with unittests. diff --git a/changelog/5991.bugfix.rst b/changelog/5991.bugfix.rst new file mode 100644 index 00000000000..5659069da3e --- /dev/null +++ b/changelog/5991.bugfix.rst @@ -0,0 +1 @@ +Fix interaction with ``--pdb`` and unittests: do not use unittest's ``TestCase.debug()``. diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 0f6737c0dc0..cd7858190fb 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -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 diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 11dc77cc4ff..fea417925cf 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -1,4 +1,5 @@ """ discovery and running of std-library "unittest" style tests. """ +import functools import sys import traceback @@ -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) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 25d2292e9cb..f4779d4ee73 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -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): diff --git a/testing/test_unittest.py b/testing/test_unittest.py index f56284d8510..b34f543135f 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -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 *", ] ) @@ -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