diff --git a/Lib/pdb.py b/Lib/pdb.py index 4abf216b773780..f97c1b3019475b 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1134,6 +1134,7 @@ def do_break(self, arg, temporary=False): filename = None lineno = None cond = None + module_globals = None comma = arg.find(',') if comma > 0: # parse stuff after comma: "condition" @@ -1179,6 +1180,7 @@ def do_break(self, arg, temporary=False): funcname = code.co_name lineno = find_first_executable_line(code) filename = code.co_filename + module_globals = func.__globals__ except: # last thing to try (ok, filename, ln) = self.lineinfo(arg) @@ -1190,8 +1192,9 @@ def do_break(self, arg, temporary=False): lineno = int(ln) if not filename: filename = self.defaultFile() + filename = self.canonic(filename) # Check for reasonable breakpoint - line = self.checkline(filename, lineno) + line = self.checkline(filename, lineno, module_globals) if line: # now set the break point err = self.set_break(filename, line, temporary, cond, funcname) @@ -1258,7 +1261,7 @@ def lineinfo(self, identifier): answer = find_function(item, self.canonic(fname)) return answer or failed - def checkline(self, filename, lineno): + def checkline(self, filename, lineno, module_globals=None): """Check whether specified line seems to be executable. Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank @@ -1267,8 +1270,9 @@ def checkline(self, filename, lineno): # this method should be callable before starting debugging, so default # to "no globals" if there is no current frame frame = getattr(self, 'curframe', None) - globs = frame.f_globals if frame else None - line = linecache.getline(filename, lineno, globs) + if module_globals is None: + module_globals = frame.f_globals if frame else None + line = linecache.getline(filename, lineno, module_globals) if not line: self.message('End of file') return 0 diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 4d371a6e754b96..d253d7fe1e8109 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -11,6 +11,7 @@ import textwrap import linecache import zipapp +import zipfile from contextlib import ExitStack, redirect_stdout from io import StringIO @@ -4199,6 +4200,38 @@ def f(x): self.assertIn('42', stdout) self.assertIn('return x + 1', stdout) + def test_zipimport(self): + with os_helper.temp_dir() as temp_dir: + os.mkdir(os.path.join(temp_dir, 'source')) + zipmodule = textwrap.dedent( + """ + def bar(): + pass + """ + ) + script = textwrap.dedent( + f""" + import sys; sys.path.insert(0, {repr(os.path.join(temp_dir, 'zipmodule.zip'))}) + import foo + foo.bar() + """ + ) + + with zipfile.ZipFile(os.path.join(temp_dir, 'zipmodule.zip'), 'w') as zf: + zf.writestr('foo.py', zipmodule) + with open(os.path.join(temp_dir, 'script.py'), 'w') as f: + f.write(script) + + stdout, _ = self._run_pdb([os.path.join(temp_dir, 'script.py')], '\n'.join([ + 'n', + 'n', + 'b foo.bar', + 'c', + 'p f"break in {$_frame.f_code.co_name}"', + 'q' + ])) + self.assertIn('break in bar', stdout) + class ChecklineTests(unittest.TestCase): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2025-02-19-01-29-16.gh-issue-57537.4tdVuK.rst b/Misc/NEWS.d/next/Library/2025-02-19-01-29-16.gh-issue-57537.4tdVuK.rst new file mode 100644 index 00000000000000..40e4094cc5fccf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-19-01-29-16.gh-issue-57537.4tdVuK.rst @@ -0,0 +1 @@ +Support breakpoints for :mod:`zipimport` modules on :mod:`pdb`