summaryrefslogtreecommitdiff
path: root/Lib
diff options
context:
space:
mode:
authorTian Gao <gaogaotiantian@hotmail.com>2023-05-03 07:04:50 -0700
committerGitHub <noreply@github.com>2023-05-03 15:04:50 +0100
commit0fc58c66bafbd20f02c206c801cf9ab939853164 (patch)
tree2c800e245c030eaa814c9645dce864ccbbea3cd9 /Lib
parent524a7f77fd8244835e382f076dd4a76404580bb3 (diff)
downloadcpython-git-0fc58c66bafbd20f02c206c801cf9ab939853164.tar.gz
gh-103693: Add convenience variable feature to `pdb` (#103694)
Diffstat (limited to 'Lib')
-rwxr-xr-xLib/pdb.py17
-rw-r--r--Lib/test/test_pdb.py78
2 files changed, 95 insertions, 0 deletions
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 645cbf518e..b3dc5a455e 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -270,6 +270,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.lineno = None
self.stack = []
self.curindex = 0
+ if hasattr(self, 'curframe') and self.curframe:
+ self.curframe.f_globals.pop('__pdb_convenience_variables', None)
self.curframe = None
self.tb_lineno.clear()
@@ -288,6 +290,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# locals whenever the .f_locals accessor is called, so we
# cache it here to ensure that modifications are not overwritten.
self.curframe_locals = self.curframe.f_locals
+ self.set_convenience_variable(self.curframe, '_frame', self.curframe)
return self.execRcLines()
# Can be executed earlier than 'setup' if desired
@@ -359,6 +362,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if self._wait_for_mainpyfile:
return
frame.f_locals['__return__'] = return_value
+ self.set_convenience_variable(frame, '_retval', return_value)
self.message('--Return--')
self.interaction(frame, None)
@@ -369,6 +373,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return
exc_type, exc_value, exc_traceback = exc_info
frame.f_locals['__exception__'] = exc_type, exc_value
+ self.set_convenience_variable(frame, '_exception', exc_value)
# An 'Internal StopIteration' exception is an exception debug event
# issued by the interpreter when handling a subgenerator run with
@@ -394,6 +399,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.message('--KeyboardInterrupt--')
# Called before loop, handles display expressions
+ # Set up convenience variable containers
def preloop(self):
displaying = self.displaying.get(self.curframe)
if displaying:
@@ -477,6 +483,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
next = line[marker+2:].lstrip()
self.cmdqueue.append(next)
line = line[:marker].rstrip()
+
+ # Replace all the convenience variables
+ line = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'__pdb_convenience_variables["\1"]', line)
return line
def onecmd(self, line):
@@ -527,6 +536,13 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def error(self, msg):
print('***', msg, file=self.stdout)
+ # convenience variables
+
+ def set_convenience_variable(self, frame, name, value):
+ if '__pdb_convenience_variables' not in frame.f_globals:
+ frame.f_globals['__pdb_convenience_variables'] = {}
+ frame.f_globals['__pdb_convenience_variables'][name] = value
+
# Generic completion functions. Individual complete_foo methods can be
# assigned below to one of these functions.
@@ -1018,6 +1034,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.curindex = number
self.curframe = self.stack[self.curindex][0]
self.curframe_locals = self.curframe.f_locals
+ self.set_convenience_variable(self.curframe, '_frame', self.curframe)
self.print_stack_entry(self.stack[self.curindex])
self.lineno = None
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 2f712a1025..482c92dbf1 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -746,6 +746,84 @@ def test_pdb_where_command():
(Pdb) continue
"""
+def test_convenience_variables():
+ """Test convenience variables
+
+ >>> def util_function():
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ ... try:
+ ... raise Exception('test')
+ ... except:
+ ... pass
+ ... return 1
+
+ >>> def test_function():
+ ... util_function()
+
+ >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ ... '$_frame.f_lineno', # Check frame convenience variable
+ ... '$a = 10', # Set a convenience variable
+ ... '$a', # Print its value
+ ... 'p $a + 2', # Do some calculation
+ ... 'u', # Switch frame
+ ... '$_frame.f_lineno', # Make sure the frame changed
+ ... '$a', # Make sure the value persists
+ ... 'd', # Go back to the original frame
+ ... 'next',
+ ... '$a', # The value should be gone
+ ... 'next',
+ ... '$_exception', # Check exception convenience variable
+ ... 'next',
+ ... '$_exception', # Exception should be gone
+ ... 'return',
+ ... '$_retval', # Check return convenience variable
+ ... 'continue',
+ ... ]):
+ ... test_function()
+ > <doctest test.test_pdb.test_convenience_variables[0]>(3)util_function()
+ -> try:
+ (Pdb) $_frame.f_lineno
+ 3
+ (Pdb) $a = 10
+ (Pdb) $a
+ 10
+ (Pdb) p $a + 2
+ 12
+ (Pdb) u
+ > <doctest test.test_pdb.test_convenience_variables[1]>(2)test_function()
+ -> util_function()
+ (Pdb) $_frame.f_lineno
+ 2
+ (Pdb) $a
+ 10
+ (Pdb) d
+ > <doctest test.test_pdb.test_convenience_variables[0]>(3)util_function()
+ -> try:
+ (Pdb) next
+ > <doctest test.test_pdb.test_convenience_variables[0]>(4)util_function()
+ -> raise Exception('test')
+ (Pdb) $a
+ *** KeyError: 'a'
+ (Pdb) next
+ Exception: test
+ > <doctest test.test_pdb.test_convenience_variables[0]>(4)util_function()
+ -> raise Exception('test')
+ (Pdb) $_exception
+ Exception('test')
+ (Pdb) next
+ > <doctest test.test_pdb.test_convenience_variables[0]>(5)util_function()
+ -> except:
+ (Pdb) $_exception
+ *** KeyError: '_exception'
+ (Pdb) return
+ --Return--
+ > <doctest test.test_pdb.test_convenience_variables[0]>(7)util_function()->1
+ -> return 1
+ (Pdb) $_retval
+ 1
+ (Pdb) continue
+ """
+
def test_post_mortem():
"""Test post mortem traceback debugging.