mirror of https://github.com/python/cpython
bpo-29590: fix stack trace for gen.throw() with yield from (#19896)
* Add failing test. * bpo-29590: fix stack trace for gen.throw() with yield from (GH-NNNN) When gen.throw() is called on a generator after a "yield from", the intermediate stack trace entries are lost. This commit fixes that.
This commit is contained in:
parent
96a6a6d42b
commit
8b33961e4b
|
@ -415,6 +415,55 @@ class GeneratorThrowTest(unittest.TestCase):
|
|||
gen.throw(ValueError)
|
||||
|
||||
|
||||
class GeneratorStackTraceTest(unittest.TestCase):
|
||||
|
||||
def check_stack_names(self, frame, expected):
|
||||
names = []
|
||||
while frame:
|
||||
name = frame.f_code.co_name
|
||||
# Stop checking frames when we get to our test helper.
|
||||
if name.startswith('check_') or name.startswith('call_'):
|
||||
break
|
||||
|
||||
names.append(name)
|
||||
frame = frame.f_back
|
||||
|
||||
self.assertEqual(names, expected)
|
||||
|
||||
def check_yield_from_example(self, call_method):
|
||||
def f():
|
||||
self.check_stack_names(sys._getframe(), ['f', 'g'])
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
pass
|
||||
self.check_stack_names(sys._getframe(), ['f', 'g'])
|
||||
|
||||
def g():
|
||||
self.check_stack_names(sys._getframe(), ['g'])
|
||||
yield from f()
|
||||
self.check_stack_names(sys._getframe(), ['g'])
|
||||
|
||||
gen = g()
|
||||
gen.send(None)
|
||||
try:
|
||||
call_method(gen)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
def test_send_with_yield_from(self):
|
||||
def call_send(gen):
|
||||
gen.send(None)
|
||||
|
||||
self.check_yield_from_example(call_send)
|
||||
|
||||
def test_throw_with_yield_from(self):
|
||||
def call_throw(gen):
|
||||
gen.throw(RuntimeError)
|
||||
|
||||
self.check_yield_from_example(call_throw)
|
||||
|
||||
|
||||
class YieldFromTests(unittest.TestCase):
|
||||
def test_generator_gi_yieldfrom(self):
|
||||
def a():
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Make the stack trace correct after calling :meth:`generator.throw`
|
||||
on a generator that has yielded from a ``yield from``.
|
|
@ -415,11 +415,21 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
|
|||
}
|
||||
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
|
||||
/* `yf` is a generator or a coroutine. */
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
PyFrameObject *f = tstate->frame;
|
||||
|
||||
gen->gi_running = 1;
|
||||
/* Since we are fast-tracking things by skipping the eval loop,
|
||||
we need to update the current frame so the stack trace
|
||||
will be reported correctly to the user. */
|
||||
/* XXX We should probably be updating the current frame
|
||||
somewhere in ceval.c. */
|
||||
tstate->frame = gen->gi_frame;
|
||||
/* Close the generator that we are currently iterating with
|
||||
'yield from' or awaiting on with 'await'. */
|
||||
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
|
||||
typ, val, tb);
|
||||
tstate->frame = f;
|
||||
gen->gi_running = 0;
|
||||
} else {
|
||||
/* `yf` is an iterator or a coroutine-like object. */
|
||||
|
|
Loading…
Reference in New Issue