diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2015-01-31 09:29:47 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2015-01-31 09:29:47 (GMT) |
commit | 26f7b8acdc010801a8fe877376488e0b69a89dad (patch) | |
tree | be0c85b2c6deaeecedc9313c6983f917a6943ce5 /Lib/test | |
parent | fdc995336f3884117f2ede573694b9f0f7b9c11c (diff) | |
download | cpython-26f7b8acdc010801a8fe877376488e0b69a89dad.zip cpython-26f7b8acdc010801a8fe877376488e0b69a89dad.tar.gz cpython-26f7b8acdc010801a8fe877376488e0b69a89dad.tar.bz2 |
Issue #23353: Fix the exception handling of generators in PyEval_EvalFrameEx().
At entry, save or swap the exception state even if PyEval_EvalFrameEx() is
called with throwflag=0. At exit, the exception state is now always restored or
swapped, not only if why is WHY_YIELD or WHY_RETURN. Patch co-written with
Antoine Pitrou.
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/test_generators.py | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 91afe47..7825a77 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -50,6 +50,115 @@ class FinalizationTest(unittest.TestCase): self.assertEqual(gc.garbage, old_garbage) +class ExceptionTest(unittest.TestCase): + # Tests for the issue #23353: check that the currently handled exception + # is correctly saved/restored in PyEval_EvalFrameEx(). + + def test_except_throw(self): + def store_raise_exc_generator(): + try: + self.assertEqual(sys.exc_info()[0], None) + yield + except Exception as exc: + # exception raised by gen.throw(exc) + self.assertEqual(sys.exc_info()[0], ValueError) + self.assertIsNone(exc.__context__) + yield + + # ensure that the exception is not lost + self.assertEqual(sys.exc_info()[0], ValueError) + yield + + # we should be able to raise back the ValueError + raise + + make = store_raise_exc_generator() + next(make) + + try: + raise ValueError() + except Exception as exc: + try: + make.throw(exc) + except Exception: + pass + + next(make) + with self.assertRaises(ValueError) as cm: + next(make) + self.assertIsNone(cm.exception.__context__) + + self.assertEqual(sys.exc_info(), (None, None, None)) + + def test_except_next(self): + def gen(): + self.assertEqual(sys.exc_info()[0], ValueError) + yield "done" + + g = gen() + try: + raise ValueError + except Exception: + self.assertEqual(next(g), "done") + self.assertEqual(sys.exc_info(), (None, None, None)) + + def test_except_gen_except(self): + def gen(): + try: + self.assertEqual(sys.exc_info()[0], None) + yield + # we are called from "except ValueError:", TypeError must + # inherit ValueError in its context + raise TypeError() + except TypeError as exc: + self.assertEqual(sys.exc_info()[0], TypeError) + self.assertEqual(type(exc.__context__), ValueError) + # here we are still called from the "except ValueError:" + self.assertEqual(sys.exc_info()[0], ValueError) + yield + self.assertIsNone(sys.exc_info()[0]) + yield "done" + + g = gen() + next(g) + try: + raise ValueError + except Exception: + next(g) + + self.assertEqual(next(g), "done") + self.assertEqual(sys.exc_info(), (None, None, None)) + + def test_except_throw_exception_context(self): + def gen(): + try: + try: + self.assertEqual(sys.exc_info()[0], None) + yield + except ValueError: + # we are called from "except ValueError:" + self.assertEqual(sys.exc_info()[0], ValueError) + raise TypeError() + except Exception as exc: + self.assertEqual(sys.exc_info()[0], TypeError) + self.assertEqual(type(exc.__context__), ValueError) + # we are still called from "except ValueError:" + self.assertEqual(sys.exc_info()[0], ValueError) + yield + self.assertIsNone(sys.exc_info()[0]) + yield "done" + + g = gen() + next(g) + try: + raise ValueError + except Exception as exc: + g.throw(exc) + + self.assertEqual(next(g), "done") + self.assertEqual(sys.exc_info(), (None, None, None)) + + tutorial_tests = """ Let's try a simple generator: |