coding utf8 test suite for pep 380 implementation adapted from original tests written by greg ewing see http www cosc canterbury ac nzgreg ewingpythonyieldfromyieldfrompython3 1 2rev5 zip test semantics test delegation of initial next call to subgenerator test raising exception in initial next call test delegation of next call to subgenerator test raising exception in delegated next call test delegation of send test handling exception while delegating send test delegating close test handling exception while delegating close test delegating throw test value attribute of stopiteration exception there used to be a refcount error when the return value stored in the stopiteration has a refcount of 1 test generator return value test delegation of next to nongenerator test conversion of sendnone to next test delegation of close to nongenerator test delegating throw to nongenerator test attempting to send to nongenerator test subiterator with a broken getattr implementation test exception in initial next call test attempted yieldfrom loop test returning value from delegated throw test next and return with value test send and return with value test catching an exception thrown into a subgenerator and returning a value test throwing generatorexit into a subgenerator that catches it and returns normally test throwing generatorexit into a subgenerator that catches it and yields test throwing generatorexit into a subgenerator that catches it and raises a different exception check with basic iteration check with send check with throw check with close see issue 15568 see issue 17669 create a stack of generators outer delegating to inner delegating to innermost the key point is that the instance of inner is created first this ensures that its frame appears before the instance of outer in the gc linked list at the gc collect call frameclear is called on the innergen frame gendealloc is called on the outergen generator the only reference is in the frame s locals genclose is called on the outergen generator gencloseiter is called to close the innergen generator which in turn calls genclose and genyf previously genyf would crash since innergen s frame had been cleared and in particular fstacktop was null see issue 21209 pep 479 generatorexit is suppressed this is consistent with pep 342 https peps python orgpep0342newgeneratormethodclose the raised generatorexit is suppressed but the thrown one propagates this is consistent with pep 380 https peps python orgpep0380proposal pep 479 pep 479 this isn t the same generatorexit as thrown it s the one calling inner close pep 479 pep 479 pep 479 this isn t the same generatorexit as thrown it s the one calling inner close this isn t the same generatorexit as thrown it s the one calling inner close no chaining happens this is consistent with pep 342 https peps python orgpep0342newgeneratormethodclose no chaining happens this is consistent with pep 342 https peps python orgpep0342newgeneratormethodclose pep 479 stopiteration is suppressed this is consistent with pep 342 https peps python orgpep0342newgeneratormethodclose stopiteration is suppressed this is consistent with pep 342 https peps python orgpep0342newgeneratormethodclose coding utf 8 test suite for pep 380 implementation adapted from original tests written by greg ewing see http www cosc canterbury ac nz greg ewing python yield from yieldfrom python3 1 2 rev5 zip test semantics test delegation of initial next call to subgenerator test raising exception in initial next call test delegation of next call to subgenerator test raising exception in delegated next call test delegation of send test handling exception while delegating send test delegating close test handling exception while delegating close test delegating throw test value attribute of stopiteration exception there used to be a refcount error when the return value stored in the stopiteration has a refcount of 1 test generator return value test delegation of next to non generator test conversion of send none to next test delegation of close to non generator test delegating throw to non generator test attempting to send to non generator test subiterator with a broken getattr implementation test exception in initial next call test attempted yield from loop test returning value from delegated throw test next and return with value test send and return with value test catching an exception thrown into a subgenerator and returning a value test throwing generatorexit into a subgenerator that catches it and returns normally test throwing generatorexit into a subgenerator that catches it and yields test throwing generatorexit into a subgenerator that catches it and raises a different exception check with basic iteration check with send check with throw check with close see issue 15568 see issue 17669 create a stack of generators outer delegating to inner delegating to innermost the key point is that the instance of inner is created first this ensures that its frame appears before the instance of outer in the gc linked list at the gc collect call frame_clear is called on the inner_gen frame gen_dealloc is called on the outer_gen generator the only reference is in the frame s locals gen_close is called on the outer_gen generator gen_close_iter is called to close the inner_gen generator which in turn calls gen_close and gen_yf previously gen_yf would crash since inner_gen s frame had been cleared and in particular f_stacktop was null see issue 21209 pep 479 generatorexit is suppressed this is consistent with pep 342 https peps python org pep 0342 new generator method close the raised generatorexit is suppressed but the thrown one propagates this is consistent with pep 380 https peps python org pep 0380 proposal pep 479 pep 479 this isn t the same generatorexit as thrown it s the one created by calling inner close pep 479 pep 479 pep 479 this isn t the same generatorexit as thrown it s the one created by calling inner close this isn t the same generatorexit as thrown it s the one created by calling inner close no chaining happens this is consistent with pep 342 https peps python org pep 0342 new generator method close no chaining happens this is consistent with pep 342 https peps python org pep 0342 new generator method close pep 479 stopiteration is suppressed this is consistent with pep 342 https peps python org pep 0342 new generator method close stopiteration is suppressed this is consistent with pep 342 https peps python org pep 0342 new generator method close
import unittest
import inspect

from test.support import captured_stderr, disable_gc, gc_collect
from test import support

class TestPEP380Operation(unittest.TestCase):
    

    def test_delegation_of_initial_next_to_subgenerator(self):
        
        trace = []
        def g1():
            trace.append("Starting g1")
            yield from g2()
            trace.append("Finishing g1")
        def g2():
            trace.append("Starting g2")
            yield 42
            trace.append("Finishing g2")
        for x in g1():
            trace.append("Yielded %s" % (x,))
        self.assertEqual(trace,[
            "Starting g1",
            "Starting g2",
            "Yielded 42",
            "Finishing g2",
            "Finishing g1",
        ])

    def test_raising_exception_in_initial_next_call(self):
        
        trace = []
        def g1():
            try:
                trace.append("Starting g1")
                yield from g2()
            finally:
                trace.append("Finishing g1")
        def g2():
            try:
                trace.append("Starting g2")
                raise ValueError("spanish inquisition occurred")
            finally:
                trace.append("Finishing g2")
        try:
            for x in g1():
                trace.append("Yielded %s" % (x,))
        except ValueError as e:
            self.assertEqual(e.args[0], "spanish inquisition occurred")
        else:
            self.fail("subgenerator failed to raise ValueError")
        self.assertEqual(trace,[
            "Starting g1",
            "Starting g2",
            "Finishing g2",
            "Finishing g1",
        ])

    def test_delegation_of_next_call_to_subgenerator(self):
        
        trace = []
        def g1():
            trace.append("Starting g1")
            yield "g1 ham"
            yield from g2()
            yield "g1 eggs"
            trace.append("Finishing g1")
        def g2():
            trace.append("Starting g2")
            yield "g2 spam"
            yield "g2 more spam"
            trace.append("Finishing g2")
        for x in g1():
            trace.append("Yielded %s" % (x,))
        self.assertEqual(trace,[
            "Starting g1",
            "Yielded g1 ham",
            "Starting g2",
            "Yielded g2 spam",
            "Yielded g2 more spam",
            "Finishing g2",
            "Yielded g1 eggs",
            "Finishing g1",
        ])

    def test_raising_exception_in_delegated_next_call(self):
        
        trace = []
        def g1():
            try:
                trace.append("Starting g1")
                yield "g1 ham"
                yield from g2()
                yield "g1 eggs"
            finally:
                trace.append("Finishing g1")
        def g2():
            try:
                trace.append("Starting g2")
                yield "g2 spam"
                raise ValueError("hovercraft is full of eels")
                yield "g2 more spam"
            finally:
                trace.append("Finishing g2")
        try:
            for x in g1():
                trace.append("Yielded %s" % (x,))
        except ValueError as e:
            self.assertEqual(e.args[0], "hovercraft is full of eels")
        else:
            self.fail("subgenerator failed to raise ValueError")
        self.assertEqual(trace,[
            "Starting g1",
            "Yielded g1 ham",
            "Starting g2",
            "Yielded g2 spam",
            "Finishing g2",
            "Finishing g1",
        ])

    def test_delegation_of_send(self):
        
        trace = []
        def g1():
            trace.append("Starting g1")
            x = yield "g1 ham"
            trace.append("g1 received %s" % (x,))
            yield from g2()
            x = yield "g1 eggs"
            trace.append("g1 received %s" % (x,))
            trace.append("Finishing g1")
        def g2():
            trace.append("Starting g2")
            x = yield "g2 spam"
            trace.append("g2 received %s" % (x,))
            x = yield "g2 more spam"
            trace.append("g2 received %s" % (x,))
            trace.append("Finishing g2")
        g = g1()
        y = next(g)
        x = 1
        try:
            while 1:
                y = g.send(x)
                trace.append("Yielded %s" % (y,))
                x += 1
        except StopIteration:
            pass
        self.assertEqual(trace,[
            "Starting g1",
            "g1 received 1",
            "Starting g2",
            "Yielded g2 spam",
            "g2 received 2",
            "Yielded g2 more spam",
            "g2 received 3",
            "Finishing g2",
            "Yielded g1 eggs",
            "g1 received 4",
            "Finishing g1",
        ])

    def test_handling_exception_while_delegating_send(self):
        
        trace = []
        def g1():
            trace.append("Starting g1")
            x = yield "g1 ham"
            trace.append("g1 received %s" % (x,))
            yield from g2()
            x = yield "g1 eggs"
            trace.append("g1 received %s" % (x,))
            trace.append("Finishing g1")
        def g2():
            trace.append("Starting g2")
            x = yield "g2 spam"
            trace.append("g2 received %s" % (x,))
            raise ValueError("hovercraft is full of eels")
            x = yield "g2 more spam"
            trace.append("g2 received %s" % (x,))
            trace.append("Finishing g2")
        def run():
            g = g1()
            y = next(g)
            x = 1
            try:
                while 1:
                    y = g.send(x)
                    trace.append("Yielded %s" % (y,))
                    x += 1
            except StopIteration:
                trace.append("StopIteration")
        self.assertRaises(ValueError,run)
        self.assertEqual(trace,[
            "Starting g1",
            "g1 received 1",
            "Starting g2",
            "Yielded g2 spam",
            "g2 received 2",
        ])

    def test_delegating_close(self):
        
        trace = []
        def g1():
            try:
                trace.append("Starting g1")
                yield "g1 ham"
                yield from g2()
                yield "g1 eggs"
            finally:
                trace.append("Finishing g1")
        def g2():
            try:
                trace.append("Starting g2")
                yield "g2 spam"
                yield "g2 more spam"
            finally:
                trace.append("Finishing g2")
        g = g1()
        for i in range(2):
            x = next(g)
            trace.append("Yielded %s" % (x,))
        g.close()
        self.assertEqual(trace,[
            "Starting g1",
            "Yielded g1 ham",
            "Starting g2",
            "Yielded g2 spam",
            "Finishing g2",
            "Finishing g1"
        ])

    def test_handing_exception_while_delegating_close(self):
        
        trace = []
        def g1():
            try:
                trace.append("Starting g1")
                yield "g1 ham"
                yield from g2()
                yield "g1 eggs"
            finally:
                trace.append("Finishing g1")
        def g2():
            try:
                trace.append("Starting g2")
                yield "g2 spam"
                yield "g2 more spam"
            finally:
                trace.append("Finishing g2")
                raise ValueError("nybbles have exploded with delight")
        try:
            g = g1()
            for i in range(2):
                x = next(g)
                trace.append("Yielded %s" % (x,))
            g.close()
        except ValueError as e:
            self.assertEqual(e.args[0], "nybbles have exploded with delight")
            self.assertIsInstance(e.__context__, GeneratorExit)
        else:
            self.fail("subgenerator failed to raise ValueError")
        self.assertEqual(trace,[
            "Starting g1",
            "Yielded g1 ham",
            "Starting g2",
            "Yielded g2 spam",
            "Finishing g2",
            "Finishing g1",
        ])

    def test_delegating_throw(self):
        
        trace = []
        def g1():
            try:
                trace.append("Starting g1")
                yield "g1 ham"
                yield from g2()
                yield "g1 eggs"
            finally:
                trace.append("Finishing g1")
        def g2():
            try:
                trace.append("Starting g2")
                yield "g2 spam"
                yield "g2 more spam"
            finally:
                trace.append("Finishing g2")
        try:
            g = g1()
            for i in range(2):
                x = next(g)
                trace.append("Yielded %s" % (x,))
            e = ValueError("tomato ejected")
            g.throw(e)
        except ValueError as e:
            self.assertEqual(e.args[0], "tomato ejected")
        else:
            self.fail("subgenerator failed to raise ValueError")
        self.assertEqual(trace,[
            "Starting g1",
            "Yielded g1 ham",
            "Starting g2",
            "Yielded g2 spam",
            "Finishing g2",
            "Finishing g1",
        ])

    def test_value_attribute_of_StopIteration_exception(self):
        
        trace = []
        def pex(e):
            trace.append("%s: %s" % (e.__class__.__name__, e))
            trace.append("value = %s" % (e.value,))
        e = StopIteration()
        pex(e)
        e = StopIteration("spam")
        pex(e)
        e.value = "eggs"
        pex(e)
        self.assertEqual(trace,[
            "StopIteration: ",
            "value = None",
            "StopIteration: spam",
            "value = spam",
            "StopIteration: spam",
            "value = eggs",
        ])


    def test_exception_value_crash(self):
        
        
        def g1():
            yield from g2()
        def g2():
            yield "g2"
            return [42]
        self.assertEqual(list(g1()), ["g2"])


    def test_generator_return_value(self):
        
        trace = []
        def g1():
            trace.append("Starting g1")
            yield "g1 ham"
            ret = yield from g2()
            trace.append("g2 returned %r" % (ret,))
            for v in 1, (2,), StopIteration(3):
                ret = yield from g2(v)
                trace.append("g2 returned %r" % (ret,))
            yield "g1 eggs"
            trace.append("Finishing g1")
        def g2(v = None):
            trace.append("Starting g2")
            yield "g2 spam"
            yield "g2 more spam"
            trace.append("Finishing g2")
            if v:
                return v
        for x in g1():
            trace.append("Yielded %s" % (x,))
        self.assertEqual(trace,[
            "Starting g1",
            "Yielded g1 ham",
            "Starting g2",
            "Yielded g2 spam",
            "Yielded g2 more spam",
            "Finishing g2",
            "g2 returned None",
            "Starting g2",
            "Yielded g2 spam",
            "Yielded g2 more spam",
            "Finishing g2",
            "g2 returned 1",
            "Starting g2",
            "Yielded g2 spam",
            "Yielded g2 more spam",
            "Finishing g2",
            "g2 returned (2,)",
            "Starting g2",
            "Yielded g2 spam",
            "Yielded g2 more spam",
            "Finishing g2",
            "g2 returned StopIteration(3)",
            "Yielded g1 eggs",
            "Finishing g1",
        ])

    def test_delegation_of_next_to_non_generator(self):
        
        trace = []
        def g():
            yield from range(3)
        for x in g():
            trace.append("Yielded %s" % (x,))
        self.assertEqual(trace,[
            "Yielded 0",
            "Yielded 1",
            "Yielded 2",
        ])


    def test_conversion_of_sendNone_to_next(self):
        
        trace = []
        def g():
            yield from range(3)
        gi = g()
        for x in range(3):
            y = gi.send(None)
            trace.append("Yielded: %s" % (y,))
        self.assertEqual(trace,[
            "Yielded: 0",
            "Yielded: 1",
            "Yielded: 2",
        ])

    def test_delegation_of_close_to_non_generator(self):
        
        trace = []
        def g():
            try:
                trace.append("starting g")
                yield from range(3)
                trace.append("g should not be here")
            finally:
                trace.append("finishing g")
        gi = g()
        next(gi)
        with captured_stderr() as output:
            gi.close()
        self.assertEqual(output.getvalue(), '')
        self.assertEqual(trace,[
            "starting g",
            "finishing g",
        ])

    def test_delegating_throw_to_non_generator(self):
        
        trace = []
        def g():
            try:
                trace.append("Starting g")
                yield from range(10)
            finally:
                trace.append("Finishing g")
        try:
            gi = g()
            for i in range(5):
                x = next(gi)
                trace.append("Yielded %s" % (x,))
            e = ValueError("tomato ejected")
            gi.throw(e)
        except ValueError as e:
            self.assertEqual(e.args[0],"tomato ejected")
        else:
            self.fail("subgenerator failed to raise ValueError")
        self.assertEqual(trace,[
            "Starting g",
            "Yielded 0",
            "Yielded 1",
            "Yielded 2",
            "Yielded 3",
            "Yielded 4",
            "Finishing g",
        ])

    def test_attempting_to_send_to_non_generator(self):
        
        trace = []
        def g():
            try:
                trace.append("starting g")
                yield from range(3)
                trace.append("g should not be here")
            finally:
                trace.append("finishing g")
        try:
            gi = g()
            next(gi)
            for x in range(3):
                y = gi.send(42)
                trace.append("Should not have yielded: %s" % (y,))
        except AttributeError as e:
            self.assertIn("send", e.args[0])
        else:
            self.fail("was able to send into non-generator")
        self.assertEqual(trace,[
            "starting g",
            "finishing g",
        ])

    def test_broken_getattr_handling(self):
        
        class Broken:
            def __iter__(self):
                return self
            def __next__(self):
                return 1
            def __getattr__(self, attr):
                1/0

        def g():
            yield from Broken()

        with self.assertRaises(ZeroDivisionError):
            gi = g()
            self.assertEqual(next(gi), 1)
            gi.send(1)

        with self.assertRaises(ZeroDivisionError):
            gi = g()
            self.assertEqual(next(gi), 1)
            gi.throw(AttributeError)

        with support.catch_unraisable_exception() as cm:
            gi = g()
            self.assertEqual(next(gi), 1)
            gi.close()

            self.assertEqual(ZeroDivisionError, cm.unraisable.exc_type)

    def test_exception_in_initial_next_call(self):
        
        trace = []
        def g1():
            trace.append("g1 about to yield from g2")
            yield from g2()
            trace.append("g1 should not be here")
        def g2():
            yield 1/0
        def run():
            gi = g1()
            next(gi)
        self.assertRaises(ZeroDivisionError,run)
        self.assertEqual(trace,[
            "g1 about to yield from g2"
        ])

    def test_attempted_yield_from_loop(self):
        
        trace = []
        def g1():
            trace.append("g1: starting")
            yield "y1"
            trace.append("g1: about to yield from g2")
            yield from g2()
            trace.append("g1 should not be here")

        def g2():
            trace.append("g2: starting")
            yield "y2"
            trace.append("g2: about to yield from g1")
            yield from gi
            trace.append("g2 should not be here")
        try:
            gi = g1()
            for y in gi:
                trace.append("Yielded: %s" % (y,))
        except ValueError as e:
            self.assertEqual(e.args[0],"generator already executing")
        else:
            self.fail("subgenerator didn't raise ValueError")
        self.assertEqual(trace,[
            "g1: starting",
            "Yielded: y1",
            "g1: about to yield from g2",
            "g2: starting",
            "Yielded: y2",
            "g2: about to yield from g1",
        ])

    def test_returning_value_from_delegated_throw(self):
        
        trace = []
        def g1():
            try:
                trace.append("Starting g1")
                yield "g1 ham"
                yield from g2()
                yield "g1 eggs"
            finally:
                trace.append("Finishing g1")
        def g2():
            try:
                trace.append("Starting g2")
                yield "g2 spam"
                yield "g2 more spam"
            except LunchError:
                trace.append("Caught LunchError in g2")
                yield "g2 lunch saved"
                yield "g2 yet more spam"
        class LunchError(Exception):
            pass
        g = g1()
        for i in range(2):
            x = next(g)
            trace.append("Yielded %s" % (x,))
        e = LunchError("tomato ejected")
        g.throw(e)
        for x in g:
            trace.append("Yielded %s" % (x,))
        self.assertEqual(trace,[
            "Starting g1",
            "Yielded g1 ham",
            "Starting g2",
            "Yielded g2 spam",
            "Caught LunchError in g2",
            "Yielded g2 yet more spam",
            "Yielded g1 eggs",
            "Finishing g1",
        ])

    def test_next_and_return_with_value(self):
        
        trace = []
        def f(r):
            gi = g(r)
            next(gi)
            try:
                trace.append("f resuming g")
                next(gi)
                trace.append("f SHOULD NOT BE HERE")
            except StopIteration as e:
                trace.append("f caught %r" % (e,))
        def g(r):
            trace.append("g starting")
            yield
            trace.append("g returning %r" % (r,))
            return r
        f(None)
        f(1)
        f((2,))
        f(StopIteration(3))
        self.assertEqual(trace,[
            "g starting",
            "f resuming g",
            "g returning None",
            "f caught StopIteration()",
            "g starting",
            "f resuming g",
            "g returning 1",
            "f caught StopIteration(1)",
            "g starting",
            "f resuming g",
            "g returning (2,)",
            "f caught StopIteration((2,))",
            "g starting",
            "f resuming g",
            "g returning StopIteration(3)",
            "f caught StopIteration(StopIteration(3))",
        ])

    def test_send_and_return_with_value(self):
        
        trace = []
        def f(r):
            gi = g(r)
            next(gi)
            try:
                trace.append("f sending spam to g")
                gi.send("spam")
                trace.append("f SHOULD NOT BE HERE")
            except StopIteration as e:
                trace.append("f caught %r" % (e,))
        def g(r):
            trace.append("g starting")
            x = yield
            trace.append("g received %r" % (x,))
            trace.append("g returning %r" % (r,))
            return r
        f(None)
        f(1)
        f((2,))
        f(StopIteration(3))
        self.assertEqual(trace, [
            "g starting",
            "f sending spam to g",
            "g received 'spam'",
            "g returning None",
            "f caught StopIteration()",
            "g starting",
            "f sending spam to g",
            "g received 'spam'",
            "g returning 1",
            'f caught StopIteration(1)',
            'g starting',
            'f sending spam to g',
            "g received 'spam'",
            'g returning (2,)',
            'f caught StopIteration((2,))',
            'g starting',
            'f sending spam to g',
            "g received 'spam'",
            'g returning StopIteration(3)',
            'f caught StopIteration(StopIteration(3))'
        ])

    def test_catching_exception_from_subgen_and_returning(self):
        
        def inner():
            try:
                yield 1
            except ValueError:
                trace.append("inner caught ValueError")
            return value

        def outer():
            v = yield from inner()
            trace.append("inner returned %r to outer" % (v,))
            yield v

        for value in 2, (2,), StopIteration(2):
            trace = []
            g = outer()
            trace.append(next(g))
            trace.append(repr(g.throw(ValueError)))
            self.assertEqual(trace, [
                1,
                "inner caught ValueError",
                "inner returned %r to outer" % (value,),
                repr(value),
            ])

    def test_throwing_GeneratorExit_into_subgen_that_returns(self):
        
        trace = []
        def f():
            try:
                trace.append("Enter f")
                yield
                trace.append("Exit f")
            except GeneratorExit:
                return
        def g():
            trace.append("Enter g")
            yield from f()
            trace.append("Exit g")
        try:
            gi = g()
            next(gi)
            gi.throw(GeneratorExit)
        except GeneratorExit:
            pass
        else:
            self.fail("subgenerator failed to raise GeneratorExit")
        self.assertEqual(trace,[
            "Enter g",
            "Enter f",
        ])

    def test_throwing_GeneratorExit_into_subgenerator_that_yields(self):
        
        trace = []
        def f():
            try:
                trace.append("Enter f")
                yield
                trace.append("Exit f")
            except GeneratorExit:
                yield
        def g():
            trace.append("Enter g")
            yield from f()
            trace.append("Exit g")
        try:
            gi = g()
            next(gi)
            gi.throw(GeneratorExit)
        except RuntimeError as e:
            self.assertEqual(e.args[0], "generator ignored GeneratorExit")
        else:
            self.fail("subgenerator failed to raise GeneratorExit")
        self.assertEqual(trace,[
            "Enter g",
            "Enter f",
        ])

    def test_throwing_GeneratorExit_into_subgen_that_raises(self):
        
        trace = []
        def f():
            try:
                trace.append("Enter f")
                yield
                trace.append("Exit f")
            except GeneratorExit:
                raise ValueError("Vorpal bunny encountered")
        def g():
            trace.append("Enter g")
            yield from f()
            trace.append("Exit g")
        try:
            gi = g()
            next(gi)
            gi.throw(GeneratorExit)
        except ValueError as e:
            self.assertEqual(e.args[0], "Vorpal bunny encountered")
            self.assertIsInstance(e.__context__, GeneratorExit)
        else:
            self.fail("subgenerator failed to raise ValueError")
        self.assertEqual(trace,[
            "Enter g",
            "Enter f",
        ])

    def test_yield_from_empty(self):
        def g():
            yield from ()
        self.assertRaises(StopIteration, next, g())

    def test_delegating_generators_claim_to_be_running(self):
        
        def one():
            yield 0
            yield from two()
            yield 3
        def two():
            yield 1
            try:
                yield from g1
            except ValueError:
                pass
            yield 2
        g1 = one()
        self.assertEqual(list(g1), [0, 1, 2, 3])
        
        g1 = one()
        res = [next(g1)]
        try:
            while True:
                res.append(g1.send(42))
        except StopIteration:
            pass
        self.assertEqual(res, [0, 1, 2, 3])
        
        class MyErr(Exception):
            pass
        def one():
            try:
                yield 0
            except MyErr:
                pass
            yield from two()
            try:
                yield 3
            except MyErr:
                pass
        def two():
            try:
                yield 1
            except MyErr:
                pass
            try:
                yield from g1
            except ValueError:
                pass
            try:
                yield 2
            except MyErr:
                pass
        g1 = one()
        res = [next(g1)]
        try:
            while True:
                res.append(g1.throw(MyErr))
        except StopIteration:
            pass
        except:
            self.assertEqual(res, [0, 1, 2, 3])
            raise
        
        class MyIt(object):
            def __iter__(self):
                return self
            def __next__(self):
                return 42
            def close(self_):
                self.assertTrue(g1.gi_running)
                self.assertRaises(ValueError, next, g1)
        def one():
            yield from MyIt()
        g1 = one()
        next(g1)
        g1.close()

    def test_delegator_is_visible_to_debugger(self):
        def call_stack():
            return [f[3] for f in inspect.stack()]

        def gen():
            yield call_stack()
            yield call_stack()
            yield call_stack()

        def spam(g):
            yield from g

        def eggs(g):
            yield from g

        for stack in spam(gen()):
            self.assertTrue('spam' in stack)

        for stack in spam(eggs(gen())):
            self.assertTrue('spam' in stack and 'eggs' in stack)

    def test_custom_iterator_return(self):
        
        class MyIter:
            def __iter__(self):
                return self
            def __next__(self):
                raise StopIteration(42)
        def gen():
            nonlocal ret
            ret = yield from MyIter()
        ret = None
        list(gen())
        self.assertEqual(ret, 42)

    def test_close_with_cleared_frame(self):
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

        def innermost():
            yield
        def inner():
            outer_gen = yield
            yield from innermost()
        def outer():
            inner_gen = yield
            yield from inner_gen

        with disable_gc():
            inner_gen = inner()
            outer_gen = outer()
            outer_gen.send(None)
            outer_gen.send(inner_gen)
            outer_gen.send(outer_gen)

            del outer_gen
            del inner_gen
            gc_collect()

    def test_send_tuple_with_custom_generator(self):
        
        class MyGen:
            def __iter__(self):
                return self
            def __next__(self):
                return 42
            def send(self, what):
                nonlocal v
                v = what
                return None
        def outer():
            v = yield from MyGen()
        g = outer()
        next(g)
        v = None
        g.send((1, 2, 3, 4))
        self.assertEqual(v, (1, 2, 3, 4))

class TestInterestingEdgeCases(unittest.TestCase):

    def assert_stop_iteration(self, iterator):
        with self.assertRaises(StopIteration) as caught:
            next(iterator)
        self.assertIsNone(caught.exception.value)
        self.assertIsNone(caught.exception.__context__)

    def assert_generator_raised_stop_iteration(self):
        return self.assertRaisesRegex(RuntimeError, r"^generator raised StopIteration$")

    def assert_generator_ignored_generator_exit(self):
        return self.assertRaisesRegex(RuntimeError, r"^generator ignored GeneratorExit$")

    def test_close_and_throw_work(self):

        yielded_first = object()
        yielded_second = object()
        returned = object()

        def inner():
            yield yielded_first
            yield yielded_second
            return returned

        def outer():
            return (yield from inner())

        with self.subTest("close"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            g.close()
            self.assert_stop_iteration(g)

        with self.subTest("throw GeneratorExit"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = GeneratorExit()
            with self.assertRaises(GeneratorExit) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw StopIteration"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = StopIteration()
            
            with self.assert_generator_raised_stop_iteration() as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw BaseException"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = BaseException()
            with self.assertRaises(BaseException) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw Exception"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = Exception()
            with self.assertRaises(Exception) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

    def test_close_and_throw_raise_generator_exit(self):

        yielded_first = object()
        yielded_second = object()
        returned = object()

        def inner():
            try:
                yield yielded_first
                yield yielded_second
                return returned
            finally:
                raise raised

        def outer():
            return (yield from inner())

        with self.subTest("close"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = GeneratorExit()
            
            
            g.close()
            self.assert_stop_iteration(g)

        with self.subTest("throw GeneratorExit"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = GeneratorExit()
            thrown = GeneratorExit()
            with self.assertRaises(GeneratorExit) as caught:
                g.throw(thrown)
            
            
            
            self.assertIs(caught.exception, thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw StopIteration"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = GeneratorExit()
            thrown = StopIteration()
            with self.assertRaises(GeneratorExit) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw BaseException"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = GeneratorExit()
            thrown = BaseException()
            with self.assertRaises(GeneratorExit) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw Exception"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = GeneratorExit()
            thrown = Exception()
            with self.assertRaises(GeneratorExit) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

    def test_close_and_throw_raise_stop_iteration(self):

        yielded_first = object()
        yielded_second = object()
        returned = object()

        def inner():
            try:
                yield yielded_first
                yield yielded_second
                return returned
            finally:
                raise raised

        def outer():
            return (yield from inner())

        with self.subTest("close"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = StopIteration()
            
            with self.assert_generator_raised_stop_iteration() as caught:
                g.close()
            self.assertIs(caught.exception.__context__, raised)
            self.assertIsInstance(caught.exception.__context__.__context__, GeneratorExit)
            self.assertIsNone(caught.exception.__context__.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw GeneratorExit"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = StopIteration()
            thrown = GeneratorExit()
            
            with self.assert_generator_raised_stop_iteration() as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.__context__, raised)
            
            
            self.assertIsInstance(caught.exception.__context__.__context__, GeneratorExit)
            self.assertIsNone(caught.exception.__context__.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw StopIteration"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = StopIteration()
            thrown = StopIteration()
            
            with self.assert_generator_raised_stop_iteration() as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.__context__, raised)
            self.assertIs(caught.exception.__context__.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw BaseException"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = StopIteration()
            thrown = BaseException()
            
            with self.assert_generator_raised_stop_iteration() as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.__context__, raised)
            self.assertIs(caught.exception.__context__.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw Exception"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = StopIteration()
            thrown = Exception()
            
            with self.assert_generator_raised_stop_iteration() as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.__context__, raised)
            self.assertIs(caught.exception.__context__.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__.__context__)
            self.assert_stop_iteration(g)

    def test_close_and_throw_raise_base_exception(self):

        yielded_first = object()
        yielded_second = object()
        returned = object()

        def inner():
            try:
                yield yielded_first
                yield yielded_second
                return returned
            finally:
                raise raised

        def outer():
            return (yield from inner())

        with self.subTest("close"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = BaseException()
            with self.assertRaises(BaseException) as caught:
                g.close()
            self.assertIs(caught.exception, raised)
            self.assertIsInstance(caught.exception.__context__, GeneratorExit)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw GeneratorExit"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = BaseException()
            thrown = GeneratorExit()
            with self.assertRaises(BaseException) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            
            
            self.assertIsInstance(caught.exception.__context__, GeneratorExit)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw StopIteration"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = BaseException()
            thrown = StopIteration()
            with self.assertRaises(BaseException) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw BaseException"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = BaseException()
            thrown = BaseException()
            with self.assertRaises(BaseException) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw Exception"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = BaseException()
            thrown = Exception()
            with self.assertRaises(BaseException) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

    def test_close_and_throw_raise_exception(self):

        yielded_first = object()
        yielded_second = object()
        returned = object()

        def inner():
            try:
                yield yielded_first
                yield yielded_second
                return returned
            finally:
                raise raised

        def outer():
            return (yield from inner())

        with self.subTest("close"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = Exception()
            with self.assertRaises(Exception) as caught:
                g.close()
            self.assertIs(caught.exception, raised)
            self.assertIsInstance(caught.exception.__context__, GeneratorExit)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw GeneratorExit"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = Exception()
            thrown = GeneratorExit()
            with self.assertRaises(Exception) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            
            
            self.assertIsInstance(caught.exception.__context__, GeneratorExit)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw StopIteration"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = Exception()
            thrown = StopIteration()
            with self.assertRaises(Exception) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw BaseException"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = Exception()
            thrown = BaseException()
            with self.assertRaises(Exception) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw Exception"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            raised = Exception()
            thrown = Exception()
            with self.assertRaises(Exception) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, raised)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

    def test_close_and_throw_yield(self):

        yielded_first = object()
        yielded_second = object()
        returned = object()

        def inner():
            try:
                yield yielded_first
            finally:
                yield yielded_second
            return returned

        def outer():
            return (yield from inner())

        with self.subTest("close"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            
            
            with self.assert_generator_ignored_generator_exit() as caught:
                g.close()
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw GeneratorExit"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = GeneratorExit()
            
            
            with self.assert_generator_ignored_generator_exit() as caught:
                g.throw(thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw StopIteration"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = StopIteration()
            self.assertEqual(g.throw(thrown), yielded_second)
            
            with self.assert_generator_raised_stop_iteration() as caught:
                next(g)
            self.assertIs(caught.exception.__context__, thrown)
            self.assertIsNone(caught.exception.__context__.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw BaseException"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = BaseException()
            self.assertEqual(g.throw(thrown), yielded_second)
            with self.assertRaises(BaseException) as caught:
                next(g)
            self.assertIs(caught.exception, thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw Exception"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = Exception()
            self.assertEqual(g.throw(thrown), yielded_second)
            with self.assertRaises(Exception) as caught:
                next(g)
            self.assertIs(caught.exception, thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

    def test_close_and_throw_return(self):

        yielded_first = object()
        yielded_second = object()
        returned = object()

        def inner():
            try:
                yield yielded_first
                yield yielded_second
            finally:
                return returned

        def outer():
            return (yield from inner())

        with self.subTest("close"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            
            
            g.close()
            self.assert_stop_iteration(g)

        with self.subTest("throw GeneratorExit"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = GeneratorExit()
            
            
            with self.assertRaises(GeneratorExit) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception, thrown)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw StopIteration"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = StopIteration()
            with self.assertRaises(StopIteration) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.value, returned)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw BaseException"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = BaseException()
            with self.assertRaises(StopIteration) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.value, returned)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)

        with self.subTest("throw Exception"):
            g = outer()
            self.assertIs(next(g), yielded_first)
            thrown = Exception()
            with self.assertRaises(StopIteration) as caught:
                g.throw(thrown)
            self.assertIs(caught.exception.value, returned)
            self.assertIsNone(caught.exception.__context__)
            self.assert_stop_iteration(g)


if __name__ == '__main__':
    unittest.main()