Add more tests for throwing into yield from (GH-94097)

This commit is contained in:
Brandt Bucher 2022-06-23 16:48:28 -07:00 committed by GitHub
parent 8e6eccabe8
commit 5075e81c51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 527 additions and 0 deletions

View File

@ -1049,6 +1049,533 @@ class TestPEP380Operation(unittest.TestCase):
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()