Skip to content

Commit c1d05f3

Browse files
committed
pdb: resume capturing after continue
After `pdb.set_trace()` capturing is turned off. This patch resumes it after using the `continue` (or `c` / `cont`) command. Store _pytest_capman on the class, for pdbpp's do_debug hack to keep it. Without this, `debug …` would fail like this: /usr/lib/python3.6/cmd.py:217: in onecmd return func(arg) .venv/lib/python3.6/site-packages/pdb.py:608: in do_debug return orig_do_debug(self, arg) /usr/lib/python3.6/pdb.py:1099: in do_debug sys.call_tracing(p.run, (arg, globals, locals)) /usr/lib/python3.6/bdb.py:434: in run exec(cmd, globals, locals) /usr/lib/python3.6/bdb.py:51: in trace_dispatch return self.dispatch_line(frame) /usr/lib/python3.6/bdb.py:69: in dispatch_line self.user_line(frame) /usr/lib/python3.6/pdb.py:261: in user_line self.interaction(frame, None) .venv/lib/python3.6/site-packages/pdb.py:203: in interaction self.setup(frame, traceback) E AttributeError: 'PytestPdb' object has no attribute '_pytest_capman' - add pytest_leave_pdb hook - fixes test_pdb_interaction_capturing_twice: would fail on master now, but works here
1 parent f30911d commit c1d05f3

File tree

4 files changed

+70
-7
lines changed

4 files changed

+70
-7
lines changed

changelog/2619.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``.

src/_pytest/debugging.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,44 @@ def set_trace(cls, set_break=True):
9696
tw.line()
9797
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
9898
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
99+
100+
class _PdbWrapper(cls._pdb_cls, object):
101+
_pytest_capman = capman
102+
_continued = False
103+
104+
def do_continue(self, arg):
105+
ret = super(_PdbWrapper, self).do_continue(arg)
106+
if self._pytest_capman:
107+
tw = _pytest.config.create_terminal_writer(cls._config)
108+
tw.line()
109+
tw.sep(">", "PDB continue (IO-capturing resumed)")
110+
self._pytest_capman.resume_global_capture()
111+
cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config)
112+
self._continued = True
113+
return ret
114+
115+
do_c = do_cont = do_continue
116+
117+
def setup(self, f, tb):
118+
"""Suspend on setup().
119+
120+
Needed after do_continue resumed, and entering another
121+
breakpoint again.
122+
"""
123+
ret = super(_PdbWrapper, self).setup(f, tb)
124+
if not ret and self._continued:
125+
# pdb.setup() returns True if the command wants to exit
126+
# from the interaction: do not suspend capturing then.
127+
if self._pytest_capman:
128+
self._pytest_capman.suspend_global_capture(in_=True)
129+
return ret
130+
131+
_pdb = _PdbWrapper()
132+
else:
133+
_pdb = cls._pdb_cls()
134+
99135
if set_break:
100-
cls._pdb_cls().set_trace(frame)
136+
_pdb.set_trace(frame)
101137

102138

103139
class PdbInvoke(object):

src/_pytest/hookspec.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,3 +609,13 @@ def pytest_enter_pdb(config):
609609
610610
:param _pytest.config.Config config: pytest config object
611611
"""
612+
613+
614+
def pytest_leave_pdb(config):
615+
""" called when leaving pdb (e.g. with continue after pdb.set_trace()).
616+
617+
Can be used by plugins to take special action just after the python
618+
debugger leaves interactive mode.
619+
620+
:param _pytest.config.Config config: pytest config object
621+
"""

testing/test_pdb.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def test_not_called_due_to_quit():
158158
assert "= 1 failed in" in rest
159159
assert "def test_1" not in rest
160160
assert "Exit: Quitting debugger" in rest
161+
assert "PDB continue (IO-capturing resumed)" not in rest
161162
self.flush(child)
162163

163164
@staticmethod
@@ -489,18 +490,23 @@ def test_1():
489490
"""
490491
)
491492
child = testdir.spawn_pytest(str(p1))
493+
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
492494
child.expect("test_1")
493495
child.expect("x = 3")
494496
child.expect("Pdb")
495497
child.sendline("c")
498+
child.expect(r"PDB continue \(IO-capturing resumed\)")
499+
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
496500
child.expect("x = 4")
497501
child.expect("Pdb")
498502
child.sendeof()
503+
child.expect("_ test_1 _")
504+
child.expect("def test_1")
505+
child.expect("Captured stdout call")
499506
rest = child.read().decode("utf8")
500-
assert "1 failed" in rest
501-
assert "def test_1" in rest
502507
assert "hello17" in rest # out is captured
503508
assert "hello18" in rest # out is captured
509+
assert "1 failed" in rest
504510
self.flush(child)
505511

506512
def test_pdb_used_outside_test(self, testdir):
@@ -541,15 +547,19 @@ def test_pdb_collection_failure_is_shown(self, testdir):
541547
["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF
542548
)
543549

544-
def test_enter_pdb_hook_is_called(self, testdir):
550+
def test_enter_leave_pdb_hooks_are_called(self, testdir):
545551
testdir.makeconftest(
546552
"""
553+
def pytest_configure(config):
554+
config.testing_verification = 'configured'
555+
547556
def pytest_enter_pdb(config):
548557
assert config.testing_verification == 'configured'
549558
print('enter_pdb_hook')
550559
551-
def pytest_configure(config):
552-
config.testing_verification = 'configured'
560+
def pytest_leave_pdb(config):
561+
assert config.testing_verification == 'configured'
562+
print('leave_pdb_hook')
553563
"""
554564
)
555565
p1 = testdir.makepyfile(
@@ -558,11 +568,17 @@ def pytest_configure(config):
558568
559569
def test_foo():
560570
pytest.set_trace()
571+
assert 0
561572
"""
562573
)
563574
child = testdir.spawn_pytest(str(p1))
564575
child.expect("enter_pdb_hook")
565-
child.send("c\n")
576+
child.sendline("c")
577+
child.expect(r"PDB continue \(IO-capturing resumed\)")
578+
child.expect("Captured stdout call")
579+
rest = child.read().decode("utf8")
580+
assert "leave_pdb_hook" in rest
581+
assert "1 failed" in rest
566582
child.sendeof()
567583
self.flush(child)
568584

0 commit comments

Comments
 (0)