diff --git a/changelog/12946.bugfix.rst b/changelog/12946.bugfix.rst new file mode 100644 index 00000000000..1fdb4e7acc6 --- /dev/null +++ b/changelog/12946.bugfix.rst @@ -0,0 +1 @@ +Fixed missing help for :mod:`pdb` commands wrapped by pytest. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 763606cd60e..1778d844f3a 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -159,6 +159,8 @@ def do_debug(self, arg): cls._recursive_debug -= 1 return ret + do_debug.__doc__ = pdb_cls.do_debug.__doc__ + def do_continue(self, arg): ret = super().do_continue(arg) if cls._recursive_debug == 0: @@ -185,15 +187,16 @@ def do_continue(self, arg): self._continued = True return ret + do_continue.__doc__ = pdb_cls.do_continue.__doc__ + do_c = do_cont = do_continue def do_quit(self, arg): - """Raise Exit outcome when quit command is used in pdb. - - This is a bit of a hack - it would be better if BdbQuit - could be handled, but this would require to wrap the - whole pytest run, and adjust the report etc. - """ + # Raise Exit outcome when quit command is used in pdb. + # + # This is a bit of a hack - it would be better if BdbQuit + # could be handled, but this would require to wrap the + # whole pytest run, and adjust the report etc. ret = super().do_quit(arg) if cls._recursive_debug == 0: @@ -201,6 +204,8 @@ def do_quit(self, arg): return ret + do_quit.__doc__ = pdb_cls.do_quit.__doc__ + do_q = do_quit do_exit = do_quit diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 9588da8936f..1af643f860f 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -52,6 +52,16 @@ def reset(self): def interaction(self, *args): called.append("interaction") + # Methods which we copy the docstring over. + def do_debug(self, *args): + pass + + def do_continue(self, *args): + pass + + def do_quit(self, *args): + pass + _pytest._CustomPdb = _CustomPdb # type: ignore return called @@ -75,6 +85,16 @@ def set_trace(self, frame): print("**CustomDebugger**") called.append("set_trace") + # Methods which we copy the docstring over. + def do_debug(self, *args): + pass + + def do_continue(self, *args): + pass + + def do_quit(self, *args): + pass + _pytest._CustomDebugger = _CustomDebugger # type: ignore yield called del _pytest._CustomDebugger # type: ignore @@ -965,6 +985,34 @@ def test_1(): child.sendeof() self.flush(child) + def test_pdb_wrapped_commands_docstrings(self, pytester: Pytester) -> None: + p1 = pytester.makepyfile( + """ + def test_1(): + assert False + """ + ) + + child = pytester.spawn_pytest(f"--pdb {p1}") + child.expect("Pdb") + + # Verify no undocumented commands + child.sendline("help") + child.expect("Documented commands") + assert "Undocumented commands" not in child.before.decode() + + child.sendline("help continue") + child.expect("Continue execution") + child.expect("Pdb") + + child.sendline("help debug") + child.expect("Enter a recursive debugger") + child.expect("Pdb") + + child.sendline("c") + child.sendeof() + self.flush(child) + class TestDebuggingBreakpoints: @pytest.mark.parametrize("arg", ["--pdb", ""]) @@ -1288,6 +1336,16 @@ def set_trace(self, *args): def runcall(self, *args, **kwds): print("runcall_called", args, kwds) + + # Methods which we copy the docstring over. + def do_debug(self, *args): + pass + + def do_continue(self, *args): + pass + + def do_quit(self, *args): + pass """, ) result = pytester.runpytest( @@ -1354,6 +1412,16 @@ def __init__(self, *args, **kwargs): def set_trace(self, *args): print("set_trace_called", args) + + # Methods which we copy the docstring over. + def do_debug(self, *args): + pass + + def do_continue(self, *args): + pass + + def do_quit(self, *args): + pass """, ) result = pytester.runpytest(str(p1), "--pdbcls=mypdb:MyPdb", syspathinsert=True)