Better behavior when stepping over yield[from]. Fixes issue 16596. By Xavier de Gaye.
This commit is contained in:
parent
9c55a58a1d
commit
8820c239f7
32
Lib/bdb.py
32
Lib/bdb.py
|
@ -3,6 +3,7 @@
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
from inspect import CO_GENERATOR
|
||||||
|
|
||||||
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
|
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
|
||||||
|
|
||||||
|
@ -75,24 +76,48 @@ class Bdb:
|
||||||
if not (self.stop_here(frame) or self.break_anywhere(frame)):
|
if not (self.stop_here(frame) or self.break_anywhere(frame)):
|
||||||
# No need to trace this function
|
# No need to trace this function
|
||||||
return # None
|
return # None
|
||||||
|
# Ignore call events in generator except when stepping.
|
||||||
|
if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
|
||||||
|
return self.trace_dispatch
|
||||||
self.user_call(frame, arg)
|
self.user_call(frame, arg)
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
|
|
||||||
def dispatch_return(self, frame, arg):
|
def dispatch_return(self, frame, arg):
|
||||||
if self.stop_here(frame) or frame == self.returnframe:
|
if self.stop_here(frame) or frame == self.returnframe:
|
||||||
|
# Ignore return events in generator except when stepping.
|
||||||
|
if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
|
||||||
|
return self.trace_dispatch
|
||||||
try:
|
try:
|
||||||
self.frame_returning = frame
|
self.frame_returning = frame
|
||||||
self.user_return(frame, arg)
|
self.user_return(frame, arg)
|
||||||
finally:
|
finally:
|
||||||
self.frame_returning = None
|
self.frame_returning = None
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
|
# The user issued a 'next' or 'until' command.
|
||||||
|
if self.stopframe is frame and self.stoplineno != -1:
|
||||||
|
self._set_stopinfo(None, None)
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
|
|
||||||
def dispatch_exception(self, frame, arg):
|
def dispatch_exception(self, frame, arg):
|
||||||
if self.stop_here(frame):
|
if self.stop_here(frame):
|
||||||
|
# When stepping with next/until/return in a generator frame, skip
|
||||||
|
# the internal StopIteration exception (with no traceback)
|
||||||
|
# triggered by a subiterator run with the 'yield from' statement.
|
||||||
|
if not (frame.f_code.co_flags & CO_GENERATOR
|
||||||
|
and arg[0] is StopIteration and arg[2] is None):
|
||||||
self.user_exception(frame, arg)
|
self.user_exception(frame, arg)
|
||||||
if self.quitting: raise BdbQuit
|
if self.quitting: raise BdbQuit
|
||||||
|
# Stop at the StopIteration or GeneratorExit exception when the user
|
||||||
|
# has set stopframe in a generator by issuing a return command, or a
|
||||||
|
# next/until command at the last statement in the generator before the
|
||||||
|
# exception.
|
||||||
|
elif (self.stopframe and frame is not self.stopframe
|
||||||
|
and self.stopframe.f_code.co_flags & CO_GENERATOR
|
||||||
|
and arg[0] in (StopIteration, GeneratorExit)):
|
||||||
|
self.user_exception(frame, arg)
|
||||||
|
if self.quitting: raise BdbQuit
|
||||||
|
|
||||||
return self.trace_dispatch
|
return self.trace_dispatch
|
||||||
|
|
||||||
# Normally derived classes don't override the following
|
# Normally derived classes don't override the following
|
||||||
|
@ -115,10 +140,8 @@ class Bdb:
|
||||||
if self.stoplineno == -1:
|
if self.stoplineno == -1:
|
||||||
return False
|
return False
|
||||||
return frame.f_lineno >= self.stoplineno
|
return frame.f_lineno >= self.stoplineno
|
||||||
while frame is not None and frame is not self.stopframe:
|
if not self.stopframe:
|
||||||
if frame is self.botframe:
|
|
||||||
return True
|
return True
|
||||||
frame = frame.f_back
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def break_here(self, frame):
|
def break_here(self, frame):
|
||||||
|
@ -207,6 +230,9 @@ class Bdb:
|
||||||
|
|
||||||
def set_return(self, frame):
|
def set_return(self, frame):
|
||||||
"""Stop when returning from the given frame."""
|
"""Stop when returning from the given frame."""
|
||||||
|
if frame.f_code.co_flags & CO_GENERATOR:
|
||||||
|
self._set_stopinfo(frame, None, -1)
|
||||||
|
else:
|
||||||
self._set_stopinfo(frame.f_back, frame)
|
self._set_stopinfo(frame.f_back, frame)
|
||||||
|
|
||||||
def set_trace(self, frame=None):
|
def set_trace(self, frame=None):
|
||||||
|
|
12
Lib/pdb.py
12
Lib/pdb.py
|
@ -297,8 +297,16 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
||||||
return
|
return
|
||||||
exc_type, exc_value, exc_traceback = exc_info
|
exc_type, exc_value, exc_traceback = exc_info
|
||||||
frame.f_locals['__exception__'] = exc_type, exc_value
|
frame.f_locals['__exception__'] = exc_type, exc_value
|
||||||
self.message(traceback.format_exception_only(exc_type,
|
|
||||||
exc_value)[-1].strip())
|
# An 'Internal StopIteration' exception is an exception debug event
|
||||||
|
# issued by the interpreter when handling a subgenerator run with
|
||||||
|
# 'yield from' or a generator controled by a for loop. No exception has
|
||||||
|
# actually occured in this case. The debugger uses this debug event to
|
||||||
|
# stop when the debuggee is returning from such generators.
|
||||||
|
prefix = 'Internal ' if (not exc_traceback
|
||||||
|
and exc_type is StopIteration) else ''
|
||||||
|
self.message('%s%s' % (prefix,
|
||||||
|
traceback.format_exception_only(exc_type, exc_value)[-1].strip()))
|
||||||
self.interaction(frame, exc_traceback)
|
self.interaction(frame, exc_traceback)
|
||||||
|
|
||||||
# General interaction function
|
# General interaction function
|
||||||
|
|
|
@ -600,6 +600,311 @@ def test_pdb_run_with_code_object():
|
||||||
(Pdb) continue
|
(Pdb) continue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def test_next_until_return_at_return_event():
|
||||||
|
"""Test that pdb stops after a next/until/return issued at a return debug event.
|
||||||
|
|
||||||
|
>>> def test_function_2():
|
||||||
|
... x = 1
|
||||||
|
... x = 2
|
||||||
|
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True).set_trace()
|
||||||
|
... test_function_2()
|
||||||
|
... test_function_2()
|
||||||
|
... test_function_2()
|
||||||
|
... end = 1
|
||||||
|
|
||||||
|
>>> with PdbTestInput(['break test_function_2',
|
||||||
|
... 'continue',
|
||||||
|
... 'return',
|
||||||
|
... 'next',
|
||||||
|
... 'continue',
|
||||||
|
... 'return',
|
||||||
|
... 'until',
|
||||||
|
... 'continue',
|
||||||
|
... 'return',
|
||||||
|
... 'return',
|
||||||
|
... 'continue']):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(3)test_function()
|
||||||
|
-> test_function_2()
|
||||||
|
(Pdb) break test_function_2
|
||||||
|
Breakpoint 1 at <doctest test.test_pdb.test_next_until_return_at_return_event[0]>:1
|
||||||
|
(Pdb) continue
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(2)test_function_2()
|
||||||
|
-> x = 1
|
||||||
|
(Pdb) return
|
||||||
|
--Return--
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(3)test_function_2()->None
|
||||||
|
-> x = 2
|
||||||
|
(Pdb) next
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(4)test_function()
|
||||||
|
-> test_function_2()
|
||||||
|
(Pdb) continue
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(2)test_function_2()
|
||||||
|
-> x = 1
|
||||||
|
(Pdb) return
|
||||||
|
--Return--
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(3)test_function_2()->None
|
||||||
|
-> x = 2
|
||||||
|
(Pdb) until
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(5)test_function()
|
||||||
|
-> test_function_2()
|
||||||
|
(Pdb) continue
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(2)test_function_2()
|
||||||
|
-> x = 1
|
||||||
|
(Pdb) return
|
||||||
|
--Return--
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(3)test_function_2()->None
|
||||||
|
-> x = 2
|
||||||
|
(Pdb) return
|
||||||
|
> <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(6)test_function()
|
||||||
|
-> end = 1
|
||||||
|
(Pdb) continue
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pdb_next_command_for_generator():
|
||||||
|
"""Testing skip unwindng stack on yield for generators for "next" command
|
||||||
|
|
||||||
|
>>> def test_gen():
|
||||||
|
... yield 0
|
||||||
|
... return 1
|
||||||
|
... yield 2
|
||||||
|
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True).set_trace()
|
||||||
|
... it = test_gen()
|
||||||
|
... try:
|
||||||
|
... assert next(it) == 0
|
||||||
|
... next(it)
|
||||||
|
... except StopIteration as ex:
|
||||||
|
... assert ex.value == 1
|
||||||
|
... print("finished")
|
||||||
|
|
||||||
|
>>> with PdbTestInput(['step',
|
||||||
|
... 'step',
|
||||||
|
... 'step',
|
||||||
|
... 'next',
|
||||||
|
... 'next',
|
||||||
|
... 'step',
|
||||||
|
... 'step',
|
||||||
|
... 'continue']):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(3)test_function()
|
||||||
|
-> it = test_gen()
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(4)test_function()
|
||||||
|
-> try:
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(5)test_function()
|
||||||
|
-> assert next(it) == 0
|
||||||
|
(Pdb) step
|
||||||
|
--Call--
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(1)test_gen()
|
||||||
|
-> def test_gen():
|
||||||
|
(Pdb) next
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(2)test_gen()
|
||||||
|
-> yield 0
|
||||||
|
(Pdb) next
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(3)test_gen()
|
||||||
|
-> return 1
|
||||||
|
(Pdb) step
|
||||||
|
--Return--
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(3)test_gen()->1
|
||||||
|
-> return 1
|
||||||
|
(Pdb) step
|
||||||
|
StopIteration: 1
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(6)test_function()
|
||||||
|
-> next(it)
|
||||||
|
(Pdb) continue
|
||||||
|
finished
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pdb_return_command_for_generator():
|
||||||
|
"""Testing no unwindng stack on yield for generators
|
||||||
|
for "return" command
|
||||||
|
|
||||||
|
>>> def test_gen():
|
||||||
|
... yield 0
|
||||||
|
... return 1
|
||||||
|
... yield 2
|
||||||
|
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True).set_trace()
|
||||||
|
... it = test_gen()
|
||||||
|
... try:
|
||||||
|
... assert next(it) == 0
|
||||||
|
... next(it)
|
||||||
|
... except StopIteration as ex:
|
||||||
|
... assert ex.value == 1
|
||||||
|
... print("finished")
|
||||||
|
|
||||||
|
>>> with PdbTestInput(['step',
|
||||||
|
... 'step',
|
||||||
|
... 'step',
|
||||||
|
... 'return',
|
||||||
|
... 'step',
|
||||||
|
... 'step',
|
||||||
|
... 'continue']):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(3)test_function()
|
||||||
|
-> it = test_gen()
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(4)test_function()
|
||||||
|
-> try:
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(5)test_function()
|
||||||
|
-> assert next(it) == 0
|
||||||
|
(Pdb) step
|
||||||
|
--Call--
|
||||||
|
> <doctest test.test_pdb.test_pdb_return_command_for_generator[0]>(1)test_gen()
|
||||||
|
-> def test_gen():
|
||||||
|
(Pdb) return
|
||||||
|
StopIteration: 1
|
||||||
|
> <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(6)test_function()
|
||||||
|
-> next(it)
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(7)test_function()
|
||||||
|
-> except StopIteration as ex:
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(8)test_function()
|
||||||
|
-> assert ex.value == 1
|
||||||
|
(Pdb) continue
|
||||||
|
finished
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pdb_until_command_for_generator():
|
||||||
|
"""Testing no unwindng stack on yield for generators
|
||||||
|
for "until" command if target breakpoing is not reached
|
||||||
|
|
||||||
|
>>> def test_gen():
|
||||||
|
... yield 0
|
||||||
|
... yield 1
|
||||||
|
... yield 2
|
||||||
|
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True).set_trace()
|
||||||
|
... for i in test_gen():
|
||||||
|
... print(i)
|
||||||
|
... print("finished")
|
||||||
|
|
||||||
|
>>> with PdbTestInput(['step',
|
||||||
|
... 'until 4',
|
||||||
|
... 'step',
|
||||||
|
... 'step',
|
||||||
|
... 'continue']):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_pdb_until_command_for_generator[1]>(3)test_function()
|
||||||
|
-> for i in test_gen():
|
||||||
|
(Pdb) step
|
||||||
|
--Call--
|
||||||
|
> <doctest test.test_pdb.test_pdb_until_command_for_generator[0]>(1)test_gen()
|
||||||
|
-> def test_gen():
|
||||||
|
(Pdb) until 4
|
||||||
|
0
|
||||||
|
1
|
||||||
|
> <doctest test.test_pdb.test_pdb_until_command_for_generator[0]>(4)test_gen()
|
||||||
|
-> yield 2
|
||||||
|
(Pdb) step
|
||||||
|
--Return--
|
||||||
|
> <doctest test.test_pdb.test_pdb_until_command_for_generator[0]>(4)test_gen()->2
|
||||||
|
-> yield 2
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_until_command_for_generator[1]>(4)test_function()
|
||||||
|
-> print(i)
|
||||||
|
(Pdb) continue
|
||||||
|
2
|
||||||
|
finished
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pdb_next_command_in_generator_for_loop():
|
||||||
|
"""The next command on returning from a generator controled by a for loop.
|
||||||
|
|
||||||
|
>>> def test_gen():
|
||||||
|
... yield 0
|
||||||
|
... return 1
|
||||||
|
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True).set_trace()
|
||||||
|
... for i in test_gen():
|
||||||
|
... print('value', i)
|
||||||
|
... x = 123
|
||||||
|
|
||||||
|
>>> with PdbTestInput(['break test_gen',
|
||||||
|
... 'continue',
|
||||||
|
... 'next',
|
||||||
|
... 'next',
|
||||||
|
... 'next',
|
||||||
|
... 'continue']):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[1]>(3)test_function()
|
||||||
|
-> for i in test_gen():
|
||||||
|
(Pdb) break test_gen
|
||||||
|
Breakpoint 6 at <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>:1
|
||||||
|
(Pdb) continue
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>(2)test_gen()
|
||||||
|
-> yield 0
|
||||||
|
(Pdb) next
|
||||||
|
value 0
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>(3)test_gen()
|
||||||
|
-> return 1
|
||||||
|
(Pdb) next
|
||||||
|
Internal StopIteration: 1
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[1]>(3)test_function()
|
||||||
|
-> for i in test_gen():
|
||||||
|
(Pdb) next
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[1]>(5)test_function()
|
||||||
|
-> x = 123
|
||||||
|
(Pdb) continue
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_pdb_next_command_subiterator():
|
||||||
|
"""The next command in a generator with a subiterator.
|
||||||
|
|
||||||
|
>>> def test_subgenerator():
|
||||||
|
... yield 0
|
||||||
|
... return 1
|
||||||
|
|
||||||
|
>>> def test_gen():
|
||||||
|
... x = yield from test_subgenerator()
|
||||||
|
... return x
|
||||||
|
|
||||||
|
>>> def test_function():
|
||||||
|
... import pdb; pdb.Pdb(nosigint=True).set_trace()
|
||||||
|
... for i in test_gen():
|
||||||
|
... print('value', i)
|
||||||
|
... x = 123
|
||||||
|
|
||||||
|
>>> with PdbTestInput(['step',
|
||||||
|
... 'step',
|
||||||
|
... 'next',
|
||||||
|
... 'next',
|
||||||
|
... 'next',
|
||||||
|
... 'continue']):
|
||||||
|
... test_function()
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_subiterator[2]>(3)test_function()
|
||||||
|
-> for i in test_gen():
|
||||||
|
(Pdb) step
|
||||||
|
--Call--
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_subiterator[1]>(1)test_gen()
|
||||||
|
-> def test_gen():
|
||||||
|
(Pdb) step
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_subiterator[1]>(2)test_gen()
|
||||||
|
-> x = yield from test_subgenerator()
|
||||||
|
(Pdb) next
|
||||||
|
value 0
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_subiterator[1]>(3)test_gen()
|
||||||
|
-> return x
|
||||||
|
(Pdb) next
|
||||||
|
Internal StopIteration: 1
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_subiterator[2]>(3)test_function()
|
||||||
|
-> for i in test_gen():
|
||||||
|
(Pdb) next
|
||||||
|
> <doctest test.test_pdb.test_pdb_next_command_subiterator[2]>(5)test_function()
|
||||||
|
-> x = 123
|
||||||
|
(Pdb) continue
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class PdbTestCase(unittest.TestCase):
|
class PdbTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -1904,6 +1904,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
||||||
Py_DECREF(v);
|
Py_DECREF(v);
|
||||||
if (retval == NULL) {
|
if (retval == NULL) {
|
||||||
PyObject *val;
|
PyObject *val;
|
||||||
|
if (tstate->c_tracefunc != NULL
|
||||||
|
&& PyErr_ExceptionMatches(PyExc_StopIteration))
|
||||||
|
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, f);
|
||||||
err = _PyGen_FetchStopIterationValue(&val);
|
err = _PyGen_FetchStopIterationValue(&val);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -2654,6 +2657,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
if (!PyErr_ExceptionMatches(PyExc_StopIteration))
|
if (!PyErr_ExceptionMatches(PyExc_StopIteration))
|
||||||
goto error;
|
goto error;
|
||||||
|
else if (tstate->c_tracefunc != NULL)
|
||||||
|
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, f);
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
/* iterator ended normally */
|
/* iterator ended normally */
|
||||||
|
|
Loading…
Reference in New Issue