From py.test help:
--pdb start the interactive Python debugger on errors.
--pdbcls=modulename:classname
start a custom interactive Python debugger on errors. For example:
--pdbcls=IPython.terminal.debugger:TerminalPdb
I created a simple test file containing just
def test_pdb():
assert 0
And ran py.test as follows:
$ py.test test_pdb.py \
--pdbcls=IPython.terminal.debugger:TerminalPdb \
--pdb
The result was an internal error with the following traceback:
Traceback (most recent call last):
File ".../_pytest/main.py", line 96, in wrap_session
session.exitstatus = doit(config, session) or 0
...
File ".../IPython/terminal/debugger.py", line 38, in pt_init
self.pt_cli = CommandLineInterface(self._pt_app, eventloop=self.shell._eventloop)
AttributeError: 'TerminalInteractiveShell' object has no attribute '_eventloop'
$ pip freeze
appnope==0.1.0
decorator==4.0.10
entrypoints==0.2.2
ipykernel==4.5.0
ipython==5.1.0
ipython-genutils==0.1.0
ipywidgets==5.2.2
Jinja2==2.8
jsonschema==2.5.1
jupyter==1.0.0
jupyter-client==4.4.0
jupyter-console==5.0.0
jupyter-core==4.2.0
MarkupSafe==0.23
mistune==0.7.3
nbconvert==4.2.0
nbformat==4.1.0
notebook==4.2.3
pexpect==4.2.1
pickleshare==0.7.4
pluggy==0.4.0
prompt-toolkit==1.0.9
ptyprocess==0.5.1
py==1.4.31
Pygments==2.1.3
pytest==3.0.4
pyzmq==16.0.1
qtconsole==4.2.1
simplegeneric==0.8.1
six==1.10.0
terminado==0.6
tornado==4.4.2
traitlets==4.3.1
wcwidth==0.1.7
widgetsnbextension==1.2.6
I am using pytest 3.0.4 and
$ python
Python 3.5.2+ (3.5:da2ac103d326+, Nov 15 2016, 14:14:18)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
pip list of the virtual environment you are usingThanks for the report!
A similar issue has been reported for django-nose. See django-nose/django-nose#274. It was resolved there with a recommendation to use the "no capture" mode. This worked for py.test as well:
$ py.test test_pdb.py --pdbcls=IPython.terminal.debugger:TerminalPdb --pdb -s
=================================================== test session starts ====================================================
platform darwin -- Python 3.5.2+, pytest-3.0.5.dev0, py-1.4.31, pluggy-0.4.0
rootdir: /Users/a/Work/abalkin/pytest, inifile: tox.ini
plugins: leaks-0.1.0
collected 1 items
test_pdb.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def test_pdb():
> assert 0
E assert 0
test_pdb.py:2: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /Users/a/Work/abalkin/pytest/test_pdb.py(2)test_pdb()
1 def test_pdb():
----> 2 assert 0
ipdb>
While this is a decent work-around, I still believe raising an AttributeError is not acceptable.
Trying this with IPython 5.4.1 and pytest 3.1.3, I'm getting this beauty:
Traceback (most recent call last):
File "Lib\runpy.py", line 174, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "Lib\runpy.py", line 72, in _run_code
exec code in run_globals
File "Scripts\pytest.exe\__main__.py", line 9, in <module>
File "lib\site-packages\_pytest\config.py", line 58, in main
return config.hook.pytest_cmdline_main(config=config)
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 745, in __call__
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 339, in _hookexec
return self._inner_hookexec(hook, methods, kwargs)
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 334, in <lambda>
_MultiCall(methods, kwargs, hook.spec_opts).execute()
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 614, in execute
res = hook_impl.function(*args)
File "lib\site-packages\_pytest\main.py", line 134, in pytest_cmdline_main
return wrap_session(config, _main)
File "lib\site-packages\_pytest\main.py", line 117, in wrap_session
config.notify_exception(excinfo, config.option)
File "lib\site-packages\_pytest\config.py", line 947, in notify_exception
excinfo=excinfo)
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 745, in __call__
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 339, in _hookexec
return self._inner_hookexec(hook, methods, kwargs)
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 334, in <lambda>
_MultiCall(methods, kwargs, hook.spec_opts).execute()
File "lib\site-packages\_pytest\vendored_packages\pluggy.py", line 614, in execute
res = hook_impl.function(*args)
File "lib\site-packages\_pytest\debugging.py", line 79, in pytest_internalerror
post_mortem(tb)
File "lib\site-packages\_pytest\debugging.py", line 121, in post_mortem
p = Pdb()
File "lib\site-packages\IPython\terminal\debugger.py", line 26, in __init__
self.pt_init()
File "lib\site-packages\IPython\terminal\debugger.py", line 67, in pt_init
style=self.shell.style
AttributeError: 'TerminalInteractiveShell' object has no attribute 'style'
This is caused by using TerminalPdb with simple_prompt which is auto detected here: IPython/terminal/interactiveshell.py:76-86 and here IPython/terminal/interactiveshell.py:113-115.
Obviously IPython's debuggers "API" is quite poor in this regard... ipdb handled this like this ipdb/__main__.py:25-35. Using simple_prompt is not desirable anyhow, it's really only meant for testing or for programs that wrap IPython.
From pytest's perspective we probably want to perform the import of the debugger class without IO redirection so that IPython correctly detects the tty (That assumes that the relevant internal IPython module gets imported when doing the import of the debugger class which smells like it can break really easily). We can also submit a patch to IPython to move that check to the class itself so it doesn't happen at import time, might be possible using this Traitlets - Dynamic default values. That way the check can happen when we first create the Pdb class with IO redirection off.
P.S. I guess there is no way to set a default pdbcls user-wide/system-wide... There is also no handling of calls to ipdb.set_trace (ipdb). All this nonsense can be handled by a plugin obviously, but the existing plugin is unmaintained and I don't think it supports IPython 5+ properly.
I am also getting AttributeError: 'TerminalInteractiveShell' object has no attribute 'style'.
My workaround is to add --capture=no to my pytest.ini configuration file:
[pytest]
addopts =
--pdbcls=IPython.terminal.debugger:TerminalPdb
--capture=no
Please post full stack traces..
In https://github.com/pytest-dev/pytest/issues/2064#issuecomment-318857657 it goes through post_mortem which might be not working correctly, as with e.g. using pdb.set_trace() directly.
There have been some fixes (e.g. for --trace), so is this still an issue?
520af9d76 might be related.
--pdbcls ipdb:__main__.debugger_cls works (see https://github.com/pytest-dev/pytest/issues/5039#issuecomment-479604656).
--pdbcls=IPython.terminal.debugger:TerminalPdb crashes:
def test_foo():
print(1)
> __import__('pdb').set_trace()
t-pdb.py:7:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class '_pytest.debugging.pytestPDB'>, args = (), kwargs = {}
frame = <frame at 0x7f9209c6cb70, file '…/Vcs/pytest/t-pdb.py', line 7, code test_foo>
@classmethod
def set_trace(cls, *args, **kwargs):
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
frame = sys._getframe().f_back
> _pdb = cls._init_pdb(*args, **kwargs)
args = ()
cls = <class '_pytest.debugging.pytestPDB'>
frame = <frame at 0x7f9209c6cb70, file '…/Vcs/pytest/t-pdb.py', line 7, code test_foo>
kwargs = {}
src/_pytest/debugging.py:225:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class '_pytest.debugging.pytestPDB'>, args = (), kwargs = {}
tw = <py._io.terminalwriter.TerminalWriter object at 0x7f9209bf8710>, header = None
capturing = 'global'
@classmethod
def _init_pdb(cls, *args, **kwargs):
""" Initialize PDB debugging, dropping any IO capturing. """
import _pytest.config
if cls._pluginmanager is not None:
…
> _pdb = PytestPdbWrapper(**kwargs)
PytestPdbWrapper = <class '_pytest.debugging.pytestPDB._init_pdb.<locals>.PytestPdbWrapper'>
_pytest = <module '_pytest' from '…/Vcs/pytest/src/_pytest/__init__.py'>
args = ()
capman = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=5 _state='suspended'> err=<FDCaptu... in_=<FDCapture 0 oldfd=3 _state='suspended'> _state='suspended' _in_suspended=True> _current_item=<Function test_foo>>
capturing = 'global'
cls = <class '_pytest.debugging.pytestPDB'>
header = None
kwargs = {}
tw = <py._io.terminalwriter.TerminalWriter object at 0x7f9209bf8710>
src/_pytest/debugging.py:215:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <_pytest.debugging.pytestPDB._init_pdb.<locals>.PytestPdbWrapper object at 0x7f9209bf8860>
args = (), kwargs = {}
def __init__(self, *args, **kwargs):
Pdb.__init__(self, *args, **kwargs)
self._ptcomp = None
> self.pt_init()
args = ()
kwargs = {}
self = <_pytest.debugging.pytestPDB._init_pdb.<locals>.PytestPdbWrapper object at 0x7f9209bf8860>
.venv/lib/python3.7/site-packages/IPython/terminal/debugger.py:25:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <_pytest.debugging.pytestPDB._init_pdb.<locals>.PytestPdbWrapper object at 0x7f9209bf8860>
def pt_init(self):
def get_prompt_tokens():
return [(Token.Prompt, self.prompt)]
if self._ptcomp is None:
compl = IPCompleter(shell=self.shell,
namespace={},
global_namespace={},
parent=self.shell,
)
self._ptcomp = IPythonPTCompleter(compl)
kb = KeyBindings()
supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
if self.shell.display_completions == 'readlinelike':
kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
& ~has_selection
& vi_insert_mode | emacs_insert_mode
& ~cursor_in_leading_ws
))(display_completions_like_readline)
self.pt_app = PromptSession(
message=(lambda: PygmentsTokens(get_prompt_tokens())),
editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
key_bindings=kb,
history=self.shell.debugger_history,
completer=self._ptcomp,
enable_history_search=True,
mouse_support=self.shell.mouse_support,
complete_style=self.shell.pt_complete_style,
> style=self.shell.style,
inputhook=self.shell.inputhook,
color_depth=self.shell.color_depth,
)
E AttributeError: 'TerminalInteractiveShell' object has no attribute 'style'
compl = <IPython.core.completer.IPCompleter object at 0x7f9209396e10>
get_prompt_tokens = <function TerminalPdb.pt_init.<locals>.get_prompt_tokens at 0x7f920938f378>
kb = <prompt_toolkit.key_binding.key_bindings.KeyBindings object at 0x7f9209396ef0>
self = <_pytest.debugging.pytestPDB._init_pdb.<locals>.PytestPdbWrapper object at 0x7f9209bf8860>
supports_suspend = Condition(<function TerminalPdb.pt_init.<locals>.<lambda> at 0x7f920938f2f0>)
.venv/lib/python3.7/site-packages/IPython/terminal/debugger.py:59: AttributeError
Just use the former I guess.. :)
Actually at least the help should be fixed then - wasn't aware that it's from there.
And then also investigate if it worked before, what broke it, and have a test for it.
Created https://github.com/ipython/ipython/issues/11745.
@segevfiner
Also thought about disabling capturing temporarily, or importing it only later altogether.
I have a plan for this, but it requires https://github.com/pytest-dev/pytest/pull/4908 to get merged first.
https://github.com/pytest-dev/pytest/pull/5307 should fix this, if you want to give it a try / review it.
Most helpful comment
A similar issue has been reported for django-nose. See django-nose/django-nose#274. It was resolved there with a recommendation to use the "no capture" mode. This worked for py.test as well:
While this is a decent work-around, I still believe raising an AttributeError is not acceptable.