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)
|
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):
|
class YieldFromTests(unittest.TestCase):
|
||||||
def test_generator_gi_yieldfrom(self):
|
def test_generator_gi_yieldfrom(self):
|
||||||
def a():
|
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)) {
|
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
|
||||||
/* `yf` is a generator or a coroutine. */
|
/* `yf` is a generator or a coroutine. */
|
||||||
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
|
PyFrameObject *f = tstate->frame;
|
||||||
|
|
||||||
gen->gi_running = 1;
|
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
|
/* Close the generator that we are currently iterating with
|
||||||
'yield from' or awaiting on with 'await'. */
|
'yield from' or awaiting on with 'await'. */
|
||||||
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
|
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
|
||||||
typ, val, tb);
|
typ, val, tb);
|
||||||
|
tstate->frame = f;
|
||||||
gen->gi_running = 0;
|
gen->gi_running = 0;
|
||||||
} else {
|
} else {
|
||||||
/* `yf` is an iterator or a coroutine-like object. */
|
/* `yf` is an iterator or a coroutine-like object. */
|
||||||
|
|
Loading…
Reference in New Issue