Skip to content

Commit d19c2eb

Browse files
[3.12] gh-90095: Make .pdbrc work properly and add some reasonable te… (#116661)
[3.12] gh-90095: Make .pdbrc work properly and add some reasonable tests (GH-110496) (cherry picked from commit 44f9a84)
1 parent 4ea9d15 commit d19c2eb

File tree

3 files changed

+102
-95
lines changed

3 files changed

+102
-95
lines changed

Lib/pdb.py

+13-33
Original file line numberDiff line numberDiff line change
@@ -299,26 +299,10 @@ def setup(self, f, tb):
299299
# cache it here to ensure that modifications are not overwritten.
300300
self.curframe_locals = self.curframe.f_locals
301301
self.set_convenience_variable(self.curframe, '_frame', self.curframe)
302-
return self.execRcLines()
303302

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

323307
# Override Bdb methods
324308

@@ -431,12 +415,10 @@ def interaction(self, frame, traceback):
431415
pass
432416
else:
433417
Pdb._previous_sigint_handler = None
434-
if self.setup(frame, traceback):
435-
# no interaction desired at this time (happens if .pdbrc contains
436-
# a command like "continue")
437-
self.forget()
438-
return
439-
self.print_stack_entry(self.stack[self.curindex])
418+
self.setup(frame, traceback)
419+
# if we have more commands to process, do not show the stack entry
420+
if not self.cmdqueue:
421+
self.print_stack_entry(self.stack[self.curindex])
440422
self._cmdloop()
441423
self.forget()
442424

@@ -523,7 +505,7 @@ def precmd(self, line):
523505
if marker >= 0:
524506
# queue up everything after marker
525507
next = line[marker+2:].lstrip()
526-
self.cmdqueue.append(next)
508+
self.cmdqueue.insert(0, next)
527509
line = line[:marker].rstrip()
528510

529511
# Replace all the convenience variables
@@ -547,13 +529,12 @@ def handle_command_def(self, line):
547529
"""Handles one command line during command list definition."""
548530
cmd, arg, line = self.parseline(line)
549531
if not cmd:
550-
return
532+
return False
551533
if cmd == 'silent':
552534
self.commands_silent[self.commands_bnum] = True
553-
return # continue to handle other cmd def in the cmd list
535+
return False # continue to handle other cmd def in the cmd list
554536
elif cmd == 'end':
555-
self.cmdqueue = []
556-
return 1 # end of cmd list
537+
return True # end of cmd list
557538
cmdlist = self.commands[self.commands_bnum]
558539
if arg:
559540
cmdlist.append(cmd+' '+arg)
@@ -567,9 +548,8 @@ def handle_command_def(self, line):
567548
# one of the resuming commands
568549
if func.__name__ in self.commands_resuming:
569550
self.commands_doprompt[self.commands_bnum] = False
570-
self.cmdqueue = []
571-
return 1
572-
return
551+
return True
552+
return False
573553

574554
# interface abstraction functions
575555

Lib/test/test_pdb.py

+88-62
Original file line numberDiff line numberDiff line change
@@ -1936,13 +1936,30 @@ def _run_pdb(self, pdb_args, commands, expected_returncode=0):
19361936
)
19371937
return stdout, stderr
19381938

1939-
def run_pdb_script(self, script, commands, expected_returncode=0):
1939+
def run_pdb_script(self, script, commands,
1940+
expected_returncode=0,
1941+
pdbrc=None,
1942+
remove_home=False):
19401943
"""Run 'script' lines with pdb and the pdb 'commands'."""
19411944
filename = 'main.py'
19421945
with open(filename, 'w') as f:
19431946
f.write(textwrap.dedent(script))
1947+
1948+
if pdbrc is not None:
1949+
with open('.pdbrc', 'w') as f:
1950+
f.write(textwrap.dedent(pdbrc))
1951+
self.addCleanup(os_helper.unlink, '.pdbrc')
19441952
self.addCleanup(os_helper.unlink, filename)
1945-
return self._run_pdb([filename], commands, expected_returncode)
1953+
1954+
homesave = None
1955+
if remove_home:
1956+
homesave = os.environ.pop('HOME', None)
1957+
try:
1958+
stdout, stderr = self._run_pdb([filename], commands, expected_returncode)
1959+
finally:
1960+
if homesave is not None:
1961+
os.environ['HOME'] = homesave
1962+
return stdout, stderr
19461963

19471964
def run_pdb_module(self, script, commands):
19481965
"""Runs the script code as part of a module"""
@@ -2182,37 +2199,80 @@ def test_issue26053(self):
21822199
self.assertRegex(res, "Restarting .* with arguments:\na b c")
21832200
self.assertRegex(res, "Restarting .* with arguments:\nd e f")
21842201

2185-
def test_readrc_kwarg(self):
2202+
def test_pdbrc_basic(self):
21862203
script = textwrap.dedent("""
2187-
import pdb; pdb.Pdb(readrc=False).set_trace()
2204+
a = 1
2205+
b = 2
2206+
""")
21882207

2189-
print('hello')
2208+
pdbrc = textwrap.dedent("""
2209+
# Comments should be fine
2210+
n
2211+
p f"{a+8=}"
21902212
""")
21912213

2192-
save_home = os.environ.pop('HOME', None)
2193-
try:
2194-
with os_helper.temp_cwd():
2195-
with open('.pdbrc', 'w') as f:
2196-
f.write("invalid\n")
2197-
2198-
with open('main.py', 'w') as f:
2199-
f.write(script)
2200-
2201-
cmd = [sys.executable, 'main.py']
2202-
proc = subprocess.Popen(
2203-
cmd,
2204-
stdout=subprocess.PIPE,
2205-
stdin=subprocess.PIPE,
2206-
stderr=subprocess.PIPE,
2207-
)
2208-
with proc:
2209-
stdout, stderr = proc.communicate(b'q\n')
2210-
self.assertNotIn(b"NameError: name 'invalid' is not defined",
2211-
stdout)
2214+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2215+
self.assertIn("a+8=9", stdout)
22122216

2213-
finally:
2214-
if save_home is not None:
2215-
os.environ['HOME'] = save_home
2217+
def test_pdbrc_alias(self):
2218+
script = textwrap.dedent("""
2219+
class A:
2220+
def __init__(self):
2221+
self.attr = 1
2222+
a = A()
2223+
b = 2
2224+
""")
2225+
2226+
pdbrc = textwrap.dedent("""
2227+
alias pi for k in %1.__dict__.keys(): print(f"%1.{k} = {%1.__dict__[k]}")
2228+
until 6
2229+
pi a
2230+
""")
2231+
2232+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2233+
self.assertIn("a.attr = 1", stdout)
2234+
2235+
def test_pdbrc_semicolon(self):
2236+
script = textwrap.dedent("""
2237+
class A:
2238+
def __init__(self):
2239+
self.attr = 1
2240+
a = A()
2241+
b = 2
2242+
""")
2243+
2244+
pdbrc = textwrap.dedent("""
2245+
b 5;;c;;n
2246+
""")
2247+
2248+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2249+
self.assertIn("-> b = 2", stdout)
2250+
2251+
def test_pdbrc_commands(self):
2252+
script = textwrap.dedent("""
2253+
class A:
2254+
def __init__(self):
2255+
self.attr = 1
2256+
a = A()
2257+
b = 2
2258+
""")
2259+
2260+
pdbrc = textwrap.dedent("""
2261+
b 6
2262+
commands 1 ;; p a;; end
2263+
c
2264+
""")
2265+
2266+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2267+
self.assertIn("<__main__.A object at", stdout)
2268+
2269+
def test_readrc_kwarg(self):
2270+
script = textwrap.dedent("""
2271+
print('hello')
2272+
""")
2273+
2274+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc='invalid', remove_home=True)
2275+
self.assertIn("NameError: name 'invalid' is not defined", stdout)
22162276

22172277
def test_readrc_homedir(self):
22182278
save_home = os.environ.pop("HOME", None)
@@ -2227,40 +2287,6 @@ def test_readrc_homedir(self):
22272287
if save_home is not None:
22282288
os.environ["HOME"] = save_home
22292289

2230-
def test_read_pdbrc_with_ascii_encoding(self):
2231-
script = textwrap.dedent("""
2232-
import pdb; pdb.Pdb().set_trace()
2233-
print('hello')
2234-
""")
2235-
save_home = os.environ.pop('HOME', None)
2236-
try:
2237-
with os_helper.temp_cwd():
2238-
with open('.pdbrc', 'w', encoding='utf-8') as f:
2239-
f.write("Fran\u00E7ais")
2240-
2241-
with open('main.py', 'w', encoding='utf-8') as f:
2242-
f.write(script)
2243-
2244-
cmd = [sys.executable, 'main.py']
2245-
env = {'PYTHONIOENCODING': 'ascii'}
2246-
if sys.platform == 'win32':
2247-
env['PYTHONLEGACYWINDOWSSTDIO'] = 'non-empty-string'
2248-
proc = subprocess.Popen(
2249-
cmd,
2250-
stdout=subprocess.PIPE,
2251-
stdin=subprocess.PIPE,
2252-
stderr=subprocess.PIPE,
2253-
env={**os.environ, **env}
2254-
)
2255-
with proc:
2256-
stdout, stderr = proc.communicate(b'c\n')
2257-
self.assertIn(b"UnicodeEncodeError: \'ascii\' codec can\'t encode character "
2258-
b"\'\\xe7\' in position 21: ordinal not in range(128)", stderr)
2259-
2260-
finally:
2261-
if save_home is not None:
2262-
os.environ['HOME'] = save_home
2263-
22642290
def test_header(self):
22652291
stdout = StringIO()
22662292
header = 'Nobody expects... blah, blah, blah'
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)