Skip to content

Commit

Permalink
pythongh-111051: Check if file is modifed during debugging in pdb (p…
Browse files Browse the repository at this point in the history
  • Loading branch information
gaogaotiantian authored and aisk committed Feb 11, 2024
1 parent dfe71a7 commit ea38356
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 0 deletions.
21 changes: 21 additions & 0 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# but in case there are recursions, we stop at 999.
MAX_CHAINED_EXCEPTION_DEPTH = 999

_file_mtime_table = {}

def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
nosigint=False, readrc=True):
bdb.Bdb.__init__(self, skip=skip)
Expand Down Expand Up @@ -437,6 +439,20 @@ def _cmdloop(self):
except KeyboardInterrupt:
self.message('--KeyboardInterrupt--')

def _validate_file_mtime(self):
"""Check if the source file of the current frame has been modified since
the last time we saw it. If so, give a warning."""
try:
filename = self.curframe.f_code.co_filename
mtime = os.path.getmtime(filename)
except Exception:
return
if (filename in self._file_mtime_table and
mtime != self._file_mtime_table[filename]):
self.message(f"*** WARNING: file '{filename}' was edited, "
"running stale code until the program is rerun")
self._file_mtime_table[filename] = mtime

# Called before loop, handles display expressions
# Set up convenience variable containers
def preloop(self):
Expand Down Expand Up @@ -681,6 +697,7 @@ def onecmd(self, line):
a breakpoint command list definition.
"""
if not self.commands_defining:
self._validate_file_mtime()
return cmd.Cmd.onecmd(self, line)
else:
return self.handle_command_def(line)
Expand Down Expand Up @@ -2021,6 +2038,10 @@ def _run(self, target: Union[_ModuleTarget, _ScriptTarget]):
__main__.__dict__.clear()
__main__.__dict__.update(target.namespace)

# Clear the mtime table for program reruns, assume all the files
# are up to date.
self._file_mtime_table.clear()

self.run(target.code)

def _format_exc(self, exc: BaseException):
Expand Down
81 changes: 81 additions & 0 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3056,6 +3056,87 @@ def test_blocks_at_first_code_line(self):
self.assertTrue(any("__main__.py(4)<module>()"
in l for l in stdout.splitlines()), stdout)

def test_file_modified_after_execution(self):
script = """
print("hello")
"""

commands = """
filename = $_frame.f_code.co_filename
f = open(filename, "w")
f.write("print('goodbye')")
f.close()
ll
"""

stdout, stderr = self.run_pdb_script(script, commands)
self.assertIn("WARNING:", stdout)
self.assertIn("was edited", stdout)

def test_file_modified_after_execution_with_multiple_instances(self):
script = """
import pdb; pdb.Pdb().set_trace()
with open(__file__, "w") as f:
f.write("print('goodbye')\\n" * 5)
import pdb; pdb.Pdb().set_trace()
"""

commands = """
continue
continue
"""

filename = 'main.py'
with open(filename, 'w') as f:
f.write(textwrap.dedent(script))
self.addCleanup(os_helper.unlink, filename)
self.addCleanup(os_helper.rmtree, '__pycache__')
cmd = [sys.executable, filename]
with subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.STDOUT,
env = {**os.environ, 'PYTHONIOENCODING': 'utf-8'},
) as proc:
stdout, _ = proc.communicate(str.encode(commands))
stdout = stdout and bytes.decode(stdout)

self.assertEqual(proc.returncode, 0)
self.assertIn("WARNING:", stdout)
self.assertIn("was edited", stdout)

def test_file_modified_after_execution_with_restart(self):
script = """
import random
# Any code with a source to step into so this script is not checked
# for changes when it's being changed
random.randint(1, 4)
print("hello")
"""

commands = """
ll
n
s
filename = $_frame.f_back.f_code.co_filename
def change_file(content, filename):
with open(filename, "w") as f:
f.write(f"print({content})")
change_file('world', filename)
restart
ll
"""

stdout, stderr = self.run_pdb_script(script, commands)
# Make sure the code is running correctly and the file is edited
self.assertIn("hello", stdout)
self.assertIn("world", stdout)
# The file was edited, but restart should clear the state and consider
# the file as up to date
self.assertNotIn("WARNING:", stdout)

def test_relative_imports(self):
self.module_name = 't_main'
os_helper.rmtree(self.module_name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added check for file modification during debugging with :mod:`pdb`

0 comments on commit ea38356

Please sign in to comment.