diff --git a/changelog/4908.bugfix.rst b/changelog/4908.bugfix.rst new file mode 100644 index 00000000000..2513618a1d9 --- /dev/null +++ b/changelog/4908.bugfix.rst @@ -0,0 +1 @@ +The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``). diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 56017113429..84f773254fe 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -358,8 +358,7 @@ def __init__(self, captureclass, request): self._captured_err = self.captureclass.EMPTY_BUFFER def _start(self): - # Start if not started yet - if getattr(self, "_capture", None) is None: + if self._capture is None: self._capture = MultiCapture( out=True, err=True, in_=False, Capture=self.captureclass ) @@ -389,11 +388,13 @@ def readouterr(self): def _suspend(self): """Suspends this fixture's own capturing temporarily.""" - self._capture.suspend_capturing() + if self._capture is not None: + self._capture.suspend_capturing() def _resume(self): """Resumes this fixture's own capturing temporarily.""" - self._capture.resume_capturing() + if self._capture is not None: + self._capture.resume_capturing() @contextlib.contextmanager def disabled(self): diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 52c6536f4dc..3c433b274c5 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -212,6 +212,17 @@ def setup(self, f, tb): self._pytest_capman.suspend_global_capture(in_=True) return ret + def get_stack(self, f, t): + stack, i = super(PytestPdbWrapper, self).get_stack(f, t) + if f is None: + # Find last non-hidden frame. + i = max(0, len(stack) - 1) + while i and stack[i][0].f_locals.get( + "__tracebackhide__", False + ): + i -= 1 + return stack, i + _pdb = PytestPdbWrapper(**kwargs) cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb) else: @@ -298,22 +309,8 @@ def _postmortem_traceback(excinfo): return excinfo._excinfo[2] -def _find_last_non_hidden_frame(stack): - i = max(0, len(stack) - 1) - while i and stack[i][0].f_locals.get("__tracebackhide__", False): - i -= 1 - return i - - def post_mortem(t): - class Pdb(pytestPDB._pdb_cls, object): - def get_stack(self, f, t): - stack, i = super(Pdb, self).get_stack(f, t) - if f is None: - i = _find_last_non_hidden_frame(stack) - return stack, i - - p = Pdb() + p = pytestPDB._init_pdb() p.reset() p.interaction(None, t) if p.quitting: diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 3b21bacd902..9bac7855780 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -744,7 +744,8 @@ def test_pdb_collection_failure_is_shown(self, testdir): ["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF ) - def test_enter_leave_pdb_hooks_are_called(self, testdir): + @pytest.mark.parametrize("post_mortem", (False, True)) + def test_enter_leave_pdb_hooks_are_called(self, post_mortem, testdir): testdir.makeconftest( """ mypdb = None @@ -773,16 +774,25 @@ def pytest_leave_pdb(config, pdb): """ import pytest - def test_foo(): + def test_set_trace(): pytest.set_trace() assert 0 + + def test_post_mortem(): + assert 0 """ ) - child = testdir.spawn_pytest(str(p1)) + if post_mortem: + child = testdir.spawn_pytest(str(p1) + " --pdb -s -k test_post_mortem") + else: + child = testdir.spawn_pytest(str(p1) + " -k test_set_trace") child.expect("enter_pdb_hook") child.sendline("c") - child.expect(r"PDB continue \(IO-capturing resumed\)") - child.expect("Captured stdout call") + if post_mortem: + child.expect(r"PDB continue") + else: + child.expect(r"PDB continue \(IO-capturing resumed\)") + child.expect("Captured stdout call") rest = child.read().decode("utf8") assert "leave_pdb_hook" in rest assert "1 failed" in rest