Skip to content

Commit 44f9a84

Browse files
gh-90095: Make .pdbrc work properly and add some reasonable tests (#110496)
1 parent 3c0dcef commit 44f9a84

File tree

3 files changed

+101
-96
lines changed

3 files changed

+101
-96
lines changed

Lib/pdb.py

+13-34
Original file line numberDiff line numberDiff line change
@@ -363,26 +363,9 @@ def setup(self, f, tb):
363363
self._chained_exceptions[self._chained_exception_index],
364364
)
365365

366-
return self.execRcLines()
367-
368-
# Can be executed earlier than 'setup' if desired
369-
def execRcLines(self):
370-
if not self.rcLines:
371-
return
372-
# local copy because of recursion
373-
rcLines = self.rcLines
374-
rcLines.reverse()
375-
# execute every line only once
376-
self.rcLines = []
377-
while rcLines:
378-
line = rcLines.pop().strip()
379-
if line and line[0] != '#':
380-
if self.onecmd(line):
381-
# if onecmd returns True, the command wants to exit
382-
# from the interaction, save leftover rc lines
383-
# to execute before next interaction
384-
self.rcLines += reversed(rcLines)
385-
return True
366+
if self.rcLines:
367+
self.cmdqueue = self.rcLines
368+
self.rcLines = []
386369

387370
# Override Bdb methods
388371

@@ -571,12 +554,10 @@ def interaction(self, frame, tb_or_exc):
571554
if isinstance(tb_or_exc, BaseException):
572555
assert tb is not None, "main exception must have a traceback"
573556
with self._hold_exceptions(_chained_exceptions):
574-
if self.setup(frame, tb):
575-
# no interaction desired at this time (happens if .pdbrc contains
576-
# a command like "continue")
577-
self.forget()
578-
return
579-
self.print_stack_entry(self.stack[self.curindex])
557+
self.setup(frame, tb)
558+
# if we have more commands to process, do not show the stack entry
559+
if not self.cmdqueue:
560+
self.print_stack_entry(self.stack[self.curindex])
580561
self._cmdloop()
581562
self.forget()
582563

@@ -712,7 +693,7 @@ def precmd(self, line):
712693
if marker >= 0:
713694
# queue up everything after marker
714695
next = line[marker+2:].lstrip()
715-
self.cmdqueue.append(next)
696+
self.cmdqueue.insert(0, next)
716697
line = line[:marker].rstrip()
717698

718699
# Replace all the convenience variables
@@ -737,13 +718,12 @@ def handle_command_def(self, line):
737718
"""Handles one command line during command list definition."""
738719
cmd, arg, line = self.parseline(line)
739720
if not cmd:
740-
return
721+
return False
741722
if cmd == 'silent':
742723
self.commands_silent[self.commands_bnum] = True
743-
return # continue to handle other cmd def in the cmd list
724+
return False # continue to handle other cmd def in the cmd list
744725
elif cmd == 'end':
745-
self.cmdqueue = []
746-
return 1 # end of cmd list
726+
return True # end of cmd list
747727
cmdlist = self.commands[self.commands_bnum]
748728
if arg:
749729
cmdlist.append(cmd+' '+arg)
@@ -757,9 +737,8 @@ def handle_command_def(self, line):
757737
# one of the resuming commands
758738
if func.__name__ in self.commands_resuming:
759739
self.commands_doprompt[self.commands_bnum] = False
760-
self.cmdqueue = []
761-
return 1
762-
return
740+
return True
741+
return False
763742

764743
# interface abstraction functions
765744

Lib/test/test_pdb.py

+87-62
Original file line numberDiff line numberDiff line change
@@ -2618,13 +2618,29 @@ def _run_pdb(self, pdb_args, commands,
26182618

26192619
def run_pdb_script(self, script, commands,
26202620
expected_returncode=0,
2621-
extra_env=None):
2621+
extra_env=None,
2622+
pdbrc=None,
2623+
remove_home=False):
26222624
"""Run 'script' lines with pdb and the pdb 'commands'."""
26232625
filename = 'main.py'
26242626
with open(filename, 'w') as f:
26252627
f.write(textwrap.dedent(script))
2628+
2629+
if pdbrc is not None:
2630+
with open('.pdbrc', 'w') as f:
2631+
f.write(textwrap.dedent(pdbrc))
2632+
self.addCleanup(os_helper.unlink, '.pdbrc')
26262633
self.addCleanup(os_helper.unlink, filename)
2627-
return self._run_pdb([filename], commands, expected_returncode, extra_env)
2634+
2635+
homesave = None
2636+
if remove_home:
2637+
homesave = os.environ.pop('HOME', None)
2638+
try:
2639+
stdout, stderr = self._run_pdb([filename], commands, expected_returncode, extra_env)
2640+
finally:
2641+
if homesave is not None:
2642+
os.environ['HOME'] = homesave
2643+
return stdout, stderr
26282644

26292645
def run_pdb_module(self, script, commands):
26302646
"""Runs the script code as part of a module"""
@@ -2904,37 +2920,80 @@ def test_issue26053(self):
29042920
self.assertRegex(res, "Restarting .* with arguments:\na b c")
29052921
self.assertRegex(res, "Restarting .* with arguments:\nd e f")
29062922

2907-
def test_readrc_kwarg(self):
2923+
def test_pdbrc_basic(self):
29082924
script = textwrap.dedent("""
2909-
import pdb; pdb.Pdb(readrc=False).set_trace()
2925+
a = 1
2926+
b = 2
2927+
""")
29102928

2911-
print('hello')
2929+
pdbrc = textwrap.dedent("""
2930+
# Comments should be fine
2931+
n
2932+
p f"{a+8=}"
29122933
""")
29132934

2914-
save_home = os.environ.pop('HOME', None)
2915-
try:
2916-
with os_helper.temp_cwd():
2917-
with open('.pdbrc', 'w') as f:
2918-
f.write("invalid\n")
2919-
2920-
with open('main.py', 'w') as f:
2921-
f.write(script)
2922-
2923-
cmd = [sys.executable, 'main.py']
2924-
proc = subprocess.Popen(
2925-
cmd,
2926-
stdout=subprocess.PIPE,
2927-
stdin=subprocess.PIPE,
2928-
stderr=subprocess.PIPE,
2929-
)
2930-
with proc:
2931-
stdout, stderr = proc.communicate(b'q\n')
2932-
self.assertNotIn(b"NameError: name 'invalid' is not defined",
2933-
stdout)
2935+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2936+
self.assertIn("a+8=9", stdout)
29342937

2935-
finally:
2936-
if save_home is not None:
2937-
os.environ['HOME'] = save_home
2938+
def test_pdbrc_alias(self):
2939+
script = textwrap.dedent("""
2940+
class A:
2941+
def __init__(self):
2942+
self.attr = 1
2943+
a = A()
2944+
b = 2
2945+
""")
2946+
2947+
pdbrc = textwrap.dedent("""
2948+
alias pi for k in %1.__dict__.keys(): print(f"%1.{k} = {%1.__dict__[k]}")
2949+
until 6
2950+
pi a
2951+
""")
2952+
2953+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2954+
self.assertIn("a.attr = 1", stdout)
2955+
2956+
def test_pdbrc_semicolon(self):
2957+
script = textwrap.dedent("""
2958+
class A:
2959+
def __init__(self):
2960+
self.attr = 1
2961+
a = A()
2962+
b = 2
2963+
""")
2964+
2965+
pdbrc = textwrap.dedent("""
2966+
b 5;;c;;n
2967+
""")
2968+
2969+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2970+
self.assertIn("-> b = 2", stdout)
2971+
2972+
def test_pdbrc_commands(self):
2973+
script = textwrap.dedent("""
2974+
class A:
2975+
def __init__(self):
2976+
self.attr = 1
2977+
a = A()
2978+
b = 2
2979+
""")
2980+
2981+
pdbrc = textwrap.dedent("""
2982+
b 6
2983+
commands 1 ;; p a;; end
2984+
c
2985+
""")
2986+
2987+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
2988+
self.assertIn("<__main__.A object at", stdout)
2989+
2990+
def test_readrc_kwarg(self):
2991+
script = textwrap.dedent("""
2992+
print('hello')
2993+
""")
2994+
2995+
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc='invalid', remove_home=True)
2996+
self.assertIn("NameError: name 'invalid' is not defined", stdout)
29382997

29392998
def test_readrc_homedir(self):
29402999
save_home = os.environ.pop("HOME", None)
@@ -2949,40 +3008,6 @@ def test_readrc_homedir(self):
29493008
if save_home is not None:
29503009
os.environ["HOME"] = save_home
29513010

2952-
def test_read_pdbrc_with_ascii_encoding(self):
2953-
script = textwrap.dedent("""
2954-
import pdb; pdb.Pdb().set_trace()
2955-
print('hello')
2956-
""")
2957-
save_home = os.environ.pop('HOME', None)
2958-
try:
2959-
with os_helper.temp_cwd():
2960-
with open('.pdbrc', 'w', encoding='utf-8') as f:
2961-
f.write("Fran\u00E7ais")
2962-
2963-
with open('main.py', 'w', encoding='utf-8') as f:
2964-
f.write(script)
2965-
2966-
cmd = [sys.executable, 'main.py']
2967-
env = {'PYTHONIOENCODING': 'ascii'}
2968-
if sys.platform == 'win32':
2969-
env['PYTHONLEGACYWINDOWSSTDIO'] = 'non-empty-string'
2970-
proc = subprocess.Popen(
2971-
cmd,
2972-
stdout=subprocess.PIPE,
2973-
stdin=subprocess.PIPE,
2974-
stderr=subprocess.PIPE,
2975-
env={**os.environ, **env}
2976-
)
2977-
with proc:
2978-
stdout, stderr = proc.communicate(b'c\n')
2979-
self.assertIn(b"UnicodeEncodeError: \'ascii\' codec can\'t encode character "
2980-
b"\'\\xe7\' in position 21: ordinal not in range(128)", stderr)
2981-
2982-
finally:
2983-
if save_home is not None:
2984-
os.environ['HOME'] = save_home
2985-
29863011
def test_header(self):
29873012
stdout = StringIO()
29883013
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)