10000 gh-111051: Check if file is modifed during debugging in `pdb` by gaogaotiantian · Pull Request #111052 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content
< 10000 div class="position-relative js-review-state-classes js-suggested-changes-files-tab" data-pjax data-discussion-hovercards-enabled data-issue-and-pr-hovercards-enabled >

gh-111051: Check if file is modifed during debugging in pdb #111052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 25, 2024
Merged
21 changes: 21 additions & 0 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,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 @@ -436,6 +438,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 @@ -647,6 +663,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 @@ -1987,6 +2004,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 @@ -3043,6 +3043,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