diff --git a/changelog/2064.bugfix.rst b/changelog/2064.bugfix.rst new file mode 100644 index 000000000..eba593fc5 --- /dev/null +++ b/changelog/2064.bugfix.rst @@ -0,0 +1 @@ +The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 3605490e6..8912477db 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -49,42 +49,18 @@ def pytest_addoption(parser): ) -def _import_pdbcls(modname, classname): - try: - __import__(modname) - mod = sys.modules[modname] - - # Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp). - parts = classname.split(".") - pdb_cls = getattr(mod, parts[0]) - for part in parts[1:]: - pdb_cls = getattr(pdb_cls, part) - - return pdb_cls - except Exception as exc: - value = ":".join((modname, classname)) - raise UsageError("--pdbcls: could not import {!r}: {}".format(value, exc)) - - def pytest_configure(config): - pdb_cls = config.getvalue("usepdb_cls") - if pdb_cls: - pdb_cls = _import_pdbcls(*pdb_cls) - else: - pdb_cls = pdb.Pdb - if config.getvalue("trace"): config.pluginmanager.register(PdbTrace(), "pdbtrace") if config.getvalue("usepdb"): config.pluginmanager.register(PdbInvoke(), "pdbinvoke") pytestPDB._saved.append( - (pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config, pytestPDB._pdb_cls) + (pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config) ) 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). @@ -93,7 +69,6 @@ def pytest_configure(config): pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config, - pytestPDB._pdb_cls, ) = pytestPDB._saved.pop() config._cleanup.append(fin) @@ -104,7 +79,6 @@ class pytestPDB(object): _pluginmanager = None _config = None - _pdb_cls = pdb.Pdb _saved = [] _recursive_debug = 0 @@ -114,6 +88,33 @@ class pytestPDB(object): return capman.is_capturing() return False + @classmethod + def _import_pdb_cls(cls): + if not cls._config: + # Happens when using pytest.set_trace outside of a test. + return pdb.Pdb + + pdb_cls = cls._config.getvalue("usepdb_cls") + if not pdb_cls: + return pdb.Pdb + + modname, classname = pdb_cls + + try: + __import__(modname) + mod = sys.modules[modname] + + # Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp). + parts = classname.split(".") + pdb_cls = getattr(mod, parts[0]) + for part in parts[1:]: + pdb_cls = getattr(pdb_cls, part) + + return pdb_cls + except Exception as exc: + value = ":".join((modname, classname)) + raise UsageError("--pdbcls: could not import {!r}: {}".format(value, exc)) + @classmethod def _init_pdb(cls, *args, **kwargs): """ Initialize PDB debugging, dropping any IO capturing. """ @@ -144,7 +145,9 @@ class pytestPDB(object): else: tw.sep(">", "PDB set_trace") - class PytestPdbWrapper(cls._pdb_cls, object): + pdb_cls = cls._import_pdb_cls() + + class PytestPdbWrapper(pdb_cls, object): _pytest_capman = capman _continued = False @@ -227,7 +230,8 @@ class pytestPDB(object): _pdb = PytestPdbWrapper(**kwargs) cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb) else: - _pdb = cls._pdb_cls(**kwargs) + pdb_cls = cls._import_pdb_cls() + _pdb = pdb_cls(**kwargs) return _pdb @classmethod diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 4b084a26a..007423a9e 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -1157,12 +1157,13 @@ def test_pdbcls_via_local_module(testdir): result = testdir.runpytest( str(p1), "--pdbcls=really.invalid:Value", syspathinsert=True ) - result.stderr.fnmatch_lines( + result.stdout.fnmatch_lines( [ - "ERROR: --pdbcls: could not import 'really.invalid:Value': No module named *really*" + "*= FAILURES =*", + "E * --pdbcls: could not import 'really.invalid:Value': No module named *really*", ] ) - assert result.ret == 4 + assert result.ret == 1 result = testdir.runpytest( str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True