Make debugging's pytest_configure re-entrant

This is relevant when using runpytest in-process.

Fixes:

E             def test_1(testdir):
E                 testdir.runpytest()
E         >       __import__('pdb').set_trace()
E
E         ../../test_trace_after_runpytest.py:3:
E         …/Vcs/pytest/src/_pytest/debugging.py:81: in set_trace
E             tw = _pytest.config.create_terminal_writer(cls._config)
E
E         config = None, args = (), kwargs = {}, tw = <py._io.terminalwriter.TerminalWriter object at 0x7f1097088160>
E
E             def create_terminal_writer(config, *args, **kwargs):
E                 """Create a TerminalWriter instance configured according to the options
E                 in the config object. Every code which requires a TerminalWriter object
E                 and has access to a config object should use this function.
E                 """
E                 tw = py.io.TerminalWriter(*args, **kwargs)
E         >       if config.option.color == "yes":
E         E       AttributeError: 'NoneType' object has no attribute 'option'
This commit is contained in:
Daniel Hahler 2018-11-01 19:40:38 +01:00
parent 21725e9304
commit e61e81a7b5
3 changed files with 43 additions and 7 deletions

View File

@ -0,0 +1 @@
Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``.

View File

@ -47,17 +47,24 @@ def pytest_configure(config):
if config.getvalue("usepdb"): if config.getvalue("usepdb"):
config.pluginmanager.register(PdbInvoke(), "pdbinvoke") config.pluginmanager.register(PdbInvoke(), "pdbinvoke")
old = (pdb.set_trace, pytestPDB._pluginmanager) pytestPDB._saved.append(
(pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config, pytestPDB._pdb_cls)
def fin(): )
pdb.set_trace, pytestPDB._pluginmanager = old
pytestPDB._config = None
pytestPDB._pdb_cls = pdb.Pdb
pdb.set_trace = pytestPDB.set_trace pdb.set_trace = pytestPDB.set_trace
pytestPDB._pluginmanager = config.pluginmanager pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config pytestPDB._config = config
pytestPDB._pdb_cls = pdb_cls 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) config._cleanup.append(fin)
@ -67,6 +74,7 @@ class pytestPDB(object):
_pluginmanager = None _pluginmanager = None
_config = None _config = None
_pdb_cls = pdb.Pdb _pdb_cls = pdb.Pdb
_saved = []
@classmethod @classmethod
def set_trace(cls, set_break=True): def set_trace(cls, set_break=True):

View File

@ -784,3 +784,30 @@ class TestTraceOption:
assert "1 passed" in rest assert "1 passed" in rest
assert "reading from stdin while output" not in rest assert "reading from stdin while output" not in rest
TestPDB.flush(child) 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