Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-119824: Print stack entry when user input is needed #119882

Merged
merged 9 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Doc/library/pdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,17 @@ can be overridden by the local file.
argument must be an identifier, ``help exec`` must be entered to get help on
the ``!`` command.

.. pdbcommand:: w(here)
.. pdbcommand:: w(here) [count]

Print a stack trace, with the most recent frame at the bottom. An arrow (``>``)
Print a stack trace, with the most recent frame at the bottom. if *count*
is 0, print the current frame entry. If *count* is negative, print the least
recent - *count* frames. If *count* is positive, print the most recent
*count* frames. An arrow (``>``)
indicates the current frame, which determines the context of most commands.
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved

.. versionchanged:: 3.14
*count* argument is added.

.. pdbcommand:: d(own) [count]

Move the current frame *count* (default one) levels down in the stack trace
Expand Down
50 changes: 39 additions & 11 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,10 +603,18 @@ def interaction(self, frame, tb_or_exc):
assert tb is not None, "main exception must have a traceback"
with self._hold_exceptions(_chained_exceptions):
self.setup(frame, tb)
# if we have more commands to process, do not show the stack entry
if not self.cmdqueue:
# We should print the stack entry if and only if the user input
# is expected, and we should print it right before the user input.
# If self.cmdqueue is not empty, we append a "w 0" command to the
# queue, which is equivalent to print_stack_entry
if self.cmdqueue:
self.cmdqueue.append('w 0')
else:
self.print_stack_entry(self.stack[self.curindex])
self._cmdloop()
# If "w 0" is not used, pop it out
if self.cmdqueue and self.cmdqueue[-1] == 'w 0':
self.cmdqueue.pop()
self.forget()

def displayhook(self, obj):
Expand Down Expand Up @@ -1401,16 +1409,24 @@ def do_clear(self, arg):
complete_cl = _complete_location

def do_where(self, arg):
"""w(here)
"""w(here) [count]

Print a stack trace, with the most recent frame at the bottom.
Print a stack trace. If count is not specified, print the full stack.
If count is 0, print the current frame entry. If count is positive,
print count entries from the most recent frame. If count is negative,
print -count entries from the least recent frame.
An arrow indicates the "current frame", which determines the
context of most commands. 'bt' is an alias for this command.
"""
if arg:
self._print_invalid_arg(arg)
return
self.print_stack_trace()
if not arg:
count = None
else:
try:
count = int(arg)
except ValueError:
self.error('Invalid count (%s)' % arg)
return
self.print_stack_trace(count)
do_w = do_where
do_bt = do_where

Expand Down Expand Up @@ -2065,10 +2081,22 @@ def complete_unalias(self, text, line, begidx, endidx):
# It is also consistent with the up/down commands (which are
# compatible with dbx and gdb: up moves towards 'main()'
# and down moves towards the most recent stack frame).

def print_stack_trace(self):
# * if count is None, prints the full stack
# * if count = 0, prints the current frame entry
# * if count < 0, prints -count least recent frame entries
# * if count > 0, prints count most recent frame entries

def print_stack_trace(self, count=None):
if count is None:
stack_to_print = self.stack
elif count == 0:
stack_to_print = [self.stack[self.curindex]]
elif count < 0:
stack_to_print = self.stack[:-count]
else:
stack_to_print = self.stack[-count:]
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved
try:
for frame_lineno in self.stack:
for frame_lineno in stack_to_print:
self.print_stack_entry(frame_lineno)
except KeyboardInterrupt:
pass
Expand Down
50 changes: 42 additions & 8 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,52 +781,85 @@ def test_pdb_where_command():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()

>>> def f():
... g();
... g()

>>> def test_function():
... f()

>>> with PdbTestInput([ # doctest: +ELLIPSIS
... 'w',
... 'where',
... 'w 1',
... 'w invalid',
... 'u',
... 'w',
... 'w 0',
... 'w 100',
... 'w -100',
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_where_command[0]>(2)g()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) w
...
<doctest test.test_pdb.test_pdb_where_command[3]>(8)<module>()
<doctest test.test_pdb.test_pdb_where_command[3]>(13)<module>()
-> test_function()
<doctest test.test_pdb.test_pdb_where_command[2]>(2)test_function()
-> f()
<doctest test.test_pdb.test_pdb_where_command[1]>(2)f()
-> g();
-> g()
> <doctest test.test_pdb.test_pdb_where_command[0]>(2)g()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) where
...
<doctest test.test_pdb.test_pdb_where_command[3]>(8)<module>()
<doctest test.test_pdb.test_pdb_where_command[3]>(13)<module>()
-> test_function()
<doctest test.test_pdb.test_pdb_where_command[2]>(2)test_function()
-> f()
<doctest test.test_pdb.test_pdb_where_command[1]>(2)f()
-> g();
-> g()
> <doctest test.test_pdb.test_pdb_where_command[0]>(2)g()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) w 1
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved
> <doctest test.test_pdb.test_pdb_where_command[0]>(2)g()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) w invalid
*** Invalid count (invalid)
(Pdb) u
> <doctest test.test_pdb.test_pdb_where_command[1]>(2)f()
-> g();
-> g()
(Pdb) w
...
<doctest test.test_pdb.test_pdb_where_command[3]>(8)<module>()
<doctest test.test_pdb.test_pdb_where_command[3]>(13)<module>()
-> test_function()
<doctest test.test_pdb.test_pdb_where_command[2]>(2)test_function()
-> f()
> <doctest test.test_pdb.test_pdb_where_command[1]>(2)f()
-> g()
<doctest test.test_pdb.test_pdb_where_command[0]>(2)g()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) w 0
> <doctest test.test_pdb.test_pdb_where_command[1]>(2)f()
-> g()
(Pdb) w 100
...
<doctest test.test_pdb.test_pdb_where_command[3]>(13)<module>()
-> test_function()
<doctest test.test_pdb.test_pdb_where_command[2]>(2)test_function()
-> f()
> <doctest test.test_pdb.test_pdb_where_command[1]>(2)f()
-> g();
-> g()
<doctest test.test_pdb.test_pdb_where_command[0]>(2)g()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) w -100
...
<doctest test.test_pdb.test_pdb_where_command[3]>(13)<module>()
-> test_function()
<doctest test.test_pdb.test_pdb_where_command[2]>(2)test_function()
-> f()
> <doctest test.test_pdb.test_pdb_where_command[1]>(2)f()
-> g()
<doctest test.test_pdb.test_pdb_where_command[0]>(2)g()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) continue
Expand Down Expand Up @@ -3179,6 +3212,7 @@ def test_pdbrc_basic(self):
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
self.assertNotIn("SyntaxError", stdout)
self.assertIn("a+8=9", stdout)
self.assertIn("-> b = 2", stdout)

def test_pdbrc_empty_line(self):
"""Test that empty lines in .pdbrc are ignored."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Print stack entry in :mod:`pdb` when and only when user input is needed.
Loading