diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index d696161876e..ce7516a52b1 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -159,12 +159,15 @@ slightly different way: is entered. -.. function:: set_trace(*, header=None) +.. function:: set_trace(*, header=None, commands=None) Enter the debugger at the calling stack frame. This is useful to hard-code a breakpoint at a given point in a program, even if the code is not otherwise being debugged (e.g. when an assertion fails). If given, *header* is printed to the console just before debugging begins. + The *commands* argument, if given, is a list of commands to execute + when the debugger starts. + .. versionchanged:: 3.7 The keyword-only argument *header*. @@ -173,6 +176,9 @@ slightly different way: :func:`set_trace` will enter the debugger immediately, rather than on the next line of code to be executed. + .. versionadded:: 3.14 + The *commands* argument. + .. function:: post_mortem(traceback=None) Enter post-mortem debugging of the given *traceback* object. If no diff --git a/Lib/doctest.py b/Lib/doctest.py index ea7d275c91d..bb281fc483c 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -389,11 +389,11 @@ class _OutputRedirectingPdb(pdb.Pdb): # still use input() to get user input self.use_rawinput = 1 - def set_trace(self, frame=None): + def set_trace(self, frame=None, *, commands=None): self.__debugger_used = True if frame is None: frame = sys._getframe().f_back - pdb.Pdb.set_trace(self, frame) + pdb.Pdb.set_trace(self, frame, commands=commands) def set_continue(self): # Calling set_continue unconditionally would break unit test diff --git a/Lib/pdb.py b/Lib/pdb.py index 228de489a9c..dd21207a627 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -361,10 +361,14 @@ class Pdb(bdb.Bdb, cmd.Cmd): self._chained_exceptions = tuple() self._chained_exception_index = 0 - def set_trace(self, frame=None): + def set_trace(self, frame=None, *, commands=None): Pdb._last_pdb_instance = self if frame is None: frame = sys._getframe().f_back + + if commands is not None: + self.rcLines.extend(commands) + super().set_trace(frame) def sigint_handler(self, signum, frame): @@ -2350,13 +2354,14 @@ def runcall(*args, **kwds): """ return Pdb().runcall(*args, **kwds) -def set_trace(*, header=None): +def set_trace(*, header=None, commands=None): """Enter the debugger at the calling stack frame. This is useful to hard-code a breakpoint at a given point in a program, even if the code is not otherwise being debugged (e.g. when an assertion fails). If given, *header* is printed to the console - just before debugging begins. + just before debugging begins. *commands* is an optional list of + pdb commands to run when the debugger starts. """ if Pdb._last_pdb_instance is not None: pdb = Pdb._last_pdb_instance @@ -2364,7 +2369,7 @@ def set_trace(*, header=None): pdb = Pdb() if header is not None: pdb.message(header) - pdb.set_trace(sys._getframe().f_back) + pdb.set_trace(sys._getframe().f_back, commands=commands) # Post-Mortem interface diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index db7d1b1e9cd..3173b0553c2 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -901,6 +901,17 @@ def test_pdb_where_command(): (Pdb) continue """ +def test_pdb_commands_with_set_trace(): + """Test that commands can be passed to Pdb.set_trace() + + >>> def test_function(): + ... x = 1 + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace(commands=['p x', 'c']) + + >>> test_function() + 1 + """ + # skip this test if sys.flags.no_site = True; # exit() isn't defined unless there's a site module. diff --git a/Misc/NEWS.d/next/Library/2024-06-08-03-29-01.gh-issue-120254.h682ke.rst b/Misc/NEWS.d/next/Library/2024-06-08-03-29-01.gh-issue-120254.h682ke.rst new file mode 100644 index 00000000000..33ef1c91591 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-08-03-29-01.gh-issue-120254.h682ke.rst @@ -0,0 +1 @@ +Added ``commands`` argument to :func:`pdb.set_trace` which allows users to send debugger commands from the source file.