Skip to content

Commit b17d347

Browse files
[3.11] gh-90095: Make .pdbrc work properly and add some reasonable te… (#116660)
[3.11] gh-90095: Make .pdbrc work properly and add some reasonable tests (GH-110496) (cherry picked from commit 44f9a84)
1 parent 52c4117 commit b17d347

File tree

3 files changed

+102
-95
lines changed

3 files changed

+102
-95
lines changed

Lib/pdb.py

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -295,26 +295,10 @@ def setup(self, f, tb):
295295
# locals whenever the .f_locals accessor is called, so we
296296
# cache it here to ensure that modifications are not overwritten.
297297
self.curframe_locals = self.curframe.f_locals
298-
return self.execRcLines()
299298

300-
# Can be executed earlier than 'setup' if desired
301-
def execRcLines(self):
302-
if not self.rcLines:
303-
return
304-
# local copy because of recursion
305-
rcLines = self.rcLines
306-
rcLines.reverse()
307-
# execute every line only once
308-
self.rcLines = []
309-
while rcLines:
310-
line = rcLines.pop().strip()
311-
if line and line[0] != '#':
312-
if self.onecmd(line):
313-
# if onecmd returns True, the command wants to exit
314-
# from the interaction, save leftover rc lines
315-
# to execute before next interaction
316-
self.rcLines += reversed(rcLines)
317-
return True
299+
if self.rcLines:
300+
self.cmdqueue = self.rcLines
301+
self.rcLines = []
318302

319303
# Override Bdb methods
320304

@@ -425,12 +409,10 @@ def interaction(self, frame, traceback):
425409
pass
426410
else:
427411
Pdb._previous_sigint_handler = None
428-
if self.setup(frame, traceback):
429-
# no interaction desired at this time (happens if .pdbrc contains
430-
# a command like "continue")
431-
self.forget()
432-
return
433-
self.print_stack_entry(self.stack[self.curindex])
412+
self.setup(frame, traceback)
413+
# if we have more commands to process, do not show the stack entry
414+
if not self.cmdqueue:
415+
self.print_stack_entry(self.stack[self.curindex])
434416
self._cmdloop()
435417
self.forget()
436418

@@ -484,7 +466,7 @@ def precmd(self, line):
484466
if marker >= 0:
485467
# queue up everything after marker
486468
next = line[marker+2:].lstrip()
487-
self.cmdqueue.append(next)
469+
self.cmdqueue.insert(0, next)
488470
line = line[:marker].rstrip()
489471
return line
490472

@@ -504,13 +486,12 @@ def handle_command_def(self, line):
504486
"""Handles one command line during command list definition."""
505487
cmd, arg, line = self.parseline(line)
506488
if not cmd:
507-
return
489+
return False
508490
if cmd == 'silent':
509491
self.commands_silent[self.commands_bnum] = True
510-
return # continue to handle other cmd def in the cmd list
492+
return False # continue to handle other cmd def in the cmd list
511493
elif cmd == 'end':
512-
self.cmdqueue = []
513-
return 1 # end of cmd list
494+
return True # end of cmd list
514495
cmdlist = self.commands[self.commands_bnum]
515496
if arg:
516497
cmdlist.append(cmd+' '+arg)
@@ -524,9 +505,8 @@ def handle_command_def(self, line):
524505
# one of the resuming commands
525506
if func.__name__ in self.commands_resuming:
526507
self.commands_doprompt[self.commands_bnum] = False
527-
self.cmdqueue = []
528-
return 1
529-
return
508+
return True
509+
return False
530510

531511
# interface abstraction functions
532512

Lib/test/test_pdb.py

Lines changed: 88 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,13 +1785,30 @@ def _run_pdb(self, pdb_args, commands, expected_returncode=0):
17851785
)
17861786
return stdout, stderr
17871787

1788-
def run_pdb_script(self, script, commands, expected_returncode=0):
1788+
def run_pdb_script(self, script, commands,
1789+
expected_returncode=0,
1790+
pdbrc=None,
1791+
remove_home=False):
17891792
"""Run 'script' lines with pdb and the pdb 'commands'."""
17901793
filename = 'main.py'
17911794
with open(filename, 'w') as f:
17921795
f.write(textwrap.dedent(script))
1796+
1797+
if pdbrc is not None:
1798+
with open('.pdbrc', 'w') as f:
1799+
f.write(textwrap.dedent(pdbrc))
1800+
self.addCleanup(os_helper.unlink, '.pdbrc')
17931801
self.addCleanup(os_helper.unlink, filename)
1794-
return self._run_pdb([filename], commands, expected_returncode)
1802+
1803+
homesave = None
1804+
if remove_home:
1805+
homesave = os.environ.pop('HOME', None)
1806+
try:
1807+
stdout, stderr = self._run_pdb([filename], commands, expected_returncode)
1808+
finally:
1809+
if homesave is not None:
1810+
os.environ['HOME'] = homesave
1811+
return stdout, stderr
17951812

17961813
def run_pdb_module(self, script, commands):
17971814
"""Runs the script code as part of a module"""
@@ -2031,37 +2048,80 @@ def test_issue26053(self):
20312048
self.assertRegex(res, "Restarting .* with arguments:\na b c")
20322049
self.assertRegex(res, "Restarting .* with arguments:\nd e f")
20332050

2034-
def test_readrc_kwarg(self):
2051+
def test_pdbrc_basic(self):
20352052
script = textwrap.dedent("""
2036-
import pdb; pdb.Pdb(readrc=False).set_trace()
2053+
a = 1
2054+
b = 2
2055+
""")
20372056

2038-
print('hello')
2057+
pdbrc = textwrap.dedent("""
2058+
# Comments should be fine
2059+
n
2060+
p f"{a+8=}"
20392061
""")
20402062

2041-
save_home = os.environ.pop('HOME', None)
2042-
try:
2043-
with os_helper.temp_cwd():
2044-
with open('.pdbrc', 'w') as f:
2045-
f.write("invalid\n")
2046-
2047-
with open('main.py', 'w') as f:
2048-
f.write(script)
2049-
2050-
cmd = [sys.executable, 'main.py']
2051-
proc = subprocess.Popen(
2052-
cmd,
2053-
stdout=subprocess.PIPE,
2054-
stdin=subprocess.PIPE,
2055-
stderr=subprocess.PIPE,
2056-
)
2057-
with proc:
2058-
stdout, stderr = proc.communicate(b'q\n')
2059-
self.assertNotIn(b"NameError: name 'invalid' is not defined",
2060-
stdout)
2063+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2064+
self.assertIn("a+8=9", stdout)
20612065

2062-
finally:
2063-
if save_home is not None:
2064-
os.environ['HOME'] = save_home
2066+
def test_pdbrc_alias(self):
2067+
script = textwrap.dedent("""
2068+
class A:
2069+
def __init__(self):
2070+
self.attr = 1
2071+
a = A()
2072+
b = 2
2073+
""")
2074+
2075+
pdbrc = textwrap.dedent("""
2076+
alias pi for k in %1.__dict__.keys(): print(f"%1.{k} = {%1.__dict__[k]}")
2077+
until 6
2078+
pi a
2079+
""")
2080+
2081+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2082+
self.assertIn("a.attr = 1", stdout)
2083+
2084+
def test_pdbrc_semicolon(self):
2085+
script = textwrap.dedent("""
2086+
class A:
2087+
def __init__(self):
2088+
self.attr = 1
2089+
a = A()
2090+
b = 2
2091+
""")
2092+
2093+
pdbrc = textwrap.dedent("""
2094+
b 5;;c;;n
2095+
""")
2096+
2097+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2098+
self.assertIn("-> b = 2", stdout)
2099+
2100+
def test_pdbrc_commands(self):
2101+
script = textwrap.dedent("""
2102+
class A:
2103+
def __init__(self):
2104+
self.attr = 1
2105+
a = A()
2106+
b = 2
2107+
""")
2108+
2109+
pdbrc = textwrap.dedent("""
2110+
b 6
2111+
commands 1 ;; p a;; end
2112+
c
2113+
""")
2114+
2115+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2116+
self.assertIn("<__main__.A object at", stdout)
2117+
2118+
def test_readrc_kwarg(self):
2119+
script = textwrap.dedent("""
2120+
print('hello')
2121+
""")
2122+
2123+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc='invalid', remove_home=True)
2124+
self.assertIn("NameError: name 'invalid' is not defined", stdout)
20652125

20662126
def test_readrc_homedir(self):
20672127
save_home = os.environ.pop("HOME", None)
@@ -2076,40 +2136,6 @@ def test_readrc_homedir(self):
20762136
if save_home is not None:
20772137
os.environ["HOME"] = save_home
20782138

2079-
def test_read_pdbrc_with_ascii_encoding(self):
2080-
script = textwrap.dedent("""
2081-
import pdb; pdb.Pdb().set_trace()
2082-
print('hello')
2083-
""")
2084-
save_home = os.environ.pop('HOME', None)
2085-
try:
2086-
with os_helper.temp_cwd():
2087-
with open('.pdbrc', 'w', encoding='utf-8') as f:
2088-
f.write("Fran\u00E7ais")
2089-
2090-
with open('main.py', 'w', encoding='utf-8') as f:
2091-
f.write(script)
2092-
2093-
cmd = [sys.executable, 'main.py']
2094-
env = {'PYTHONIOENCODING': 'ascii'}
2095-
if sys.platform == 'win32':
2096-
env['PYTHONLEGACYWINDOWSSTDIO'] = 'non-empty-string'
2097-
proc = subprocess.Popen(
2098-
cmd,
2099-
stdout=subprocess.PIPE,
2100-
stdin=subprocess.PIPE,
2101-
stderr=subprocess.PIPE,
2102-
env={**os.environ, **env}
2103-
)
2104-
with proc:
2105-
stdout, stderr = proc.communicate(b'c\n')
2106-
self.assertIn(b"UnicodeEncodeError: \'ascii\' codec can\'t encode character "
2107-
b"\'\\xe7\' in position 21: ordinal not in range(128)", stderr)
2108-
2109-
finally:
2110-
if save_home is not None:
2111-
os.environ['HOME'] = save_home
2112-
21132139
def test_header(self):
21142140
stdout = StringIO()
21152141
header = 'Nobody expects... blah, blah, blah'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make .pdbrc and -c work with any valid pdb commands.

0 commit comments

Comments
 (0)