Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8278fa2

Browse files
gh-111051: Check if file is modifed during debugging in pdb (#111052)
1 parent 07ef63f commit 8278fa2

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

Lib/pdb.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
233233
# but in case there are recursions, we stop at 999.
234234
MAX_CHAINED_EXCEPTION_DEPTH = 999
235235

236+
_file_mtime_table = {}
237+
236238
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
237239
nosigint=False, readrc=True):
238240
bdb.Bdb.__init__(self, skip=skip)
@@ -437,6 +439,20 @@ def _cmdloop(self):
437439
except KeyboardInterrupt:
438440
self.message('--KeyboardInterrupt--')
439441

442+
def _validate_file_mtime(self):
443+
"""Check if the source file of the current frame has been modified since
444+
the last time we saw it. If so, give a warning."""
445+
try:
446+
filename = self.curframe.f_code.co_filename
447+
mtime = os.path.getmtime(filename)
448+
except Exception:
449+
return
450+
if (filename in self._file_mtime_table and
451+
mtime != self._file_mtime_table[filename]):
452+
self.message(f"*** WARNING: file '{filename}' was edited, "
453+
"running stale code until the program is rerun")
454+
self._file_mtime_table[filename] = mtime
455+
440456
# Called before loop, handles display expressions
441457
# Set up convenience variable containers
442458
def preloop(self):
@@ -681,6 +697,7 @@ def onecmd(self, line):
681697
a breakpoint command list definition.
682698
"""
683699
if not self.commands_defining:
700+
self._validate_file_mtime()
684701
return cmd.Cmd.onecmd(self, line)
685702
else:
686703
return self.handle_command_def(line)
@@ -2021,6 +2038,10 @@ def _run(self, target: Union[_ModuleTarget, _ScriptTarget]):
20212038
__main__.__dict__.clear()
20222039
__main__.__dict__.update(target.namespace)
20232040

2041+
# Clear the mtime table for program reruns, assume all the files
2042+
# are up to date.
2043+
self._file_mtime_table.clear()
2044+
20242045
self.run(target.code)
20252046

20262047
def _format_exc(self, exc: BaseException):

Lib/test/test_pdb.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3056,6 +3056,87 @@ def test_blocks_at_first_code_line(self):
30563056
self.assertTrue(any("__main__.py(4)<module>()"
30573057
in l for l in stdout.splitlines()), stdout)
30583058

3059+
def test_file_modified_after_execution(self):
3060+
script = """
3061+
print("hello")
3062+
"""
3063+
3064+
commands = """
3065+
filename = $_frame.f_code.co_filename
3066+
f = open(filename, "w")
3067+
f.write("print('goodbye')")
3068+
f.close()
3069+
ll
3070+
"""
3071+
3072+
stdout, stderr = self.run_pdb_script(script, commands)
3073+
self.assertIn("WARNING:", stdout)
3074+
self.assertIn("was edited", stdout)
3075+
3076+
def test_file_modified_after_execution_with_multiple_instances(self):
3077+
script = """
3078+
import pdb; pdb.Pdb().set_trace()
3079+
with open(__file__, "w") as f:
3080+
f.write("print('goodbye')\\n" * 5)
3081+
import pdb; pdb.Pdb().set_trace()
3082+
"""
3083+
3084+
commands = """
3085+
continue
3086+
continue
3087+
"""
3088+
3089+
filename = 'main.py'
3090+
with open(filename, 'w') as f:
3091+
f.write(textwrap.dedent(script))
3092+
self.addCleanup(os_helper.unlink, filename)
3093+
self.addCleanup(os_helper.rmtree, '__pycache__')
3094+
cmd = [sys.executable, filename]
3095+
with subprocess.Popen(
3096+
cmd,
3097+
stdout=subprocess.PIPE,
3098+
stdin=subprocess.PIPE,
3099+
stderr=subprocess.STDOUT,
3100+
env = {**os.environ, 'PYTHONIOENCODING': 'utf-8'},
3101+
) as proc:
3102+
stdout, _ = proc.communicate(str.encode(commands))
3103+
stdout = stdout and bytes.decode(stdout)
3104+
3105+
self.assertEqual(proc.returncode, 0)
3106+
self.assertIn("WARNING:", stdout)
3107+
self.assertIn("was edited", stdout)
3108+
3109+
def test_file_modified_after_execution_with_restart(self):
3110+
script = """
3111+
import random
3112+
# Any code with a source to step into so this script is not checked
3113+
# for changes when it's being changed
3114+
random.randint(1, 4)
3115+
print("hello")
3116+
"""
3117+
3118+
commands = """
3119+
ll
3120+
n
3121+
s
3122+
filename = $_frame.f_back.f_code.co_filename
3123+
def change_file(content, filename):
3124+
with open(filename, "w") as f:
3125+
f.write(f"print({content})")
3126+
3127+
change_file('world', filename)
3128+
restart
3129+
ll
3130+
"""
3131+
3132+
stdout, stderr = self.run_pdb_script(script, commands)
3133+
# Make sure the code is running correctly and the file is edited
3134+
self.assertIn("hello", stdout)
3135+
self.assertIn("world", stdout)
3136+
# The file was edited, but restart should clear the state and consider
3137+
# the file as up to date
3138+
self.assertNotIn("WARNING:", stdout)
3139+
30593140
def test_relative_imports(self):
30603141
self.module_name = 't_main'
30613142
os_helper.rmtree(self.module_name)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added check for file modification during debugging with :mod:`pdb`

0 commit comments

Comments
 (0)