summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Hahler <git@thequod.de>2017-07-26 17:18:29 +0200
committerDaniel Hahler <git@thequod.de>2018-10-25 13:28:24 +0200
commita4ea66cb1fcbb8a7723c764d67a817a0afdee36d (patch)
tree00656f0edf09b9a92bd7b2d16e5c06915806139a
parent65b97c2f41315de1b39fe9f9595727ec3b327077 (diff)
downloadpytest-a4ea66cb1fcbb8a7723c764d67a817a0afdee36d.tar.gz
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
-rw-r--r--changelog/2619.feature.rst1
-rw-r--r--src/_pytest/debugging.py38
-rw-r--r--src/_pytest/hookspec.py10
-rw-r--r--testing/test_pdb.py28
4 files changed, 70 insertions, 7 deletions
diff --git a/changelog/2619.feature.rst b/changelog/2619.feature.rst
new file mode 100644
index 000000000..df8137a66
--- /dev/null
+++ b/changelog/2619.feature.rst
@@ -0,0 +1 @@
+Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``.
diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py
index cc9bf5c2a..da35688b9 100644
--- a/src/_pytest/debugging.py
+++ b/src/_pytest/debugging.py
@@ -96,8 +96,44 @@ class pytestPDB(object):
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):
diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py
index 533806964..ae289f0a3 100644
--- a/src/_pytest/hookspec.py
+++ b/src/_pytest/hookspec.py
@@ -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
+ """
diff --git a/testing/test_pdb.py b/testing/test_pdb.py
index 57a6cb9a3..19f95959c 100644
--- a/testing/test_pdb.py
+++ b/testing/test_pdb.py
@@ -158,6 +158,7 @@ class TestPDB(object):
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
@@ -489,18 +490,23 @@ class TestPDB(object):
"""
)
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):
@@ -541,15 +547,19 @@ class TestPDB(object):
["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(
@@ -558,11 +568,17 @@ class TestPDB(object):
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)