From 604e4775a3f6d46086c610f4792dfd93b88275fa Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 18 Oct 2023 18:58:32 -0700 Subject: [PATCH 01/12] Check if file is modifed when debugging --- Lib/pdb.py | 17 +++++++++++++++++ Lib/test/test_pdb.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Lib/pdb.py b/Lib/pdb.py index 1e4d0a20515fa3..879a0abc574a8f 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -76,6 +76,7 @@ import dis import code import glob +import time import codeop import pprint import signal @@ -274,6 +275,8 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, self._chained_exceptions = tuple() self._chained_exception_index = 0 + self._file_mtime_table = {} + def sigint_handler(self, signum, frame): if self.allow_kbdint: raise KeyboardInterrupt @@ -425,6 +428,19 @@ def _cmdloop(self): except KeyboardInterrupt: self.message('--KeyboardInterrupt--') + def _validate_file_mtime(self): + """Check if any of the files loaded in pdb have 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}' changed after pdb started") + self._file_mtime_table[filename] = mtime + # Called before loop, handles display expressions # Set up convenience variable containers def preloop(self): @@ -626,6 +642,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) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 4701fa0cc9656a..91b77ee32fa2bf 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2867,6 +2867,23 @@ def test_blocks_at_first_code_line(self): self.assertTrue(any("__main__.py(4)()" 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("changed after pdb started", stdout) + def test_relative_imports(self): self.module_name = 't_main' os_helper.rmtree(self.module_name) From 87b5ba19461f3a754cc39a1f5e66d095f10ad5b6 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 02:08:13 +0000 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst 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 00000000000000..adb3241b89ae3e --- /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` From dbf669de0ed0ea070bfea916fccefea24aef4570 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 10 Dec 2023 22:44:54 -0800 Subject: [PATCH 03/12] Make _file_mtime_table class member --- Lib/pdb.py | 4 ++-- Lib/test/test_pdb.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 879a0abc574a8f..b5555a74517019 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -222,6 +222,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) @@ -275,8 +277,6 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, self._chained_exceptions = tuple() self._chained_exception_index = 0 - self._file_mtime_table = {} - def sigint_handler(self, signum, frame): if self.allow_kbdint: raise KeyboardInterrupt diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 91b77ee32fa2bf..9701beccc95623 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2884,6 +2884,38 @@ def test_file_modified_after_execution(self): self.assertIn("WARNING:", stdout) self.assertIn("changed after pdb started", 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, + ) as proc: + stdout, stderr = proc.communicate(str.encode(commands)) + stdout = stdout and bytes.decode(stdout) + + self.assertEqual(proc.returncode, 0) + self.assertIn("WARNING:", stdout) + self.assertIn("changed after pdb started", stdout) + def test_relative_imports(self): self.module_name = 't_main' os_helper.rmtree(self.module_name) From 1556acffd840a6ea88d4a76fda32d16f9b529212 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 11 Dec 2023 11:14:20 -0800 Subject: [PATCH 04/12] Need IO encoding on Windows --- Lib/test/test_pdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 9701beccc95623..e6602ee3ed9d79 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2908,8 +2908,9 @@ def test_file_modified_after_execution_with_multiple_instances(self): stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, + env = {**os.environ, 'PYTHONIOENCODING': 'utf-8'}, ) as proc: - stdout, stderr = proc.communicate(str.encode(commands)) + stdout, _ = proc.communicate(str.encode(commands)) stdout = stdout and bytes.decode(stdout) self.assertEqual(proc.returncode, 0) From 53c72a3a5f029f2e5c21d9d2fb109932f61a170f Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 13 Dec 2023 13:30:56 -0800 Subject: [PATCH 05/12] Remove dead import --- Lib/pdb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index b5555a74517019..f82805dcd32ce1 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -76,7 +76,6 @@ import dis import code import glob -import time import codeop import pprint import signal From d647d3c4378ff13964a37efff5e0c26109b89ae0 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 23 Jan 2024 09:37:56 -0800 Subject: [PATCH 06/12] Update comments & warnings --- Lib/pdb.py | 5 +++-- Lib/test/test_pdb.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index e3a5bab6717fd0..4665f9a5f9bc9b 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -439,7 +439,7 @@ def _cmdloop(self): self.message('--KeyboardInterrupt--') def _validate_file_mtime(self): - """Check if any of the files loaded in pdb have been modified since + """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 @@ -448,7 +448,8 @@ def _validate_file_mtime(self): return if (filename in self._file_mtime_table and mtime != self._file_mtime_table[filename]): - self.message(f"*** WARNING: file '{filename}' changed after pdb started") + self.message(f"*** WARNING: file '{filename}' was edited after pdb started, " + "running stale code") self._file_mtime_table[filename] = mtime # Called before loop, handles display expressions diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index f1de2f88222194..24a4fea4f78b68 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3058,7 +3058,7 @@ def test_file_modified_after_execution(self): stdout, stderr = self.run_pdb_script(script, commands) self.assertIn("WARNING:", stdout) - self.assertIn("changed after pdb started", stdout) + self.assertIn("was edited after pdb started", stdout) def test_file_modified_after_execution_with_multiple_instances(self): script = """ @@ -3091,7 +3091,7 @@ def test_file_modified_after_execution_with_multiple_instances(self): self.assertEqual(proc.returncode, 0) self.assertIn("WARNING:", stdout) - self.assertIn("changed after pdb started", stdout) + self.assertIn("was edited after pdb started", stdout) def test_relative_imports(self): self.module_name = 't_main' From 87a24558258a185bae51b4c956bf0836b4966da9 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 23 Jan 2024 12:05:12 -0800 Subject: [PATCH 07/12] Polish the warning message --- Lib/pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 4665f9a5f9bc9b..f0c46d7acb3904 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -449,7 +449,7 @@ def _validate_file_mtime(self): if (filename in self._file_mtime_table and mtime != self._file_mtime_table[filename]): self.message(f"*** WARNING: file '{filename}' was edited after pdb started, " - "running stale code") + "running stale code until restart") self._file_mtime_table[filename] = mtime # Called before loop, handles display expressions From f29ce4527add996a4d59e77948c958b4d22ea1f6 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 23 Jan 2024 12:40:04 -0800 Subject: [PATCH 08/12] Update warning message --- Lib/pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index f0c46d7acb3904..cbb3759796cf8d 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -449,7 +449,7 @@ def _validate_file_mtime(self): if (filename in self._file_mtime_table and mtime != self._file_mtime_table[filename]): self.message(f"*** WARNING: file '{filename}' was edited after pdb started, " - "running stale code until restart") + "running stale code until the program is rerun") self._file_mtime_table[filename] = mtime # Called before loop, handles display expressions From c890086d28ec2ac1756aa488d939d1a41e800e4d Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 23 Jan 2024 13:10:03 -0800 Subject: [PATCH 09/12] Clear mtime table for rerun --- Lib/pdb.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/pdb.py b/Lib/pdb.py index cbb3759796cf8d..48ae8d5133cca5 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -2004,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): From 0bba01ad3c90a492506e9ca83fe868921e61bdad Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Jan 2024 12:25:01 -0800 Subject: [PATCH 10/12] Add test for file modified + pdb restart --- Lib/test/test_pdb.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 24a4fea4f78b68..c42a457dac514a 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3093,6 +3093,37 @@ def test_file_modified_after_execution_with_multiple_instances(self): self.assertIn("WARNING:", stdout) self.assertIn("was edited after pdb started", 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) From b3c84954e40a965c547bdcb2d6a3a06031bb0c08 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Jan 2024 13:32:56 -0800 Subject: [PATCH 11/12] Update Lib/pdb.py Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Lib/pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 48ae8d5133cca5..1d5c5c364c5f6c 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -448,7 +448,7 @@ def _validate_file_mtime(self): return if (filename in self._file_mtime_table and mtime != self._file_mtime_table[filename]): - self.message(f"*** WARNING: file '{filename}' was edited after pdb started, " + self.message(f"*** WARNING: file '{filename}' was edited, " "running stale code until the program is rerun") self._file_mtime_table[filename] = mtime From c53690627f0dbe931342541a20c3ec118de2a5af Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Jan 2024 15:39:51 -0800 Subject: [PATCH 12/12] Fix test for warning message --- Lib/test/test_pdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c42a457dac514a..4db317428ce201 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3058,7 +3058,7 @@ def test_file_modified_after_execution(self): stdout, stderr = self.run_pdb_script(script, commands) self.assertIn("WARNING:", stdout) - self.assertIn("was edited after pdb started", stdout) + self.assertIn("was edited", stdout) def test_file_modified_after_execution_with_multiple_instances(self): script = """ @@ -3091,7 +3091,7 @@ def test_file_modified_after_execution_with_multiple_instances(self): self.assertEqual(proc.returncode, 0) self.assertIn("WARNING:", stdout) - self.assertIn("was edited after pdb started", stdout) + self.assertIn("was edited", stdout) def test_file_modified_after_execution_with_restart(self): script = """