diff --git a/AUTHORS b/AUTHORS index fd543b145..67111393c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,7 @@ Anatoly Bubenkoff Andreas Zeidler Andy Freeland Anthon van der Neut +Antony Lee Armin Rigo Aron Curzon Aviv Palivoda diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c7b7982de..786b44c29 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -117,7 +117,8 @@ Example '-o xfail_strict=True'. A complete ini-options can be viewed by py.test --help. Thanks `@blueyed`_ and `@fengxx`_ for the PR -* +* Allow passing a custom debugger class (e.g. ``IPython.core.debugger:Pdb`` + via ``--pdbcls``). Thanks to `@anntzer`_ for the PR. * @@ -270,6 +271,7 @@ .. _@DRMacIver: https://github.com/DRMacIver .. _@RedBeardCode: https://github.com/RedBeardCode .. _@Vogtinator: https://github.com/Vogtinator +.. _@anntzer: https://github.com/anntzer .. _@bagerard: https://github.com/bagerard .. _@blueyed: https://github.com/blueyed .. _@ceridwen: https://github.com/ceridwen diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 84c920d17..d8cd5a4e3 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -8,21 +8,33 @@ import pytest def pytest_addoption(parser): group = parser.getgroup("general") - group._addoption('--pdb', - action="store_true", dest="usepdb", default=False, - help="start the interactive Python debugger on errors.") + group._addoption( + '--pdb', dest="usepdb", action="store_true", + help="start the interactive Python debugger on errors.") + group._addoption( + '--pdbcls', dest="usepdb_cls", metavar="modulename:classname", + help="start a custom interactive Python debugger on errors. " + "For example: --pdbcls=IPython.core.debugger:Pdb") def pytest_namespace(): return {'set_trace': pytestPDB().set_trace} def pytest_configure(config): - if config.getvalue("usepdb"): + if config.getvalue("usepdb") or config.getvalue("usepdb_cls"): config.pluginmanager.register(PdbInvoke(), 'pdbinvoke') + if config.getvalue("usepdb_cls"): + modname, classname = config.getvalue("usepdb_cls").split(":") + __import__(modname) + pdb_cls = getattr(sys.modules[modname], classname) + else: + pdb_cls = pdb.Pdb + pytestPDB._pdb_cls = pdb_cls old = (pdb.set_trace, pytestPDB._pluginmanager) def fin(): pdb.set_trace, pytestPDB._pluginmanager = old pytestPDB._config = None + pytestPDB._pdb_cls = pdb.Pdb pdb.set_trace = pytest.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config @@ -32,6 +44,7 @@ class pytestPDB: """ Pseudo PDB that defers to the real pdb. """ _pluginmanager = None _config = None + _pdb_cls = pdb.Pdb def set_trace(self): """ invoke PDB set_trace debugging, dropping any IO capturing. """ @@ -45,7 +58,7 @@ class pytestPDB: tw.line() tw.sep(">", "PDB set_trace (IO-capturing turned off)") self._pluginmanager.hook.pytest_enter_pdb(config=self._config) - pdb.Pdb().set_trace(frame) + self._pdb_cls().set_trace(frame) class PdbInvoke: @@ -98,7 +111,7 @@ def _find_last_non_hidden_frame(stack): def post_mortem(t): - class Pdb(pdb.Pdb): + class Pdb(pytestPDB._pdb_cls): def get_stack(self, f, t): stack, i = pdb.Pdb.get_stack(self, f, t) if f is None: diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 14c1fe8b8..6e4f3e805 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -314,3 +314,28 @@ class TestPDB: child.sendeof() if child.isalive(): child.wait() + + def test_pdb_custom_cls(self, testdir): + called = [] + + # install dummy debugger class and track which methods were called on it + class _CustomPdb: + def __init__(self, *args, **kwargs): + called.append("init") + + def reset(self): + called.append("reset") + + def interaction(self, *args): + called.append("interaction") + + _pytest._CustomPdb = _CustomPdb + + p1 = testdir.makepyfile("""xxx """) + result = testdir.runpytest_inprocess( + "--pdbcls=_pytest:_CustomPdb", p1) + result.stdout.fnmatch_lines([ + "*NameError*xxx*", + "*1 error*", + ]) + assert called == ["init", "reset", "interaction"]