click
click copied to clipboard
click.testing.CliRunner.invoke prevents use of pdb
Will someone please re-open #138. It's still a problem with Click 6.7. In particular, my stack trace ends with:
File "/home/rsyring/projects/agari-adm-src/agariadm/cli.py", line 64, in batch
import pdb; pdb.set_trace()
File "/usr/lib/python3.5/bdb.py", line 52, in trace_dispatch
return self.dispatch_return(frame, arg)
File "/usr/lib/python3.5/bdb.py", line 96, in dispatch_return
if self.quitting: raise BdbQuit
bdb.BdbQuit
And there is explanation about why this is happening in the related issue.
@rsyring Perhaps you've figured this out in the interim, but the workaround that I've come up for this is to mock out click.testing.make_input_stream
and replace the returned value with the original sys.stdin
.
For example:
import sys
from mock import patch
patcher = patch('click.testing.make_input_stream')
m_make_input_stream = patcher.start()
m_make_input_stream.return_value = sys.stdin
# ...
patcher.stop()
Please let me know if you have any more questions.
Since CliRunner
overrides sys.{stdin,stdout}
and pdb depends on them, it's quite expected that this doesn't just work. pytest
, which also does IO redirection, monkey patches the global pdb.set_trace
so that it disables IO redirection when called allowing it work even if IO redirection is enabled. But I think click
generally tries to avoid affecting global state...
You can try this workaround:
import sys
import pdb
import click
from click.testing import CliRunner
stdin, stdout = sys.stdin, sys.stdout
def set_trace():
pdb.Pdb(stdin=stdin, stdout=stdout).set_trace()
@click.command()
def main():
set_trace()
if __name__ == '__main__':
runner = CliRunner()
print(runner.invoke(main))
How click should be handling this issue is left open for discussion.
This might be useful for some:
Click CliRunner with PDB working better under pytest https://gist.github.com/rcoup/2566c92a1c47d66cfb429a6e3cb0cca2
I use this snipped and it works basically always (argsparse, click, sys.argv):
import unittest
import pytest
from thing.__main__ import cli
class TestCli(unittest.TestCase):
@pytest.fixture(autouse=True)
def capsys(self, capsys):
self.capsys = capsys
def test_cli(self):
with pytest.raises(SystemExit) as ex:
cli(["create", "--name", "test"])
self.assertEqual(ex.value.code, 0)
out, err = self.capsys.readouterr()
self.assertEqual(out, "Succesfully created test\n")
I tried out a few samples and workarounds from here. The CliRunner
replaces the stdin
and stdout
with _NamedTextIOWrapper
on top of EchoingStdin
and io.BytesIO
objects respectively. The return value of make_input_stream
and io.BytesIO
is simply not compatible with pdb
which uses sys.stdin
and sys.stdout
.
I went through pdb
source code as well and from what I can tell, BdbQuit
is raised when the debugger is quit and the program is aborted though there could be some other places as well that raise it. I don't think it has anything to do with the exception, just that which stream is being used to output it.
In my opinion, it will be better to leave this as it is until there is a better way to mock sys.stdin
and sys.stdout
inside CliRunner
. One way could be to somehow detect the usage of pdb
inside CliRunner
and accordingly mock the streams. Perhaps an extra flag could serve this purpose. Or mock pdb
itself like pytest
does as mentioned by @segevfiner
I had the same error, calling PDB from pytest using pytest --pdb
, with import pytest; pytest.set_trace()
in my code instead of import pdb; pdb.set_trace()
fixed it for me.
Still got the same issue BdbQuit
with Click 7.1.2, none of the workarounds work for me