Visitar URL original
Update doctest,test_generators from v3.14.2 and fix generator bugs by youknowone · Pull Request #7067 · RustPython/RustPython · GitHub
Skip to content
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Update test_generators from v3.14.2
  • Loading branch information
CPython Developers authored and youknowone committed Feb 11, 2026
commit cafde31502b29a3ae32210ea6143124f77e09697
117 changes: 92 additions & 25 deletions Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import unittest
import weakref
import inspect
import textwrap
import types

from test import support
Expand Down Expand Up @@ -48,7 +49,6 @@ def test_raise_and_yield_from(self):

class FinalizationTest(unittest.TestCase):

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_frame_resurrect(self):
# A generator frame can be resurrected by a generator's finalization.
def gen():
Expand All @@ -68,7 +68,7 @@ def gen():
del frame
support.gc_collect()

@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true
def test_refcycle(self):
# A generator caught in a refcycle gets finalized anyway.
old_garbage = gc.garbage[:]
Expand All @@ -84,7 +84,7 @@ def gen():
g = gen()
next(g)
g.send(g)
self.assertGreater(sys.getrefcount(g), 2)
self.assertGreaterEqual(sys.getrefcount(g), 2)
self.assertFalse(finalized)
del g
support.gc_collect()
Expand Down Expand Up @@ -114,6 +114,28 @@ def g3(): return (yield from f())
gen.send(2)
self.assertEqual(cm.exception.value, 2)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 1
def test_generator_resurrect(self):
# Test that a resurrected generator still has a valid gi_code
resurrected = []

# Resurrect a generator in a finalizer
exec(textwrap.dedent("""
def gen():
try:
yield
except:
resurrected.append(g)

g = gen()
next(g)
"""), {"resurrected": resurrected})

support.gc_collect()

self.assertEqual(len(resurrected), 1)
self.assertIsInstance(resurrected[0].gi_code, types.CodeType)


class GeneratorTest(unittest.TestCase):

Expand Down Expand Up @@ -248,6 +270,28 @@ def loop():
#This should not raise
loop()

def test_genexpr_only_calls_dunder_iter_once(self):

class Iterator:

def __init__(self):
self.val = 0

def __next__(self):
if self.val == 2:
raise StopIteration
self.val += 1
return self.val

# No __iter__ method

class C:

def __iter__(self):
return Iterator()

self.assertEqual([1, 2], list(i for i in C()))


class ModifyUnderlyingIterableTest(unittest.TestCase):
iterables = [
Expand Down Expand Up @@ -276,23 +320,27 @@ def gen(it):
yield x
return gen(range(10))

def process_tests(self, get_generator):
def process_tests(self, get_generator, is_expr):
err_iterator = "'.*' object is not an iterator"
err_iterable = "'.*' object is not iterable"
for obj in self.iterables:
g_obj = get_generator(obj)
with self.subTest(g_obj=g_obj, obj=obj):
self.assertListEqual(list(g_obj), list(obj))
if is_expr:
self.assertRaisesRegex(TypeError, err_iterator, list, g_obj)
else:
self.assertListEqual(list(g_obj), list(obj))

g_iter = get_generator(iter(obj))
with self.subTest(g_iter=g_iter, obj=obj):
self.assertListEqual(list(g_iter), list(obj))

err_regex = "'.*' object is not iterable"
for obj in self.non_iterables:
g_obj = get_generator(obj)
with self.subTest(g_obj=g_obj):
self.assertRaisesRegex(TypeError, err_regex, list, g_obj)
err = err_iterator if is_expr else err_iterable
self.assertRaisesRegex(TypeError, err, list, g_obj)

@unittest.expectedFailure # AssertionError: TypeError not raised by list
def test_modify_f_locals(self):
def modify_f_locals(g, local, obj):
g.gi_frame.f_locals[local] = obj
Expand All @@ -304,10 +352,9 @@ def get_generator_genexpr(obj):
def get_generator_genfunc(obj):
return modify_f_locals(self.genfunc(), 'it', obj)

self.process_tests(get_generator_genexpr)
self.process_tests(get_generator_genfunc)
self.process_tests(get_generator_genexpr, True)
self.process_tests(get_generator_genfunc, False)

@unittest.expectedFailure # AssertionError: "'.*' object is not iterable" does not match "'complex' object is not an iterator"
def test_new_gen_from_gi_code(self):
def new_gen_from_gi_code(g, obj):
generator_func = types.FunctionType(g.gi_code, {})
Expand All @@ -319,8 +366,8 @@ def get_generator_genexpr(obj):
def get_generator_genfunc(obj):
return new_gen_from_gi_code(self.genfunc(), obj)

self.process_tests(get_generator_genexpr)
self.process_tests(get_generator_genfunc)
self.process_tests(get_generator_genexpr, True)
self.process_tests(get_generator_genfunc, False)


class ExceptionTest(unittest.TestCase):
Expand Down Expand Up @@ -451,7 +498,6 @@ def gen():
self.assertEqual(next(g), "done")
self.assertIsNone(sys.exception())

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_except_throw_bad_exception(self):
class E(Exception):
def __new__(cls, *args, **kwargs):
Expand Down Expand Up @@ -543,7 +589,6 @@ def f():
gen.send(None)
self.assertIsNone(gen.close())

@unittest.expectedFailure # AssertionError: None != 0
def test_close_return_value(self):
def f():
try:
Expand Down Expand Up @@ -590,7 +635,6 @@ def f():
next(gen)
self.assertIsNone(gen.close())

@unittest.expectedFailure # AssertionError: None != 0
def test_close_closed(self):
def f():
try:
Expand All @@ -616,7 +660,6 @@ def f():
with self.assertRaises(RuntimeError):
gen.close()

@unittest.expectedFailure # AssertionError: <test.test_generators.GeneratorCloseTest.test_close_releases_frame_locals.<locals>.Foo object at 0xb400007e3c212160> is not None
def test_close_releases_frame_locals(self):
# See gh-118272

Expand Down Expand Up @@ -679,7 +722,6 @@ def get_frame(index):
self.assertIn('a', frame_locals)
self.assertEqual(frame_locals['a'], 42)

@unittest.expectedFailure # AssertionError: 'a' not found in {'frame_locals1': None}
def test_frame_locals_outlive_generator(self):
frame_locals1 = None

Expand Down Expand Up @@ -828,7 +870,8 @@ def check_stack_names(self, frame, expected):
while frame:
name = frame.f_code.co_name
# Stop checking frames when we get to our test helper.
if name.startswith('check_') or name.startswith('call_'):
if (name.startswith('check_') or name.startswith('call_')
or name.startswith('test')):
break

names.append(name)
Expand Down Expand Up @@ -869,9 +912,27 @@ def call_throw(gen):

self.check_yield_from_example(call_throw)

def test_throw_with_yield_from_custom_generator(self):

class CustomGen:
def __init__(self, test):
self.test = test
def throw(self, *args):
self.test.check_stack_names(sys._getframe(), ['throw', 'g'])
def __iter__(self):
return self
def __next__(self):
return 42

def g(target):
yield from target

gen = g(CustomGen(self))
gen.send(None)
gen.throw(RuntimeError)


class YieldFromTests(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_generator_gi_yieldfrom(self):
def a():
self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_RUNNING)
Expand Down Expand Up @@ -1077,7 +1138,7 @@ def b():
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in g
File "<stdin>", line 2, in f
ZeroDivisionError: integer division or modulo by zero
ZeroDivisionError: division by zero
>>> next(k) # and the generator cannot be resumed
Traceback (most recent call last):
File "<stdin>", line 1, in ?
Expand Down Expand Up @@ -2632,14 +2693,18 @@ def printsolution(self, x):
>>> with support.catch_unraisable_exception() as cm:
... g = f()
... next(g)
... gen_repr = repr(g)
... del g
...
... cm.unraisable.err_msg == (f'Exception ignored while closing '
... f'generator {gen_repr}')
... cm.unraisable.exc_type == RuntimeError
... "generator ignored GeneratorExit" in str(cm.unraisable.exc_value)
... cm.unraisable.exc_traceback is not None
True
True
True
True

And errors thrown during closing should propagate:

Expand Down Expand Up @@ -2744,10 +2809,12 @@ def printsolution(self, x):
... invoke("del failed")
...
>>> with support.catch_unraisable_exception() as cm:
... l = Leaker()
... del l
... leaker = Leaker()
... del_repr = repr(type(leaker).__del__)
... del leaker
...
... cm.unraisable.object == Leaker.__del__
... cm.unraisable.err_msg == (f'Exception ignored while '
... f'calling deallocator {del_repr}')
... cm.unraisable.exc_type == RuntimeError
... str(cm.unraisable.exc_value) == "del failed"
... cm.unraisable.exc_traceback is not None
Expand All @@ -2774,7 +2841,7 @@ def printsolution(self, x):
}

def load_tests(loader, tests, pattern):
# tests.addTest(doctest.DocTestSuite()) # TODO: RUSTPYTHON
tests.addTest(doctest.DocTestSuite())
return tests


Expand Down