Skip to content

Commit

Permalink
pdb: resume capturing after continue
Browse files Browse the repository at this point in the history
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
  • Loading branch information
blueyed committed Oct 23, 2018
1 parent f30911d commit c1d05f3
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 7 deletions.
1 change: 1 addition & 0 deletions changelog/2619.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``.
38 changes: 37 additions & 1 deletion src/_pytest/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,44 @@ def set_trace(cls, set_break=True):
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)

class _PdbWrapper(cls._pdb_cls, object):
_pytest_capman = capman
_continued = False

def do_continue(self, arg):
ret = super(_PdbWrapper, self).do_continue(arg)
if self._pytest_capman:
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
tw.sep(">", "PDB continue (IO-capturing resumed)")
self._pytest_capman.resume_global_capture()
cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config)
self._continued = True
return ret

do_c = do_cont = do_continue

def setup(self, f, tb):
"""Suspend on setup().
Needed after do_continue resumed, and entering another
breakpoint again.
"""
ret = super(_PdbWrapper, self).setup(f, tb)
if not ret and self._continued:
# pdb.setup() returns True if the command wants to exit
# from the interaction: do not suspend capturing then.
if self._pytest_capman:
self._pytest_capman.suspend_global_capture(in_=True)
return ret

_pdb = _PdbWrapper()
else:
_pdb = cls._pdb_cls()

if set_break:
cls._pdb_cls().set_trace(frame)
_pdb.set_trace(frame)


class PdbInvoke(object):
Expand Down
10 changes: 10 additions & 0 deletions src/_pytest/hookspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,3 +609,13 @@ def pytest_enter_pdb(config):
:param _pytest.config.Config config: pytest config object
"""


def pytest_leave_pdb(config):
""" called when leaving pdb (e.g. with continue after pdb.set_trace()).
Can be used by plugins to take special action just after the python
debugger leaves interactive mode.
:param _pytest.config.Config config: pytest config object
"""
28 changes: 22 additions & 6 deletions testing/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def test_not_called_due_to_quit():
assert "= 1 failed in" in rest
assert "def test_1" not in rest
assert "Exit: Quitting debugger" in rest
assert "PDB continue (IO-capturing resumed)" not in rest
self.flush(child)

@staticmethod
Expand Down Expand Up @@ -489,18 +490,23 @@ def test_1():
"""
)
child = testdir.spawn_pytest(str(p1))
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
child.expect("test_1")
child.expect("x = 3")
child.expect("Pdb")
child.sendline("c")
child.expect(r"PDB continue \(IO-capturing resumed\)")
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
child.expect("x = 4")
child.expect("Pdb")
child.sendeof()
child.expect("_ test_1 _")
child.expect("def test_1")
child.expect("Captured stdout call")
rest = child.read().decode("utf8")
assert "1 failed" in rest
assert "def test_1" in rest
assert "hello17" in rest # out is captured
assert "hello18" in rest # out is captured
assert "1 failed" in rest
self.flush(child)

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

def test_enter_pdb_hook_is_called(self, testdir):
def test_enter_leave_pdb_hooks_are_called(self, testdir):
testdir.makeconftest(
"""
def pytest_configure(config):
config.testing_verification = 'configured'
def pytest_enter_pdb(config):
assert config.testing_verification == 'configured'
print('enter_pdb_hook')
def pytest_configure(config):
config.testing_verification = 'configured'
def pytest_leave_pdb(config):
assert config.testing_verification == 'configured'
print('leave_pdb_hook')
"""
)
p1 = testdir.makepyfile(
Expand All @@ -558,11 +568,17 @@ def pytest_configure(config):
def test_foo():
pytest.set_trace()
assert 0
"""
)
child = testdir.spawn_pytest(str(p1))
child.expect("enter_pdb_hook")
child.send("c\n")
child.sendline("c")
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
child.sendeof()
self.flush(child)

Expand Down

0 comments on commit c1d05f3

Please sign in to comment.