Skip to content

Commit f466105

Browse files
authored
Merge pull request #2619 from blueyed/pdb-resume-capture
pdb: resume capturing after `continue`
2 parents 65b97c2 + ede3a4e commit f466105

File tree

4 files changed

+90
-10
lines changed

4 files changed

+90
-10
lines changed

changelog/2619.feature.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``.
2+
3+
This also adds a new ``pytest_leave_pdb`` hook, and passes in ``pdb`` to the
4+
existing ``pytest_enter_pdb`` hook.

src/_pytest/debugging.py

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

102140

103141
class PdbInvoke(object):

src/_pytest/hookspec.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,9 +603,21 @@ def pytest_exception_interact(node, call, report):
603603
"""
604604

605605

606-
def pytest_enter_pdb(config):
606+
def pytest_enter_pdb(config, pdb):
607607
""" called upon pdb.set_trace(), can be used by plugins to take special
608608
action just before the python debugger enters in interactive mode.
609609
610610
:param _pytest.config.Config config: pytest config object
611+
:param pdb.Pdb pdb: Pdb instance
612+
"""
613+
614+
615+
def pytest_leave_pdb(config, pdb):
616+
""" called when leaving pdb (e.g. with continue after pdb.set_trace()).
617+
618+
Can be used by plugins to take special action just after the python
619+
debugger leaves interactive mode.
620+
621+
:param _pytest.config.Config config: pytest config object
622+
:param pdb.Pdb pdb: Pdb instance
611623
"""

testing/test_pdb.py

Lines changed: 33 additions & 7 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,29 @@ 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
"""
547-
def pytest_enter_pdb(config):
548-
assert config.testing_verification == 'configured'
549-
print('enter_pdb_hook')
553+
mypdb = None
550554
551555
def pytest_configure(config):
552556
config.testing_verification = 'configured'
557+
558+
def pytest_enter_pdb(config, pdb):
559+
assert config.testing_verification == 'configured'
560+
print('enter_pdb_hook')
561+
562+
global mypdb
563+
mypdb = pdb
564+
mypdb.set_attribute = "bar"
565+
566+
def pytest_leave_pdb(config, pdb):
567+
assert config.testing_verification == 'configured'
568+
print('leave_pdb_hook')
569+
570+
global mypdb
571+
assert mypdb is pdb
572+
assert mypdb.set_attribute == "bar"
553573
"""
554574
)
555575
p1 = testdir.makepyfile(
@@ -558,11 +578,17 @@ def pytest_configure(config):
558578
559579
def test_foo():
560580
pytest.set_trace()
581+
assert 0
561582
"""
562583
)
563584
child = testdir.spawn_pytest(str(p1))
564585
child.expect("enter_pdb_hook")
565-
child.send("c\n")
586+
child.sendline("c")
587+
child.expect(r"PDB continue \(IO-capturing resumed\)")
588+
child.expect("Captured stdout call")
589+
rest = child.read().decode("utf8")
590+
assert "leave_pdb_hook" in rest
591+
assert "1 failed" in rest
566592
child.sendeof()
567593
self.flush(child)
568594

0 commit comments

Comments
 (0)