gh-90095: Make .pdbrc work properly and add some reasonable tests (#110496)

This commit is contained in:
Tian Gao 2024-03-11 14:27:00 -07:00 committed by GitHub
parent 3c0dcef980
commit 44f9a84b67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 96 deletions

View File

@ -363,26 +363,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self._chained_exceptions[self._chained_exception_index], self._chained_exceptions[self._chained_exception_index],
) )
return self.execRcLines() if self.rcLines:
self.cmdqueue = self.rcLines
# Can be executed earlier than 'setup' if desired self.rcLines = []
def execRcLines(self):
if not self.rcLines:
return
# local copy because of recursion
rcLines = self.rcLines
rcLines.reverse()
# execute every line only once
self.rcLines = []
while rcLines:
line = rcLines.pop().strip()
if line and line[0] != '#':
if self.onecmd(line):
# if onecmd returns True, the command wants to exit
# from the interaction, save leftover rc lines
# to execute before next interaction
self.rcLines += reversed(rcLines)
return True
# Override Bdb methods # Override Bdb methods
@ -571,12 +554,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if isinstance(tb_or_exc, BaseException): if isinstance(tb_or_exc, BaseException):
assert tb is not None, "main exception must have a traceback" assert tb is not None, "main exception must have a traceback"
with self._hold_exceptions(_chained_exceptions): with self._hold_exceptions(_chained_exceptions):
if self.setup(frame, tb): self.setup(frame, tb)
# no interaction desired at this time (happens if .pdbrc contains # if we have more commands to process, do not show the stack entry
# a command like "continue") if not self.cmdqueue:
self.forget() self.print_stack_entry(self.stack[self.curindex])
return
self.print_stack_entry(self.stack[self.curindex])
self._cmdloop() self._cmdloop()
self.forget() self.forget()
@ -712,7 +693,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if marker >= 0: if marker >= 0:
# queue up everything after marker # queue up everything after marker
next = line[marker+2:].lstrip() next = line[marker+2:].lstrip()
self.cmdqueue.append(next) self.cmdqueue.insert(0, next)
line = line[:marker].rstrip() line = line[:marker].rstrip()
# Replace all the convenience variables # Replace all the convenience variables
@ -737,13 +718,12 @@ class Pdb(bdb.Bdb, cmd.Cmd):
"""Handles one command line during command list definition.""" """Handles one command line during command list definition."""
cmd, arg, line = self.parseline(line) cmd, arg, line = self.parseline(line)
if not cmd: if not cmd:
return return False
if cmd == 'silent': if cmd == 'silent':
self.commands_silent[self.commands_bnum] = True self.commands_silent[self.commands_bnum] = True
return # continue to handle other cmd def in the cmd list return False # continue to handle other cmd def in the cmd list
elif cmd == 'end': elif cmd == 'end':
self.cmdqueue = [] return True # end of cmd list
return 1 # end of cmd list
cmdlist = self.commands[self.commands_bnum] cmdlist = self.commands[self.commands_bnum]
if arg: if arg:
cmdlist.append(cmd+' '+arg) cmdlist.append(cmd+' '+arg)
@ -757,9 +737,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# one of the resuming commands # one of the resuming commands
if func.__name__ in self.commands_resuming: if func.__name__ in self.commands_resuming:
self.commands_doprompt[self.commands_bnum] = False self.commands_doprompt[self.commands_bnum] = False
self.cmdqueue = [] return True
return 1 return False
return
# interface abstraction functions # interface abstraction functions

View File

@ -2618,13 +2618,29 @@ class PdbTestCase(unittest.TestCase):
def run_pdb_script(self, script, commands, def run_pdb_script(self, script, commands,
expected_returncode=0, expected_returncode=0,
extra_env=None): extra_env=None,
pdbrc=None,
remove_home=False):
"""Run 'script' lines with pdb and the pdb 'commands'.""" """Run 'script' lines with pdb and the pdb 'commands'."""
filename = 'main.py' filename = 'main.py'
with open(filename, 'w') as f: with open(filename, 'w') as f:
f.write(textwrap.dedent(script)) f.write(textwrap.dedent(script))
if pdbrc is not None:
with open('.pdbrc', 'w') as f:
f.write(textwrap.dedent(pdbrc))
self.addCleanup(os_helper.unlink, '.pdbrc')
self.addCleanup(os_helper.unlink, filename) self.addCleanup(os_helper.unlink, filename)
return self._run_pdb([filename], commands, expected_returncode, extra_env)
homesave = None
if remove_home:
homesave = os.environ.pop('HOME', None)
try:
stdout, stderr = self._run_pdb([filename], commands, expected_returncode, extra_env)
finally:
if homesave is not None:
os.environ['HOME'] = homesave
return stdout, stderr
def run_pdb_module(self, script, commands): def run_pdb_module(self, script, commands):
"""Runs the script code as part of a module""" """Runs the script code as part of a module"""
@ -2904,37 +2920,80 @@ def bœr():
self.assertRegex(res, "Restarting .* with arguments:\na b c") self.assertRegex(res, "Restarting .* with arguments:\na b c")
self.assertRegex(res, "Restarting .* with arguments:\nd e f") self.assertRegex(res, "Restarting .* with arguments:\nd e f")
def test_pdbrc_basic(self):
script = textwrap.dedent("""
a = 1
b = 2
""")
pdbrc = textwrap.dedent("""
# Comments should be fine
n
p f"{a+8=}"
""")
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
self.assertIn("a+8=9", stdout)
def test_pdbrc_alias(self):
script = textwrap.dedent("""
class A:
def __init__(self):
self.attr = 1
a = A()
b = 2
""")
pdbrc = textwrap.dedent("""
alias pi for k in %1.__dict__.keys(): print(f"%1.{k} = {%1.__dict__[k]}")
until 6
pi a
""")
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
self.assertIn("a.attr = 1", stdout)
def test_pdbrc_semicolon(self):
script = textwrap.dedent("""
class A:
def __init__(self):
self.attr = 1
a = A()
b = 2
""")
pdbrc = textwrap.dedent("""
b 5;;c;;n
""")
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
self.assertIn("-> b = 2", stdout)
def test_pdbrc_commands(self):
script = textwrap.dedent("""
class A:
def __init__(self):
self.attr = 1
a = A()
b = 2
""")
pdbrc = textwrap.dedent("""
b 6
commands 1 ;; p a;; end
c
""")
stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True)
self.assertIn("<__main__.A object at", stdout)
def test_readrc_kwarg(self): def test_readrc_kwarg(self):
script = textwrap.dedent(""" script = textwrap.dedent("""
import pdb; pdb.Pdb(readrc=False).set_trace()
print('hello') print('hello')
""") """)
save_home = os.environ.pop('HOME', None) stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc='invalid', remove_home=True)
try: self.assertIn("NameError: name 'invalid' is not defined", stdout)
with os_helper.temp_cwd():
with open('.pdbrc', 'w') as f:
f.write("invalid\n")
with open('main.py', 'w') as f:
f.write(script)
cmd = [sys.executable, 'main.py']
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
)
with proc:
stdout, stderr = proc.communicate(b'q\n')
self.assertNotIn(b"NameError: name 'invalid' is not defined",
stdout)
finally:
if save_home is not None:
os.environ['HOME'] = save_home
def test_readrc_homedir(self): def test_readrc_homedir(self):
save_home = os.environ.pop("HOME", None) save_home = os.environ.pop("HOME", None)
@ -2949,40 +3008,6 @@ def bœr():
if save_home is not None: if save_home is not None:
os.environ["HOME"] = save_home os.environ["HOME"] = save_home
def test_read_pdbrc_with_ascii_encoding(self):
script = textwrap.dedent("""
import pdb; pdb.Pdb().set_trace()
print('hello')
""")
save_home = os.environ.pop('HOME', None)
try:
with os_helper.temp_cwd():
with open('.pdbrc', 'w', encoding='utf-8') as f:
f.write("Fran\u00E7ais")
with open('main.py', 'w', encoding='utf-8') as f:
f.write(script)
cmd = [sys.executable, 'main.py']
env = {'PYTHONIOENCODING': 'ascii'}
if sys.platform == 'win32':
env['PYTHONLEGACYWINDOWSSTDIO'] = 'non-empty-string'
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
env={**os.environ, **env}
)
with proc:
stdout, stderr = proc.communicate(b'c\n')
self.assertIn(b"UnicodeEncodeError: \'ascii\' codec can\'t encode character "
b"\'\\xe7\' in position 21: ordinal not in range(128)", stderr)
finally:
if save_home is not None:
os.environ['HOME'] = save_home
def test_header(self): def test_header(self):
stdout = StringIO() stdout = StringIO()
header = 'Nobody expects... blah, blah, blah' header = 'Nobody expects... blah, blah, blah'

View File

@ -0,0 +1 @@
Make .pdbrc and -c work with any valid pdb commands.