diff --git a/Lib/test/test_generator_stop.py b/Lib/test/test_generator_stop.py new file mode 100644 index 000000000..9cacdfff4 --- /dev/null +++ b/Lib/test/test_generator_stop.py @@ -0,0 +1,35 @@ +from __future__ import generator_stop + +import unittest + + +class TestPEP479(unittest.TestCase): + def test_stopiteration_wrapping(self): + def f(): + raise StopIteration + def g(): + yield f() + with self.assertRaisesRegex(RuntimeError, + "generator raised StopIteration"): + next(g()) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: is not + def test_stopiteration_wrapping_context(self): + def f(): + raise StopIteration + def g(): + yield f() + + try: + next(g()) + except RuntimeError as exc: + self.assertIs(type(exc.__cause__), StopIteration) + self.assertIs(type(exc.__context__), StopIteration) + self.assertTrue(exc.__suppress_context__) + else: + self.fail('__cause__, __context__, or __suppress_context__ ' + 'were not properly set') + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index dc5b23b0f..8da74ff53 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -136,6 +136,19 @@ class FinalizationTest(unittest.TestCase): self.assertEqual(len(resurrected), 1) self.assertIsInstance(resurrected[0].gi_code, types.CodeType) + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: is not None + def test_exhausted_generator_frame_cycle(self): + def g(): + yield + + generator = g() + frame = generator.gi_frame + self.assertIsNone(frame.f_back) + next(generator) + self.assertIsNone(frame.f_back) + next(generator, None) + self.assertIsNone(frame.f_back) + class GeneratorTest(unittest.TestCase): @@ -292,6 +305,34 @@ class GeneratorTest(unittest.TestCase): self.assertEqual([1, 2], list(i for i in C())) + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true + def test_close_clears_frame(self): + # gh-142766: Test that closing a generator clears its frame + class DetectDelete: + def __init__(self): + DetectDelete.deleted = False + + def __del__(self): + DetectDelete.deleted = True + + def generator(arg): + yield + + # Test a freshly created generator (not suspended) + g = generator(DetectDelete()) + g.close() + self.assertTrue(DetectDelete.deleted) + + # Test a suspended generator + g = generator(DetectDelete()) + next(g) + g.close() + self.assertTrue(DetectDelete.deleted) + + # Clear via gi_frame.clear() + g = generator(DetectDelete()) + g.gi_frame.clear() + self.assertTrue(DetectDelete.deleted) class ModifyUnderlyingIterableTest(unittest.TestCase): iterables = [ diff --git a/Lib/test/test_yield_from.py b/Lib/test/test_yield_from.py index e0e3db083..7028a6062 100644 --- a/Lib/test/test_yield_from.py +++ b/Lib/test/test_yield_from.py @@ -538,7 +538,7 @@ class TestPEP380Operation(unittest.TestCase): "finishing g", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_broken_getattr_handling(self): """ Test subiterator with a broken getattr implementation @@ -882,7 +882,7 @@ class TestPEP380Operation(unittest.TestCase): yield from () self.assertRaises(StopIteration, next, g()) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_delegating_generators_claim_to_be_running(self): # Check with basic iteration def one(): @@ -909,7 +909,7 @@ class TestPEP380Operation(unittest.TestCase): pass self.assertEqual(res, [0, 1, 2, 3]) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Lists differ: [0, 1, 2] != [0, 1, 2, 3] + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Lists differ: [0, 1, 2] != [0, 1, 2, 3] def test_delegating_generators_claim_to_be_running_with_throw(self): # Check with throw class MyErr(Exception): @@ -1071,7 +1071,7 @@ class TestInterestingEdgeCases(unittest.TestCase): def assert_generator_ignored_generator_exit(self): return self.assertRaisesRegex(RuntimeError, r"^generator ignored GeneratorExit$") - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_close_and_throw_work(self): yielded_first = object() @@ -1209,7 +1209,7 @@ class TestInterestingEdgeCases(unittest.TestCase): self.assertIsNone(caught.exception.__context__.__context__) self.assert_stop_iteration(g) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised def test_close_and_throw_raise_stop_iteration(self): yielded_first = object() @@ -1449,7 +1449,7 @@ class TestInterestingEdgeCases(unittest.TestCase): self.assertIsNone(caught.exception.__context__.__context__) self.assert_stop_iteration(g) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None is not StopIteration() + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None is not StopIteration() def test_close_and_throw_yield(self): yielded_first = object() @@ -1531,8 +1531,9 @@ class TestInterestingEdgeCases(unittest.TestCase): try: yield yielded_first yield yielded_second - finally: - return returned + except: + pass + return returned def outer(): return (yield from inner()) @@ -1587,6 +1588,19 @@ class TestInterestingEdgeCases(unittest.TestCase): self.assertIsNone(caught.exception.__context__) self.assert_stop_iteration(g) + def test_throws_in_iter(self): + # See GH-126366: NULL pointer dereference if __iter__ + # threw an exception. + class Silly: + def __iter__(self): + raise RuntimeError("nobody expects the spanish inquisition") + + def my_generator(): + yield from Silly() + + with self.assertRaisesRegex(RuntimeError, "nobody expects the spanish inquisition"): + next(iter(my_generator())) + if __name__ == '__main__': unittest.main()