Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more tests for throwing into yield from #94097

Merged
merged 3 commits into from
Jun 23, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
527 changes: 527 additions & 0 deletions Lib/test/test_yield_from.py
Original file line number Diff line number Diff line change
@@ -1049,6 +1049,533 @@ def outer():
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()
# PEP 479:
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()
# GeneratorExit is suppressed. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
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)
# The raised GeneratorExit is suppressed, but the thrown one
# propagates. This is consistent with PEP 380:
# https://peps.python.org/pep-0380/#proposal
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()
# PEP 479:
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()
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
g.throw(thrown)
self.assertIs(caught.exception.__context__, raised)
# This isn't the same GeneratorExit as thrown! It's the one created
# by calling inner.close():
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()
# PEP 479:
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()
# PEP 479:
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()
# PEP 479:
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)
# This isn't the same GeneratorExit as thrown! It's the one created
# by calling inner.close():
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)
# This isn't the same GeneratorExit as thrown! It's the one created
# by calling inner.close():
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)
# No chaining happens. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
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()
# No chaining happens. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
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)
# PEP 479:
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)
# StopIteration is suppressed. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
g.close()
self.assert_stop_iteration(g)

with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = GeneratorExit()
# StopIteration is suppressed. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
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()