[Python-checkins] Add more tests for throwing into yield from (GH-94097)

brandtbucher webhook-mailer at python.org
Thu Jun 23 19:48:37 EDT 2022


https://github.com/python/cpython/commit/5075e81c519af5cf3fd9d356d483b0c9df6b116a
commit: 5075e81c519af5cf3fd9d356d483b0c9df6b116a
branch: main
author: Brandt Bucher <brandtbucher at microsoft.com>
committer: brandtbucher <brandtbucher at gmail.com>
date: 2022年06月23日T16:48:28-07:00
summary:
Add more tests for throwing into yield from (GH-94097)
files:
M Lib/test/test_yield_from.py
diff --git a/Lib/test/test_yield_from.py b/Lib/test/test_yield_from.py
index d105d8c6eb513..1a60357a1bcd6 100644
--- a/Lib/test/test_yield_from.py
+++ b/Lib/test/test_yield_from.py
@@ -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()


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /