mirror of https://github.com/python/cpython
gh-90095: Make .pdbrc work properly and add some reasonable tests (#110496)
This commit is contained in:
parent
3c0dcef980
commit
44f9a84b67
47
Lib/pdb.py
47
Lib/pdb.py
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Make .pdbrc and -c work with any valid pdb commands.
|
Loading…
Reference in New Issue