|
4 | 4 | import contextlib |
5 | 5 | import io |
6 | 6 | import os |
| 7 | +import pdb |
7 | 8 | import shlex |
8 | 9 | import sys |
9 | 10 | import tempfile |
@@ -390,12 +391,55 @@ def should_strip_ansi( |
390 | 391 | old__getchar_func = termui._getchar |
391 | 392 | old_should_strip_ansi = utils.should_strip_ansi # type: ignore |
392 | 393 | old__compat_should_strip_ansi = _compat.should_strip_ansi |
| 394 | + old_pdb_init = pdb.Pdb.__init__ |
393 | 395 | termui.visible_prompt_func = visible_input |
394 | 396 | termui.hidden_prompt_func = hidden_input |
395 | 397 | termui._getchar = _getchar |
396 | 398 | utils.should_strip_ansi = should_strip_ansi # type: ignore |
397 | 399 | _compat.should_strip_ansi = should_strip_ansi |
398 | 400 |
|
| 401 | + def _patched_pdb_init( |
| 402 | + self: pdb.Pdb, |
| 403 | + completekey: str = "tab", |
| 404 | + stdin: t.IO[str] | None = None, |
| 405 | + stdout: t.IO[str] | None = None, |
| 406 | + **kwargs: t.Any, |
| 407 | + ) -> None: |
| 408 | + """Default ``pdb.Pdb`` to real terminal streams during |
| 409 | + ``CliRunner`` isolation. |
| 410 | +
|
| 411 | + Without this patch, ``pdb.Pdb.__init__`` inherits from |
| 412 | + ``cmd.Cmd`` which falls back to ``sys.stdin``/``sys.stdout`` |
| 413 | + when no explicit streams are provided. During isolation |
| 414 | + those are ``BytesIO``-backed wrappers, so the debugger |
| 415 | + reads from an empty buffer and writes to captured output, |
| 416 | + making interactive debugging impossible. |
| 417 | +
|
| 418 | + By defaulting to ``sys.__stdin__``/``sys.__stdout__`` (the |
| 419 | + original terminal streams Python preserves regardless of |
| 420 | + redirection), debuggers can interact with the user while |
| 421 | + ``click.echo`` output is still captured normally. |
| 422 | +
|
| 423 | + This covers ``pdb.set_trace()``, ``breakpoint()``, |
| 424 | + ``pdb.post_mortem()``, and debuggers that subclass |
| 425 | + ``pdb.Pdb`` (ipdb, pdbpp). Explicit ``stdin``/``stdout`` |
| 426 | + arguments are honored and not overridden. Debuggers that |
| 427 | + do not subclass ``pdb.Pdb`` (pudb, debugpy) are not |
| 428 | + covered. |
| 429 | +
|
| 430 | + See: https://github.com/pallets/click/issues/654 and |
| 431 | + https://github.com/pallets/click/issues/824 |
| 432 | + """ |
| 433 | + if stdin is None: |
| 434 | + stdin = sys.__stdin__ |
| 435 | + if stdout is None: |
| 436 | + stdout = sys.__stdout__ |
| 437 | + old_pdb_init( |
| 438 | + self, completekey=completekey, stdin=stdin, stdout=stdout, **kwargs |
| 439 | + ) |
| 440 | + |
| 441 | + pdb.Pdb.__init__ = _patched_pdb_init # type: ignore[assignment] |
| 442 | + |
399 | 443 | old_env = {} |
400 | 444 | try: |
401 | 445 | for key, value in env.items(): |
@@ -426,6 +470,7 @@ def should_strip_ansi( |
426 | 470 | utils.should_strip_ansi = old_should_strip_ansi # type: ignore |
427 | 471 | _compat.should_strip_ansi = old__compat_should_strip_ansi |
428 | 472 | formatting.FORCED_WIDTH = old_forced_width |
| 473 | + pdb.Pdb.__init__ = old_pdb_init # type: ignore[method-assign] |
429 | 474 |
|
430 | 475 | def invoke( |
431 | 476 | self, |
|
0 commit comments