diff --git a/changelog/4287.bugfix.rst b/changelog/4287.bugfix.rst new file mode 100644 index 000000000..5ba5fbb7a --- /dev/null +++ b/changelog/4287.bugfix.rst @@ -0,0 +1 @@ +Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 5a9729d5b..0baa84afa 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -47,17 +47,24 @@ def pytest_configure(config): if config.getvalue("usepdb"): config.pluginmanager.register(PdbInvoke(), "pdbinvoke") - old = (pdb.set_trace, pytestPDB._pluginmanager) - - def fin(): - pdb.set_trace, pytestPDB._pluginmanager = old - pytestPDB._config = None - pytestPDB._pdb_cls = pdb.Pdb - + pytestPDB._saved.append( + (pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config, pytestPDB._pdb_cls) + ) pdb.set_trace = pytestPDB.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config pytestPDB._pdb_cls = pdb_cls + + # NOTE: not using pytest_unconfigure, since it might get called although + # pytest_configure was not (if another plugin raises UsageError). + def fin(): + ( + pdb.set_trace, + pytestPDB._pluginmanager, + pytestPDB._config, + pytestPDB._pdb_cls, + ) = pytestPDB._saved.pop() + config._cleanup.append(fin) @@ -67,6 +74,7 @@ class pytestPDB(object): _pluginmanager = None _config = None _pdb_cls = pdb.Pdb + _saved = [] @classmethod def set_trace(cls, set_break=True): diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 56fe5fc7a..ec1af5b56 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -784,3 +784,30 @@ class TestTraceOption: assert "1 passed" in rest assert "reading from stdin while output" not in rest TestPDB.flush(child) + + +def test_trace_after_runpytest(testdir): + """Test that debugging's pytest_configure is re-entrant.""" + p1 = testdir.makepyfile( + """ + from _pytest.debugging import pytestPDB + + def test_outer(testdir): + from _pytest.debugging import pytestPDB + + assert len(pytestPDB._saved) == 1 + + testdir.runpytest("-k test_inner") + + __import__('pdb').set_trace() + + def test_inner(testdir): + assert len(pytestPDB._saved) == 2 + """ + ) + child = testdir.spawn_pytest("-p pytester %s -k test_outer" % p1) + child.expect(r"\(Pdb") + child.sendline("c") + rest = child.read().decode("utf8") + TestPDB.flush(child) + assert child.exitstatus == 0, rest