Skip to content

Commit 04d955a

Browse files
committed
pdb: fix capturing with recursive debugging and pdb++
While I think that pdb++ should be fixed in this regard (by using `pdb.Pdb`, and not `self.__class__` maybe), this ensures that custom debuggers like this are working.
1 parent 7ab3d81 commit 04d955a

File tree

3 files changed

+89
-9
lines changed

3 files changed

+89
-9
lines changed

changelog/4347.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix output capturing when using pdb++ with recursive debugging.

src/_pytest/debugging.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class pytestPDB(object):
7575
_config = None
7676
_pdb_cls = pdb.Pdb
7777
_saved = []
78+
_recursive_debug = 0
7879

7980
@classmethod
8081
def set_trace(cls, set_break=True):
@@ -88,25 +89,33 @@ def set_trace(cls, set_break=True):
8889
capman.suspend_global_capture(in_=True)
8990
tw = _pytest.config.create_terminal_writer(cls._config)
9091
tw.line()
91-
if capman and capman.is_globally_capturing():
92-
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
93-
else:
94-
tw.sep(">", "PDB set_trace")
92+
if cls._recursive_debug == 0:
93+
if capman and capman.is_globally_capturing():
94+
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
95+
else:
96+
tw.sep(">", "PDB set_trace")
9597

9698
class _PdbWrapper(cls._pdb_cls, object):
9799
_pytest_capman = capman
98100
_continued = False
99101

102+
def do_debug(self, arg):
103+
cls._recursive_debug += 1
104+
ret = super(_PdbWrapper, self).do_debug(arg)
105+
cls._recursive_debug -= 1
106+
return ret
107+
100108
def do_continue(self, arg):
101109
ret = super(_PdbWrapper, self).do_continue(arg)
102110
if self._pytest_capman:
103111
tw = _pytest.config.create_terminal_writer(cls._config)
104112
tw.line()
105-
if self._pytest_capman.is_globally_capturing():
106-
tw.sep(">", "PDB continue (IO-capturing resumed)")
107-
else:
108-
tw.sep(">", "PDB continue")
109-
self._pytest_capman.resume_global_capture()
113+
if cls._recursive_debug == 0:
114+
if self._pytest_capman.is_globally_capturing():
115+
tw.sep(">", "PDB continue (IO-capturing resumed)")
116+
else:
117+
tw.sep(">", "PDB continue")
118+
self._pytest_capman.resume_global_capture()
110119
cls._pluginmanager.hook.pytest_leave_pdb(
111120
config=cls._config, pdb=self
112121
)

testing/test_pdb.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,76 @@ def test_1():
518518
assert "1 failed" in rest
519519
self.flush(child)
520520

521+
def test_pdb_interaction_continue_recursive(self, testdir):
522+
p1 = testdir.makepyfile(
523+
mytest="""
524+
import pdb
525+
import pytest
526+
527+
count_continue = 0
528+
529+
# Simulates pdbpp, which injects Pdb into do_debug, and uses
530+
# self.__class__ in do_continue.
531+
class CustomPdb(pdb.Pdb):
532+
def do_debug(self, arg):
533+
import sys
534+
import types
535+
536+
newglobals = {
537+
'Pdb': self.__class__, # NOTE: different with pdb.Pdb
538+
'sys': sys,
539+
}
540+
if sys.version_info < (3, ):
541+
do_debug_func = pdb.Pdb.do_debug.im_func
542+
else:
543+
do_debug_func = pdb.Pdb.do_debug
544+
545+
orig_do_debug = types.FunctionType(
546+
do_debug_func.__code__, newglobals,
547+
do_debug_func.__name__, do_debug_func.__defaults__,
548+
)
549+
return orig_do_debug(self, arg)
550+
do_debug.__doc__ = pdb.Pdb.do_debug.__doc__
551+
552+
def do_continue(self, *args, **kwargs):
553+
global count_continue
554+
count_continue += 1
555+
return super(CustomPdb, self).do_continue(*args, **kwargs)
556+
557+
def foo():
558+
print("print_from_foo")
559+
560+
def test_1():
561+
i = 0
562+
print("hello17")
563+
pytest.set_trace()
564+
x = 3
565+
print("hello18")
566+
567+
assert count_continue == 2, "unexpected_failure: %d != 2" % count_continue
568+
pytest.fail("expected_failure")
569+
"""
570+
)
571+
child = testdir.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1))
572+
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
573+
child.expect(r"\n\(Pdb")
574+
child.sendline("debug foo()")
575+
child.expect("ENTERING RECURSIVE DEBUGGER")
576+
child.expect(r"\n\(\(Pdb")
577+
child.sendline("c")
578+
child.expect("LEAVING RECURSIVE DEBUGGER")
579+
assert b"PDB continue" not in child.before
580+
assert b"print_from_foo" in child.before
581+
child.sendline("c")
582+
child.expect(r"PDB continue \(IO-capturing resumed\)")
583+
rest = child.read().decode("utf8")
584+
assert "hello17" in rest # out is captured
585+
assert "hello18" in rest # out is captured
586+
assert "1 failed" in rest
587+
assert "Failed: expected_failure" in rest
588+
assert "AssertionError: unexpected_failure" not in rest
589+
self.flush(child)
590+
521591
def test_pdb_without_capture(self, testdir):
522592
p1 = testdir.makepyfile(
523593
"""

0 commit comments

Comments
 (0)