summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTian Gao <gaogaotiantian@hotmail.com>2024-01-25 16:48:50 (GMT)
committerGitHub <noreply@github.com>2024-01-25 16:48:50 (GMT)
commit8278fa2f5625b41be91191d18ee8eeab904a54ff (patch)
treec707422c5a02db7218da3c1e62bf5097ebfd53c7
parent07ef63fb6a0fb996d5f56c79f4ccd7a1887a6b2b (diff)
downloadcpython-8278fa2f5625b41be91191d18ee8eeab904a54ff.zip
cpython-8278fa2f5625b41be91191d18ee8eeab904a54ff.tar.gz
cpython-8278fa2f5625b41be91191d18ee8eeab904a54ff.tar.bz2
gh-111051: Check if file is modifed during debugging in `pdb` (#111052)
-rwxr-xr-xLib/pdb.py21
-rw-r--r--Lib/test/test_pdb.py81
-rw-r--r--Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst1
3 files changed, 103 insertions, 0 deletions
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 68f8106..6f7719e 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -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)
@@ -437,6 +439,20 @@ class Pdb(bdb.Bdb, cmd.Cmd):
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):
@@ -681,6 +697,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
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)
@@ -2021,6 +2038,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
__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):
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 03487aa..c64df62 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -3056,6 +3056,87 @@ def bœr():
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)
diff --git a/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst b/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst
new file mode 100644
index 0000000..adb3241
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst
@@ -0,0 +1 @@
+Added check for file modification during debugging with :mod:`pdb`