From a0ff5deabf74afe7839d6120f7e126375edc23d2 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 11 Mar 2019 15:47:07 +0100 Subject: [PATCH 1/2] pdb: trigger pytest_enter_pdb hook with post-mortem This is required for pytest-pdb to be called with `--pdb`. TODO: - [ ] test - [ ] pass mode to hook, e.g. "post_mortem" in this case? --- changelog/4908.bugfix.rst | 1 + src/_pytest/debugging.py | 27 ++++++++++++--------------- testing/test_pdb.py | 20 +++++++++++++++----- 3 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 changelog/4908.bugfix.rst diff --git a/changelog/4908.bugfix.rst b/changelog/4908.bugfix.rst new file mode 100644 index 000000000..2513618a1 --- /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/debugging.py b/src/_pytest/debugging.py index 52c6536f4..3c433b274 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -212,6 +212,17 @@ class pytestPDB(object): 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 3b21bacd9..9bac78557 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -744,7 +744,8 @@ class TestPDB(object): ["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 @@ class TestPDB(object): """ 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 From e0b584d0485329bad3b881c0c7a4e040f4b0b6dd Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 23 May 2019 08:56:52 +0200 Subject: [PATCH 2/2] CaptureFixture: do not crash in _suspend when not started This happened in test_pdb_with_caplog_on_pdb_invocation. --- src/_pytest/capture.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 560171134..84f773254 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -358,8 +358,7 @@ class CaptureFixture(object): 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 @@ class CaptureFixture(object): 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):