[3.14] gh-136057: Allow step and next to step over for loops (GH-136160) (#141640)
Some checks are pending
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Check if the ABI has changed (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run

gh-136057: Allow step and next to step over for loops (GH-136160)
(cherry picked from commit 8be3b2f479)

Co-authored-by: Tian Gao <gaogaotiantian@hotmail.com>
This commit is contained in:
Miss Islington (bot) 2025-11-16 23:22:11 +01:00 committed by GitHub
parent 0d8fb0b852
commit eead7b43bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 50 additions and 4 deletions

View file

@ -199,6 +199,8 @@ class Bdb:
self.frame_returning = None self.frame_returning = None
self.trace_opcodes = False self.trace_opcodes = False
self.enterframe = None self.enterframe = None
self.cmdframe = None
self.cmdlineno = None
self.code_linenos = weakref.WeakKeyDictionary() self.code_linenos = weakref.WeakKeyDictionary()
self.backend = backend self.backend = backend
if backend == 'monitoring': if backend == 'monitoring':
@ -306,7 +308,12 @@ class Bdb:
self.user_line(). Raise BdbQuit if self.quitting is set. self.user_line(). Raise BdbQuit if self.quitting is set.
Return self.trace_dispatch to continue tracing in this scope. Return self.trace_dispatch to continue tracing in this scope.
""" """
if self.stop_here(frame) or self.break_here(frame): # GH-136057
# For line events, we don't want to stop at the same line where
# the latest next/step command was issued.
if (self.stop_here(frame) or self.break_here(frame)) and not (
self.cmdframe == frame and self.cmdlineno == frame.f_lineno
):
self.user_line(frame) self.user_line(frame)
self.restart_events() self.restart_events()
if self.quitting: raise BdbQuit if self.quitting: raise BdbQuit
@ -535,7 +542,8 @@ class Bdb:
if self.monitoring_tracer: if self.monitoring_tracer:
self.monitoring_tracer.update_local_events() self.monitoring_tracer.update_local_events()
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False): def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False,
cmdframe=None, cmdlineno=None):
"""Set the attributes for stopping. """Set the attributes for stopping.
If stoplineno is greater than or equal to 0, then stop at line If stoplineno is greater than or equal to 0, then stop at line
@ -548,6 +556,10 @@ class Bdb:
# stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno >= 0 means: stop at line >= the stoplineno
# stoplineno -1 means: don't stop at all # stoplineno -1 means: don't stop at all
self.stoplineno = stoplineno self.stoplineno = stoplineno
# cmdframe/cmdlineno is the frame/line number when the user issued
# step/next commands.
self.cmdframe = cmdframe
self.cmdlineno = cmdlineno
self._set_trace_opcodes(opcode) self._set_trace_opcodes(opcode)
def _set_caller_tracefunc(self, current_frame): def _set_caller_tracefunc(self, current_frame):
@ -573,7 +585,9 @@ class Bdb:
def set_step(self): def set_step(self):
"""Stop after one line of code.""" """Stop after one line of code."""
self._set_stopinfo(None, None) # set_step() could be called from signal handler so enterframe might be None
self._set_stopinfo(None, None, cmdframe=self.enterframe,
cmdlineno=getattr(self.enterframe, 'f_lineno', None))
def set_stepinstr(self): def set_stepinstr(self):
"""Stop before the next instruction.""" """Stop before the next instruction."""
@ -581,7 +595,7 @@ class Bdb:
def set_next(self, frame): def set_next(self, frame):
"""Stop on the next line in or below the given frame.""" """Stop on the next line in or below the given frame."""
self._set_stopinfo(frame, None) self._set_stopinfo(frame, None, cmdframe=frame, cmdlineno=frame.f_lineno)
def set_return(self, frame): def set_return(self, frame):
"""Stop when returning from the given frame.""" """Stop when returning from the given frame."""

View file

@ -3232,6 +3232,37 @@ def test_pdb_issue_gh_127321():
""" """
def test_pdb_issue_gh_136057():
"""See GH-136057
"step" and "next" commands should be able to get over list comprehensions
>>> def test_function():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
... lst = [i for i in range(10)]
... for i in lst: pass
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... 'next',
... 'next',
... 'step',
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(2)test_function()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) next
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(3)test_function()
-> lst = [i for i in range(10)]
(Pdb) next
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(4)test_function()
-> for i in lst: pass
(Pdb) step
--Return--
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(4)test_function()->None
-> for i in lst: pass
(Pdb) continue
"""
def test_pdb_issue_gh_80731(): def test_pdb_issue_gh_80731():
"""See GH-80731 """See GH-80731

View file

@ -0,0 +1 @@
Fixed the bug in :mod:`pdb` and :mod:`bdb` where ``next`` and ``step`` can't go over the line if a loop exists in the line.