From c5f2b6b928cb77ddeecf37bd72e08dcd9875732a Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 3 Jan 2025 10:51:45 +0000 Subject: [PATCH 1/9] gh-128404: remove requires_working_socket from some of test_asyncgen.py --- Lib/test/test_asyncgen.py | 89 +++++++++------------------------------ 1 file changed, 19 insertions(+), 70 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index cdae58b3e89ae36..0a4b938a49cce05 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -1,15 +1,13 @@ +import asyncio import inspect import types import unittest import contextlib from test.support.import_helper import import_module -from test.support import gc_collect, requires_working_socket -asyncio = import_module("asyncio") +from test.support import gc_collect, requires_working_socket, async_yield as _async_yield -requires_working_socket(module=True) - _no_default = object() @@ -17,12 +15,11 @@ class AwaitException(Exception): pass -@types.coroutine -def awaitable(*, throw=False): +async def awaitable(*, throw=False): if throw: - yield ('throw',) + await _async_yield(('throw',)) else: - yield ('result',) + await _async_yield(('result',)) def run_until_complete(coro): @@ -398,12 +395,6 @@ async def gen(): an.send(None) def test_async_gen_asend_throw_concurrent_with_send(self): - import types - - @types.coroutine - def _async_yield(v): - return (yield v) - class MyExc(Exception): pass @@ -431,11 +422,6 @@ async def agenfn(): gen2.send(None) def test_async_gen_athrow_throw_concurrent_with_send(self): - import types - - @types.coroutine - def _async_yield(v): - return (yield v) class MyExc(Exception): pass @@ -464,12 +450,6 @@ async def agenfn(): gen2.send(None) def test_async_gen_asend_throw_concurrent_with_throw(self): - import types - - @types.coroutine - def _async_yield(v): - return (yield v) - class MyExc(Exception): pass @@ -502,11 +482,6 @@ async def agenfn(): gen2.send(None) def test_async_gen_athrow_throw_concurrent_with_throw(self): - import types - - @types.coroutine - def _async_yield(v): - return (yield v) class MyExc(Exception): pass @@ -572,12 +547,6 @@ async def gen(): aclose.close() def test_async_gen_asend_close_runtime_error(self): - import types - - @types.coroutine - def _async_yield(v): - return (yield v) - async def agenfn(): try: await _async_yield(None) @@ -593,11 +562,6 @@ async def agenfn(): gen.close() def test_async_gen_athrow_close_runtime_error(self): - import types - - @types.coroutine - def _async_yield(v): - return (yield v) class MyExc(Exception): pass @@ -647,6 +611,7 @@ async def agenfn(): self.assertEqual(cm.exception.value, 2) +@requires_working_socket() class AsyncGenAsyncioTest(unittest.TestCase): def setUp(self): @@ -737,7 +702,6 @@ async def __anext__(self): self.check_async_iterator_anext(MyAsyncIter) def test_python_async_iterator_types_coroutine_anext(self): - import types class MyAsyncIterWithTypesCoro: """Asynchronously yield 1, then 2.""" def __init__(self): @@ -1037,10 +1001,6 @@ async def do_test(): self.assertEqual(result, "completed") def test_anext_iter(self): - @types.coroutine - def _async_yield(v): - return (yield v) - class MyError(Exception): pass @@ -1082,16 +1042,15 @@ def test3(anext): self.assertEqual(g.send(None), 1) def test4(anext): - @types.coroutine - def _async_yield(v): - yield v * 10 - return (yield (v * 10 + 1)) + async def yield_twice(v): + await _async_yield(v*10) + return await _async_yield(v*10 + 1) async def agenfn(): try: - await _async_yield(1) + await yield_twice(1) except MyError: - await _async_yield(2) + await yield_twice(2) return yield @@ -1103,14 +1062,13 @@ async def agenfn(): g.throw(MyError('val')) def test5(anext): - @types.coroutine - def _async_yield(v): - yield v * 10 - return (yield (v * 10 + 1)) + async def yield_twice(v): + await _async_yield(v*10) + return await _async_yield(v*10 + 1) async def agenfn(): try: - await _async_yield(1) + await yield_twice(1) except MyError: return yield 'aaa' @@ -1122,13 +1080,12 @@ async def agenfn(): g.throw(MyError()) def test6(anext): - @types.coroutine - def _async_yield(v): - yield v * 10 - return (yield (v * 10 + 1)) + async def yield_twice(v): + await _async_yield(v*10) + return await _async_yield(v*10 + 1) async def agenfn(): - await _async_yield(1) + await yield_twice(1) yield 'aaa' agen = agenfn() @@ -2276,10 +2233,6 @@ class MyException(Exception): gc_collect() # does not warn unawaited def test_asend_send_already_running(self): - @types.coroutine - def _async_yield(v): - return (yield v) - async def agenfn(): while True: await _async_yield(1) @@ -2300,10 +2253,6 @@ async def agenfn(): def test_athrow_send_already_running(self): - @types.coroutine - def _async_yield(v): - return (yield v) - async def agenfn(): while True: await _async_yield(1) From b4c561faa5f22bb2bf9824b5e1d5a1edc4929d42 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 3 Jan 2025 11:10:59 +0000 Subject: [PATCH 2/9] extract some tests that don't need asyncio from under requires_working_socket --- Lib/test/test_asyncgen.py | 706 +++++++++++++++++++------------------- 1 file changed, 357 insertions(+), 349 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 0a4b938a49cce05..a6afabbd1029db9 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -1,3 +1,4 @@ +import functools import asyncio import inspect import types @@ -610,18 +611,243 @@ async def agenfn(): gen.send(None) self.assertEqual(cm.exception.value, 2) + def test_aiter_idempotent(self): + async def gen(): + yield 1 + applied_once = aiter(gen()) + applied_twice = aiter(applied_once) + self.assertIs(applied_once, applied_twice) + + def test_anext_iter(self): + class MyError(Exception): + pass + + async def agenfn(): + try: + await _async_yield(1) + except MyError: + await _async_yield(2) + return + yield + + def test1(anext): + agen = agenfn() + with contextlib.closing(anext(agen, "default").__await__()) as g: + self.assertEqual(g.send(None), 1) + self.assertEqual(g.throw(MyError()), 2) + try: + g.send(None) + except StopIteration as e: + err = e + else: + self.fail('StopIteration was not raised') + self.assertEqual(err.value, "default") + + def test2(anext): + agen = agenfn() + with contextlib.closing(anext(agen, "default").__await__()) as g: + self.assertEqual(g.send(None), 1) + self.assertEqual(g.throw(MyError()), 2) + with self.assertRaises(MyError): + g.throw(MyError()) + + def test3(anext): + agen = agenfn() + with contextlib.closing(anext(agen, "default").__await__()) as g: + self.assertEqual(g.send(None), 1) + g.close() + with self.assertRaisesRegex(RuntimeError, 'cannot reuse'): + self.assertEqual(g.send(None), 1) + + def test4(anext): + async def yield_twice(v): + await _async_yield(v*10) + return await _async_yield(v*10 + 1) + + async def agenfn(): + try: + await yield_twice(1) + except MyError: + await yield_twice(2) + return + yield + + agen = agenfn() + with contextlib.closing(anext(agen, "default").__await__()) as g: + self.assertEqual(g.send(None), 10) + self.assertEqual(g.throw(MyError()), 20) + with self.assertRaisesRegex(MyError, 'val'): + g.throw(MyError('val')) + + def test5(anext): + async def yield_twice(v): + await _async_yield(v*10) + return await _async_yield(v*10 + 1) + + async def agenfn(): + try: + await yield_twice(1) + except MyError: + return + yield 'aaa' + + agen = agenfn() + with contextlib.closing(anext(agen, "default").__await__()) as g: + self.assertEqual(g.send(None), 10) + with self.assertRaisesRegex(StopIteration, 'default'): + g.throw(MyError()) + + def test6(anext): + async def yield_twice(v): + await _async_yield(v*10) + return await _async_yield(v*10 + 1) + + async def agenfn(): + await yield_twice(1) + yield 'aaa' + + agen = agenfn() + with contextlib.closing(anext(agen, "default").__await__()) as g: + with self.assertRaises(MyError): + g.throw(MyError()) + + def run_test(test): + with self.subTest('pure-Python anext()'): + test(py_anext) + with self.subTest('builtin anext()'): + test(anext) + + run_test(test1) + run_test(test2) + run_test(test3) + run_test(test4) + run_test(test5) + run_test(test6) + + def test_async_gen_throw_custom_same_aclose_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + it = async_iterate() + + class MyException(Exception): + pass + + nxt = it.aclose() + with self.assertRaises(MyException): + nxt.throw(MyException) + + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + nxt.throw(MyException) + + def test_async_gen_throw_custom_same_athrow_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + it = async_iterate() + + class MyException(Exception): + pass + + nxt = it.athrow(MyException) + with self.assertRaises(MyException): + nxt.throw(MyException) + + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + nxt.throw(MyException) + + def test_async_gen_throw_same_aclose_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + it = async_iterate() + nxt = it.aclose() + with self.assertRaises(StopIteration): + nxt.throw(GeneratorExit) + + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + nxt.throw(GeneratorExit) + + def test_sync_anext_raises_exception(self): + # See: https://github.com/python/cpython/issues/131670 + msg = 'custom' + for exc_type in [ + StopAsyncIteration, + StopIteration, + ValueError, + Exception, + ]: + exc = exc_type(msg) + with self.subTest(exc=exc): + class A: + def __anext__(self): + raise exc + + with self.assertRaisesRegex(exc_type, msg): + anext(A()) + with self.assertRaisesRegex(exc_type, msg): + anext(A(), 1) + + def test_async_gen_send_same_athrow_coro_after_completion(self): + # gh-120321: an athrow() awaitable that needs more than one send() + # to complete must be closed on completion; sending to it again + # must raise instead of resuming the generator. + class YieldOnce: + def __await__(self): + yield + + async def async_iterate(): + try: + yield 1 + except ValueError: + await YieldOnce() + yield 2 + + it = async_iterate() + with self.assertRaises(StopIteration): + it.__anext__().send(None) + + nxt = it.athrow(ValueError) + # The exception handler suspends before the operation completes. + nxt.send(None) + with self.assertRaises(StopIteration) as cm: + nxt.send(None) + self.assertEqual(cm.exception.value, 2) + + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + nxt.send(None) + + with self.assertRaises(StopIteration): + it.aclose().send(None) @requires_working_socket() class AsyncGenAsyncioTest(unittest.TestCase): + loop_used = False - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) + @functools.cached_property + def loop(self): + self.loop_used = True + loop = asyncio.EventLoop() + self.addCleanup(loop.close) + return loop def tearDown(self): - self.loop.close() - self.loop = None - asyncio.set_event_loop(None) + self.assertTrue(self.loop_used) def check_async_iterator_anext(self, ait_class): with self.subTest(anext="pure-Python"): @@ -746,13 +972,6 @@ async def consume(): self.loop.run_until_complete(consume()) self.assertEqual(results, [1, 2]) - def test_aiter_idempotent(self): - async def gen(): - yield 1 - applied_once = aiter(gen()) - applied_twice = aiter(applied_once) - self.assertIs(applied_once, applied_twice) - def make_counter(self): state = {'n': 0} async def counter(): @@ -1000,127 +1219,21 @@ async def do_test(): result = self.loop.run_until_complete(do_test()) self.assertEqual(result, "completed") - def test_anext_iter(self): - class MyError(Exception): - pass - - async def agenfn(): - try: - await _async_yield(1) - except MyError: - await _async_yield(2) - return - yield - - def test1(anext): - agen = agenfn() - with contextlib.closing(anext(agen, "default").__await__()) as g: - self.assertEqual(g.send(None), 1) - self.assertEqual(g.throw(MyError()), 2) - try: - g.send(None) - except StopIteration as e: - err = e - else: - self.fail('StopIteration was not raised') - self.assertEqual(err.value, "default") - - def test2(anext): - agen = agenfn() - with contextlib.closing(anext(agen, "default").__await__()) as g: - self.assertEqual(g.send(None), 1) - self.assertEqual(g.throw(MyError()), 2) - with self.assertRaises(MyError): - g.throw(MyError()) - - def test3(anext): - agen = agenfn() - with contextlib.closing(anext(agen, "default").__await__()) as g: - self.assertEqual(g.send(None), 1) - g.close() - with self.assertRaisesRegex(RuntimeError, 'cannot reuse'): - self.assertEqual(g.send(None), 1) - - def test4(anext): - async def yield_twice(v): - await _async_yield(v*10) - return await _async_yield(v*10 + 1) - - async def agenfn(): - try: - await yield_twice(1) - except MyError: - await yield_twice(2) - return - yield - - agen = agenfn() - with contextlib.closing(anext(agen, "default").__await__()) as g: - self.assertEqual(g.send(None), 10) - self.assertEqual(g.throw(MyError()), 20) - with self.assertRaisesRegex(MyError, 'val'): - g.throw(MyError('val')) - - def test5(anext): - async def yield_twice(v): - await _async_yield(v*10) - return await _async_yield(v*10 + 1) - - async def agenfn(): - try: - await yield_twice(1) - except MyError: - return - yield 'aaa' - - agen = agenfn() - with contextlib.closing(anext(agen, "default").__await__()) as g: - self.assertEqual(g.send(None), 10) - with self.assertRaisesRegex(StopIteration, 'default'): - g.throw(MyError()) - - def test6(anext): - async def yield_twice(v): - await _async_yield(v*10) - return await _async_yield(v*10 + 1) - - async def agenfn(): - await yield_twice(1) - yield 'aaa' - - agen = agenfn() - with contextlib.closing(anext(agen, "default").__await__()) as g: - with self.assertRaises(MyError): - g.throw(MyError()) - - def run_test(test): - with self.subTest('pure-Python anext()'): - test(py_anext) - with self.subTest('builtin anext()'): - test(anext) - - run_test(test1) - run_test(test2) - run_test(test3) - run_test(test4) - run_test(test5) - run_test(test6) - - def test_aiter_bad_args(self): - async def gen(): - yield 1 - async def call_with_too_few_args(): - await aiter() - async def call_with_too_many_args(): - await aiter(gen(), 1) - async def call_with_wrong_type_arg(): - await aiter(1) - with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_too_few_args()) - with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_too_many_args()) - with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_wrong_type_arg()) + def test_aiter_bad_args(self): + async def gen(): + yield 1 + async def call_with_too_few_args(): + await aiter() + async def call_with_too_many_args(): + await aiter(gen(), 1) + async def call_with_wrong_type_arg(): + await aiter(1) + with self.assertRaises(TypeError): + self.loop.run_until_complete(call_with_too_few_args()) + with self.assertRaises(TypeError): + self.loop.run_until_complete(call_with_too_many_args()) + with self.assertRaises(TypeError): + self.loop.run_until_complete(call_with_wrong_type_arg()) async def to_list(self, gen): res = [] @@ -1311,26 +1424,6 @@ async def run(): self.loop.run_until_complete(run()) - def test_sync_anext_raises_exception(self): - # See: https://github.com/python/cpython/issues/131670 - msg = 'custom' - for exc_type in [ - StopAsyncIteration, - StopIteration, - ValueError, - Exception, - ]: - exc = exc_type(msg) - with self.subTest(exc=exc): - class A: - def __anext__(self): - raise exc - - with self.assertRaisesRegex(exc_type, msg): - anext(A()) - with self.assertRaisesRegex(exc_type, msg): - anext(A(), 1) - def test_async_gen_asyncio_anext_stopiteration(self): async def foo(): try: @@ -1865,87 +1958,6 @@ async def wait(): self.assertEqual(finalized, 2) - def test_async_gen_asyncio_shutdown_02(self): - messages = [] - - def exception_handler(loop, context): - messages.append(context) - - async def async_iterate(): - yield 1 - yield 2 - - it = async_iterate() - async def main(): - loop = asyncio.get_running_loop() - loop.set_exception_handler(exception_handler) - - async for i in it: - break - - asyncio.run(main()) - - self.assertEqual(messages, []) - - def test_async_gen_asyncio_shutdown_exception_01(self): - messages = [] - - def exception_handler(loop, context): - messages.append(context) - - async def async_iterate(): - try: - yield 1 - yield 2 - finally: - 1/0 - - it = async_iterate() - async def main(): - loop = asyncio.get_running_loop() - loop.set_exception_handler(exception_handler) - - async for i in it: - break - - asyncio.run(main()) - - message, = messages - self.assertEqual(message['asyncgen'], it) - self.assertIsInstance(message['exception'], ZeroDivisionError) - self.assertIn('an error occurred during closing of asynchronous generator', - message['message']) - - def test_async_gen_asyncio_shutdown_exception_02(self): - messages = [] - - def exception_handler(loop, context): - messages.append(context) - - async def async_iterate(): - try: - yield 1 - yield 2 - finally: - 1/0 - - async def main(): - loop = asyncio.get_running_loop() - loop.set_exception_handler(exception_handler) - - async for i in async_iterate(): - break - gc_collect() - - asyncio.run(main()) - - message, = messages - self.assertIsInstance(message['exception'], ZeroDivisionError) - self.assertIn('unhandled exception during asyncio.run() shutdown', - message['message']) - del message, messages - gc_collect() - def test_async_gen_expression_01(self): async def arange(n): for i in range(n): @@ -1977,29 +1989,6 @@ async def run(): res = self.loop.run_until_complete(run()) self.assertEqual(res, [i * 2 for i in range(1, 10)]) - def test_asyncgen_nonstarted_hooks_are_cancellable(self): - # See https://bugs.python.org/issue38013 - messages = [] - - def exception_handler(loop, context): - messages.append(context) - - async def async_iterate(): - yield 1 - yield 2 - - async def main(): - loop = asyncio.get_running_loop() - loop.set_exception_handler(exception_handler) - - async for i in async_iterate(): - break - - asyncio.run(main()) - - self.assertEqual([], messages) - gc_collect() - def test_async_gen_await_same_anext_coro_twice(self): async def async_iterate(): yield 1 @@ -2036,97 +2025,6 @@ async def run(): self.loop.run_until_complete(run()) - def test_async_gen_throw_same_aclose_coro_twice(self): - async def async_iterate(): - yield 1 - yield 2 - - it = async_iterate() - nxt = it.aclose() - with self.assertRaises(StopIteration): - nxt.throw(GeneratorExit) - - with self.assertRaisesRegex( - RuntimeError, - r"cannot reuse already awaited aclose\(\)/athrow\(\)" - ): - nxt.throw(GeneratorExit) - - def test_async_gen_throw_custom_same_aclose_coro_twice(self): - async def async_iterate(): - yield 1 - yield 2 - - it = async_iterate() - - class MyException(Exception): - pass - - nxt = it.aclose() - with self.assertRaises(MyException): - nxt.throw(MyException) - - with self.assertRaisesRegex( - RuntimeError, - r"cannot reuse already awaited aclose\(\)/athrow\(\)" - ): - nxt.throw(MyException) - - def test_async_gen_throw_custom_same_athrow_coro_twice(self): - async def async_iterate(): - yield 1 - yield 2 - - it = async_iterate() - - class MyException(Exception): - pass - - nxt = it.athrow(MyException) - with self.assertRaises(MyException): - nxt.throw(MyException) - - with self.assertRaisesRegex( - RuntimeError, - r"cannot reuse already awaited aclose\(\)/athrow\(\)" - ): - nxt.throw(MyException) - - def test_async_gen_send_same_athrow_coro_after_completion(self): - # gh-120321: an athrow() awaitable that needs more than one send() - # to complete must be closed on completion; sending to it again - # must raise instead of resuming the generator. - class YieldOnce: - def __await__(self): - yield - - async def async_iterate(): - try: - yield 1 - except ValueError: - await YieldOnce() - yield 2 - - it = async_iterate() - with self.assertRaises(StopIteration): - it.__anext__().send(None) - - nxt = it.athrow(ValueError) - # The exception handler suspends before the operation completes. - nxt.send(None) - with self.assertRaises(StopIteration) as cm: - nxt.send(None) - self.assertEqual(cm.exception.value, 2) - - with self.assertRaisesRegex( - RuntimeError, - r"cannot reuse already awaited aclose\(\)/athrow\(\)" - ): - nxt.send(None) - - with self.assertRaises(StopIteration): - it.aclose().send(None) - def test_async_gen_aclose_twice_with_different_coros(self): # Regression test for https://bugs.python.org/issue39606 async def async_iterate(): @@ -2271,5 +2169,115 @@ async def agenfn(): del gen2 gc_collect() # does not warn unawaited + +@requires_working_socket() +class AsyncGenAsyncioRunTestCase(unittest.TestCase): + def test_async_gen_asyncio_shutdown_02(self): + messages = [] + + def exception_handler(loop, context): + messages.append(context) + + async def async_iterate(): + yield 1 + yield 2 + + it = async_iterate() + async def main(): + loop = asyncio.get_running_loop() + loop.set_exception_handler(exception_handler) + + async for i in it: + break + + asyncio.run(main()) + + self.assertEqual(messages, []) + + def test_async_gen_asyncio_shutdown_exception_01(self): + messages = [] + + def exception_handler(loop, context): + messages.append(context) + + async def async_iterate(): + try: + yield 1 + yield 2 + finally: + 1/0 + + it = async_iterate() + async def main(): + loop = asyncio.get_running_loop() + loop.set_exception_handler(exception_handler) + + async for i in it: + break + + asyncio.run(main()) + + message, = messages + self.assertEqual(message['asyncgen'], it) + self.assertIsInstance(message['exception'], ZeroDivisionError) + self.assertIn('an error occurred during closing of asynchronous generator', + message['message']) + + def test_async_gen_asyncio_shutdown_exception_02(self): + messages = [] + + def exception_handler(loop, context): + messages.append(context) + + async def async_iterate(): + try: + yield 1 + yield 2 + finally: + 1/0 + + async def main(): + loop = asyncio.get_running_loop() + loop.set_exception_handler(exception_handler) + + async for i in async_iterate(): + break + gc_collect() + + asyncio.run(main()) + + message, = messages + self.assertIsInstance(message['exception'], ZeroDivisionError) + self.assertIn('unhandled exception during asyncio.run() shutdown', + message['message']) + del message, messages + gc_collect() + + def test_asyncgen_nonstarted_hooks_are_cancellable(self): + # See https://bugs.python.org/issue38013 + messages = [] + + def exception_handler(loop, context): + messages.append(context) + + async def async_iterate(): + yield 1 + yield 2 + + async def main(): + loop = asyncio.get_running_loop() + loop.set_exception_handler(exception_handler) + + async for i in async_iterate(): + break + + asyncio.run(main()) + + self.assertEqual([], messages) + gc_collect() + + + + if __name__ == "__main__": unittest.main() From b4d6597324838ffec7728147ed9431fd6b4a2962 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 3 Jan 2025 11:11:50 +0000 Subject: [PATCH 3/9] Update Lib/test/test_asyncgen.py --- Lib/test/test_asyncgen.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index a6afabbd1029db9..d2afd7cc1f52cfd 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -2277,7 +2277,5 @@ async def main(): gc_collect() - - if __name__ == "__main__": unittest.main() From 0b851a7b8fb85b1a3430400eea82f7cc98bd5aa3 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 4 Jan 2025 07:32:27 +0000 Subject: [PATCH 4/9] avoid setting the policy in test_asyngen --- Lib/test/test_asyncgen.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index d2afd7cc1f52cfd..c54cf9d583668b0 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -2170,6 +2170,9 @@ async def agenfn(): gc_collect() # does not warn unawaited +_asyncio_run = functools.partial(asyncio.run, loop_factory=asyncio.EventLoop) + + @requires_working_socket() class AsyncGenAsyncioRunTestCase(unittest.TestCase): def test_async_gen_asyncio_shutdown_02(self): @@ -2190,7 +2193,7 @@ async def main(): async for i in it: break - asyncio.run(main()) + _asyncio_run(main()) self.assertEqual(messages, []) @@ -2215,7 +2218,7 @@ async def main(): async for i in it: break - asyncio.run(main()) + _asyncio_run(main()) message, = messages self.assertEqual(message['asyncgen'], it) @@ -2244,7 +2247,7 @@ async def main(): break gc_collect() - asyncio.run(main()) + _asyncio_run(main()) message, = messages self.assertIsInstance(message['exception'], ZeroDivisionError) @@ -2271,7 +2274,7 @@ async def main(): async for i in async_iterate(): break - asyncio.run(main()) + _asyncio_run(main()) self.assertEqual([], messages) gc_collect() From 2512c9d4e97de316d2824e4051e46db73cbe7d7a Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 31 Aug 2026 19:53:49 +0100 Subject: [PATCH 5/9] remove now-unused import_module import The last import_module() call site went away when `asyncio = import_module("asyncio")` became a plain import, leaving the import orphaned and tripping ruff's F401 in the lint job. Co-Authored-By: Claude Opus 5 --- Lib/test/test_asyncgen.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index c54cf9d583668b0..8869fef634d1db1 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -5,7 +5,6 @@ import unittest import contextlib -from test.support.import_helper import import_module from test.support import gc_collect, requires_working_socket, async_yield as _async_yield From 2c74bcfa13962943c47eccd05f01364089bd036b Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 2 Sep 2026 08:30:22 +0100 Subject: [PATCH 6/9] move test_aiter_callable_errors out from under requires_working_socket It does not use the event loop, so the tearDown assertion that the loop was used fails when it stays in AsyncGenAsyncioTest. --- Lib/test/test_asyncgen.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 8869fef634d1db1..385ae849cea0ce4 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -617,6 +617,16 @@ async def gen(): applied_twice = aiter(applied_once) self.assertIs(applied_once, applied_twice) + def test_aiter_callable_errors(self): + async def gen(): + yield 1 + self.assertRaises(TypeError, aiter, gen(), 1) + self.assertRaises(TypeError, aiter, [1, 2], stop_exception=LookupError) + self.assertRaises(TypeError, aiter, len, stop_exception=42) + self.assertRaises(TypeError, aiter, len, + stop_exception=(LookupError, 42)) + self.assertRaises(TypeError, aiter, len, stop_exception=LookupError()) + def test_anext_iter(self): class MyError(Exception): pass @@ -1119,16 +1129,6 @@ async def main(): self.loop.run_until_complete(main()) self.assertEqual(cancelled, [1]) - def test_aiter_callable_errors(self): - async def gen(): - yield 1 - self.assertRaises(TypeError, aiter, gen(), 1) - self.assertRaises(TypeError, aiter, [1, 2], stop_exception=LookupError) - self.assertRaises(TypeError, aiter, len, stop_exception=42) - self.assertRaises(TypeError, aiter, len, - stop_exception=(LookupError, 42)) - self.assertRaises(TypeError, aiter, len, stop_exception=LookupError()) - def test_anext_bad_args(self): async def gen(): yield 1 From facb2e805a1cd9c7d2a94e9c45c48f2e6a14a6bc Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 2 Sep 2026 09:28:34 +0100 Subject: [PATCH 7/9] whitespace/import ordering fixes --- Lib/test/test_asyncgen.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 385ae849cea0ce4..efd2931a3dd7c76 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -1,5 +1,5 @@ -import functools import asyncio +import functools import inspect import types import unittest @@ -422,7 +422,6 @@ async def agenfn(): gen2.send(None) def test_async_gen_athrow_throw_concurrent_with_send(self): - class MyExc(Exception): pass @@ -482,7 +481,6 @@ async def agenfn(): gen2.send(None) def test_async_gen_athrow_throw_concurrent_with_throw(self): - class MyExc(Exception): pass @@ -562,7 +560,6 @@ async def agenfn(): gen.close() def test_async_gen_athrow_close_runtime_error(self): - class MyExc(Exception): pass @@ -844,6 +841,7 @@ async def async_iterate(): with self.assertRaises(StopIteration): it.aclose().send(None) + @requires_working_socket() class AsyncGenAsyncioTest(unittest.TestCase): loop_used = False From 29317ab9c214a9fa2111970c1fef8152bcc6b39c Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 2 Sep 2026 09:40:26 +0100 Subject: [PATCH 8/9] move more tests that don't need a real event loop --- Lib/test/test_asyncgen.py | 965 +++++++++++++++++++------------------- 1 file changed, 480 insertions(+), 485 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index efd2931a3dd7c76..7da5544428c8dea 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -32,7 +32,7 @@ def run_until_complete(coro): else: fut = coro.send(None) except StopIteration as ex: - return ex.args[0] + return ex.value if fut == ('throw',): exc = True @@ -614,6 +614,128 @@ async def gen(): applied_twice = aiter(applied_once) self.assertIs(applied_once, applied_twice) + def make_counter(self): + state = {'n': 0} + async def counter(): + state['n'] += 1 + return state['n'] + return counter + + def test_aiter_callable_stop(self): + self.assertEqual(to_list(aiter(self.make_counter(), 4)), [1, 2, 3]) + self.assertEqual(to_list(aiter(self.make_counter(), stop_value=4)), + [1, 2, 3]) + + def test_aiter_callable_stop_exception(self): + counter = self.make_counter() + async def spam(): + value = await counter() + if value > 3: + raise LookupError + return value + self.assertEqual(to_list(aiter(spam, stop_exception=LookupError)), + [1, 2, 3]) + counter = self.make_counter() + self.assertEqual( + to_list(aiter(spam, stop_exception=(ZeroDivisionError, + LookupError))), + [1, 2, 3]) + + def test_aiter_callable_stop_and_exception(self): + counter = self.make_counter() + async def spam(): + value = await counter() + if value > 5: + raise LookupError + return value + self.assertEqual( + to_list(aiter(spam, 3, stop_exception=LookupError)), [1, 2]) + counter = self.make_counter() + self.assertEqual( + to_list(aiter(spam, 100, stop_exception=LookupError)), + [1, 2, 3, 4, 5]) + + def test_aiter_callable_stop_async_iteration(self): + # StopAsyncIteration is the default stop exception + counter = self.make_counter() + async def spam(): + value = await counter() + if value > 3: + raise StopAsyncIteration + return value + self.assertEqual( + to_list(aiter(spam, stop_exception=StopAsyncIteration)), + [1, 2, 3]) + + def test_aiter_callable_leak_from_await(self): + # A StopAsyncIteration leaking from the await is replaced with + # RuntimeError (see PEP 525) + async def spam(): + raise StopAsyncIteration + it = aiter(spam, 10, stop_exception=LookupError) + with self.assertRaisesRegex(RuntimeError, + 'callable raised StopAsyncIteration') as cm: + run_until_complete(anext(it)) + self.assertIsInstance(cm.exception.__cause__, StopAsyncIteration) + # but if it matches stop_exception, it stops the iteration + it = aiter(spam, 10, stop_exception=(LookupError, StopAsyncIteration)) + with self.assertRaises(StopAsyncIteration): + run_until_complete(anext(it)) + + def test_aiter_callable_leak_from_call(self): + # StopIteration and StopAsyncIteration leaking from the call are + # replaced with RuntimeError (see PEP 525) + for exc in StopIteration, StopAsyncIteration: + with self.subTest(exc=exc): + def spam(): + raise exc + it = aiter(spam, 10, stop_exception=LookupError) + with self.assertRaisesRegex( + RuntimeError, f'callable raised {exc.__name__}') as cm: + run_until_complete(anext(it)) + self.assertIsInstance(cm.exception.__cause__, exc) + # but if it matches stop_exception, it stops the iteration + it = aiter(spam, 10, stop_exception=(LookupError, exc)) + with self.assertRaises(StopAsyncIteration): + run_until_complete(anext(it)) + + def test_aiter_callable_other_exception(self): + async def spam(): + raise ZeroDivisionError + it = aiter(spam, stop_exception=LookupError) + with self.assertRaises(ZeroDivisionError): + run_until_complete(anext(it)) + + def test_aiter_callable_exhausted(self): + it = aiter(self.make_counter(), 3) + self.assertEqual(to_list(it), [1, 2]) + self.assertEqual(run_until_complete(anext(it, 'default')), + 'default') + with self.assertRaises(StopAsyncIteration): + run_until_complete(anext(it)) + + def test_aiter_callable_lazy(self): + # The callable is only called when the awaitable is awaited + calls = [] + async def spam(): + calls.append(1) + return len(calls) + it = aiter(spam, 10) + awaitable = it.__anext__() + self.assertEqual(calls, []) + self.assertEqual(run_until_complete(awaitable), 1) + self.assertEqual(calls, [1]) + + def test_aiter_callable_awaitable(self): + it = aiter(self.make_counter(), 10) + awaitable = it.__anext__() + self.assertIsNone(awaitable.close()) + with self.assertRaises(RuntimeError): + run_until_complete(awaitable) + awaitable = it.__anext__() + with self.assertRaises(KeyError): + awaitable.throw(KeyError('injected')) + def test_aiter_callable_errors(self): async def gen(): yield 1 @@ -841,21 +963,6 @@ async def async_iterate(): with self.assertRaises(StopIteration): it.aclose().send(None) - -@requires_working_socket() -class AsyncGenAsyncioTest(unittest.TestCase): - loop_used = False - - @functools.cached_property - def loop(self): - self.loop_used = True - loop = asyncio.EventLoop() - self.addCleanup(loop.close) - return loop - - def tearDown(self): - self.assertTrue(self.loop_used) - def check_async_iterator_anext(self, ait_class): with self.subTest(anext="pure-Python"): self._check_async_iterator_anext(ait_class, py_anext) @@ -870,10 +977,10 @@ async def consume(): results.append(await anext(g)) results.append(await anext(g, 'buckle my shoe')) return results - res = self.loop.run_until_complete(consume()) + res = run_until_complete(consume()) self.assertEqual(res, [1, 2, 'buckle my shoe']) with self.assertRaises(StopAsyncIteration): - self.loop.run_until_complete(consume()) + run_until_complete(consume()) async def test_2(): g1 = ait_class() @@ -892,7 +999,7 @@ async def test_2(): return "completed" - result = self.loop.run_until_complete(test_2()) + result = run_until_complete(test_2()) self.assertEqual(result, "completed") def test_send(): @@ -910,7 +1017,7 @@ async def test_throw(): self.assertRaises(SyntaxError, obj.throw, SyntaxError) return "completed" - result = self.loop.run_until_complete(test_throw()) + result = run_until_complete(test_throw()) self.assertEqual(result, "completed") def test_async_generator_anext(self): @@ -959,7 +1066,7 @@ async def gen(): g = gen() async def consume(): return [i async for i in aiter(g)] - res = self.loop.run_until_complete(consume()) + res = run_until_complete(consume()) self.assertEqual(res, [1, 2]) def test_async_gen_aiter_class(self): @@ -976,157 +1083,9 @@ async def consume(): results.append(await anext(ait)) except StopAsyncIteration: break - self.loop.run_until_complete(consume()) + run_until_complete(consume()) self.assertEqual(results, [1, 2]) - def make_counter(self): - state = {'n': 0} - async def counter(): - state['n'] += 1 - return state['n'] - return counter - - def collect(self, ait): - async def consume(): - return [i async for i in ait] - return self.loop.run_until_complete(consume()) - - def test_aiter_callable_stop(self): - self.assertEqual(self.collect(aiter(self.make_counter(), 4)), [1, 2, 3]) - self.assertEqual(self.collect(aiter(self.make_counter(), stop_value=4)), - [1, 2, 3]) - - def test_aiter_callable_stop_exception(self): - counter = self.make_counter() - async def spam(): - value = await counter() - if value > 3: - raise LookupError - return value - self.assertEqual(self.collect(aiter(spam, stop_exception=LookupError)), - [1, 2, 3]) - counter = self.make_counter() - self.assertEqual( - self.collect(aiter(spam, stop_exception=(ZeroDivisionError, - LookupError))), - [1, 2, 3]) - - def test_aiter_callable_stop_and_exception(self): - counter = self.make_counter() - async def spam(): - value = await counter() - if value > 5: - raise LookupError - return value - self.assertEqual( - self.collect(aiter(spam, 3, stop_exception=LookupError)), [1, 2]) - counter = self.make_counter() - self.assertEqual( - self.collect(aiter(spam, 100, stop_exception=LookupError)), - [1, 2, 3, 4, 5]) - - def test_aiter_callable_stop_async_iteration(self): - # StopAsyncIteration is the default stop exception - counter = self.make_counter() - async def spam(): - value = await counter() - if value > 3: - raise StopAsyncIteration - return value - self.assertEqual( - self.collect(aiter(spam, stop_exception=StopAsyncIteration)), - [1, 2, 3]) - - def test_aiter_callable_leak_from_await(self): - # A StopAsyncIteration leaking from the await is replaced with - # RuntimeError (see PEP 525) - async def spam(): - raise StopAsyncIteration - it = aiter(spam, 10, stop_exception=LookupError) - with self.assertRaisesRegex(RuntimeError, - 'callable raised StopAsyncIteration') as cm: - self.loop.run_until_complete(anext(it)) - self.assertIsInstance(cm.exception.__cause__, StopAsyncIteration) - # but if it matches stop_exception, it stops the iteration - it = aiter(spam, 10, stop_exception=(LookupError, StopAsyncIteration)) - with self.assertRaises(StopAsyncIteration): - self.loop.run_until_complete(anext(it)) - - def test_aiter_callable_leak_from_call(self): - # StopIteration and StopAsyncIteration leaking from the call are - # replaced with RuntimeError (see PEP 525) - for exc in StopIteration, StopAsyncIteration: - with self.subTest(exc=exc): - def spam(): - raise exc - it = aiter(spam, 10, stop_exception=LookupError) - with self.assertRaisesRegex( - RuntimeError, f'callable raised {exc.__name__}') as cm: - self.loop.run_until_complete(anext(it)) - self.assertIsInstance(cm.exception.__cause__, exc) - # but if it matches stop_exception, it stops the iteration - it = aiter(spam, 10, stop_exception=(LookupError, exc)) - with self.assertRaises(StopAsyncIteration): - self.loop.run_until_complete(anext(it)) - - def test_aiter_callable_other_exception(self): - async def spam(): - raise ZeroDivisionError - it = aiter(spam, stop_exception=LookupError) - with self.assertRaises(ZeroDivisionError): - self.loop.run_until_complete(anext(it)) - - def test_aiter_callable_exhausted(self): - it = aiter(self.make_counter(), 3) - self.assertEqual(self.collect(it), [1, 2]) - self.assertEqual(self.loop.run_until_complete(anext(it, 'default')), - 'default') - with self.assertRaises(StopAsyncIteration): - self.loop.run_until_complete(anext(it)) - - def test_aiter_callable_lazy(self): - # The callable is only called when the awaitable is awaited - calls = [] - async def spam(): - calls.append(1) - return len(calls) - it = aiter(spam, 10) - awaitable = it.__anext__() - self.assertEqual(calls, []) - self.assertEqual(self.loop.run_until_complete(awaitable), 1) - self.assertEqual(calls, [1]) - - def test_aiter_callable_awaitable(self): - it = aiter(self.make_counter(), 10) - awaitable = it.__anext__() - self.assertIsNone(awaitable.close()) - with self.assertRaises(RuntimeError): - self.loop.run_until_complete(awaitable) - awaitable = it.__anext__() - with self.assertRaises(KeyError): - awaitable.throw(KeyError('injected')) - - def test_aiter_callable_cancel(self): - # Cancellation is delivered to the awaited callable result - cancelled = [] - async def spam(): - try: - await asyncio.sleep(10) - except asyncio.CancelledError: - cancelled.append(1) - raise - async def consume(): - async for _ in aiter(spam, None): - pass - async def main(): - task = asyncio.ensure_future(consume()) - await asyncio.sleep(0) - task.cancel() - with self.assertRaises(asyncio.CancelledError): - await task - self.loop.run_until_complete(main()) - self.assertEqual(cancelled, [1]) - def test_anext_bad_args(self): async def gen(): yield 1 @@ -1139,13 +1098,13 @@ async def call_with_wrong_type_args(): async def call_with_kwarg(): await anext(aiterator=gen()) with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_too_few_args()) + run_until_complete(call_with_too_few_args()) with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_too_many_args()) + run_until_complete(call_with_too_many_args()) with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_wrong_type_args()) + run_until_complete(call_with_wrong_type_args()) with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_kwarg()) + run_until_complete(call_with_kwarg()) def test_anext_bad_await(self): async def bad_awaitable(): @@ -1165,7 +1124,7 @@ def __anext__(self): with self.assertRaisesRegex(TypeError, regex): await awaitable return "completed" - result = self.loop.run_until_complete(bad_awaitable()) + result = run_until_complete(bad_awaitable()) self.assertEqual(result, "completed") async def check_anext_returning_iterator(self, aiter_class): @@ -1183,7 +1142,7 @@ def __aiter__(self): return self def __anext__(self): return iter("abc") - result = self.loop.run_until_complete(self.check_anext_returning_iterator(WithIterAnext)) + result = run_until_complete(self.check_anext_returning_iterator(WithIterAnext)) self.assertEqual(result, "completed") def test_anext_return_generator(self): @@ -1192,7 +1151,7 @@ def __aiter__(self): return self def __anext__(self): yield - result = self.loop.run_until_complete(self.check_anext_returning_iterator(WithGenAnext)) + result = run_until_complete(self.check_anext_returning_iterator(WithGenAnext)) self.assertEqual(result, "completed") def test_anext_await_raises(self): @@ -1213,7 +1172,7 @@ async def do_test(): with self.assertRaises(ZeroDivisionError): await awaitable return "completed" - result = self.loop.run_until_complete(do_test()) + result = run_until_complete(do_test()) self.assertEqual(result, "completed") def test_aiter_bad_args(self): @@ -1226,94 +1185,11 @@ async def call_with_too_many_args(): async def call_with_wrong_type_arg(): await aiter(1) with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_too_few_args()) + run_until_complete(call_with_too_few_args()) with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_too_many_args()) + run_until_complete(call_with_too_many_args()) with self.assertRaises(TypeError): - self.loop.run_until_complete(call_with_wrong_type_arg()) - - async def to_list(self, gen): - res = [] - async for i in gen: - res.append(i) - return res - - def test_async_gen_asyncio_01(self): - async def gen(): - yield 1 - await asyncio.sleep(0.01) - yield 2 - await asyncio.sleep(0.01) - return - yield 3 - - res = self.loop.run_until_complete(self.to_list(gen())) - self.assertEqual(res, [1, 2]) - - def test_async_gen_asyncio_02(self): - async def gen(): - yield 1 - await asyncio.sleep(0.01) - yield 2 - 1 / 0 - yield 3 - - with self.assertRaises(ZeroDivisionError): - self.loop.run_until_complete(self.to_list(gen())) - - def test_async_gen_asyncio_03(self): - loop = self.loop - - class Gen: - async def __aiter__(self): - yield 1 - await asyncio.sleep(0.01) - yield 2 - - res = loop.run_until_complete(self.to_list(Gen())) - self.assertEqual(res, [1, 2]) - - def test_async_gen_asyncio_anext_04(self): - async def foo(): - yield 1 - await asyncio.sleep(0.01) - try: - yield 2 - yield 3 - except ZeroDivisionError: - yield 1000 - await asyncio.sleep(0.01) - yield 4 - - async def run1(): - it = foo().__aiter__() - - self.assertEqual(await it.__anext__(), 1) - self.assertEqual(await it.__anext__(), 2) - self.assertEqual(await it.__anext__(), 3) - self.assertEqual(await it.__anext__(), 4) - with self.assertRaises(StopAsyncIteration): - await it.__anext__() - with self.assertRaises(StopAsyncIteration): - await it.__anext__() - - async def run2(): - it = foo().__aiter__() - - self.assertEqual(await it.__anext__(), 1) - self.assertEqual(await it.__anext__(), 2) - try: - it.__anext__().throw(ZeroDivisionError) - except StopIteration as ex: - self.assertEqual(ex.args[0], 1000) - else: - self.fail('StopIteration was not raised') - self.assertEqual(await it.__anext__(), 4) - with self.assertRaises(StopAsyncIteration): - await it.__anext__() - - self.loop.run_until_complete(run1()) - self.loop.run_until_complete(run2()) + run_until_complete(call_with_wrong_type_arg()) def test_async_gen_asyncio_anext_05(self): async def foo(): @@ -1348,7 +1224,7 @@ async def run(): with self.assertRaises(StopAsyncIteration): await it.__anext__() - self.loop.run_until_complete(run()) + run_until_complete(run()) def test_async_gen_asyncio_anext_06(self): DONE = 0 @@ -1382,7 +1258,7 @@ async def run(): await g.asend(None) DONE += 10 - self.loop.run_until_complete(run()) + run_until_complete(run()) self.assertEqual(DONE, 11) def test_async_gen_asyncio_anext_tuple(self): @@ -1402,7 +1278,7 @@ async def run(): with self.assertRaises(StopAsyncIteration): await it.__anext__() - self.loop.run_until_complete(run()) + run_until_complete(run()) def test_async_gen_asyncio_anext_tuple_no_exceptions(self): # StopAsyncIteration exceptions should be cleared. @@ -1419,30 +1295,354 @@ async def run(): res = await anext(it, ('a', 'b')) self.assertTupleEqual(res, ('a', 'b')) - self.loop.run_until_complete(run()) + run_until_complete(run()) + + def test_async_gen_asyncio_anext_stopiteration(self): + async def foo(): + try: + yield StopIteration(1) + except ZeroDivisionError: + yield StopIteration(3) + + async def run(): + it = foo().__aiter__() + + v = await it.__anext__() + self.assertIsInstance(v, StopIteration) + self.assertEqual(v.value, 1) + with self.assertRaises(StopIteration) as cm: + it.__anext__().throw(ZeroDivisionError) + v = cm.exception.args[0] + self.assertIsInstance(v, StopIteration) + self.assertEqual(v.value, 3) + with self.assertRaises(StopAsyncIteration): + await it.__anext__() + + run_until_complete(run()) + + def test_async_gen_asyncio_aclose_10(self): + DONE = 0 + + # test synchronous generators + def foo(): + try: + yield + except: + pass + g = foo() + g.send(None) + g.close() + + # now with asynchronous generators + + async def gen(): + nonlocal DONE + try: + yield + except: + pass + DONE = 1 + + async def run(): + nonlocal DONE + g = gen() + await g.asend(None) + await g.aclose() + DONE += 10 + + run_until_complete(run()) + self.assertEqual(DONE, 11) + + def test_async_gen_asyncio_aclose_11(self): + DONE = 0 + + # test synchronous generators + def foo(): + try: + yield + except: + pass + yield + g = foo() + g.send(None) + with self.assertRaisesRegex(RuntimeError, 'ignored GeneratorExit'): + g.close() + + # now with asynchronous generators + + async def gen(): + nonlocal DONE + try: + yield + except: + pass + yield + DONE += 1 + + async def run(): + nonlocal DONE + g = gen() + await g.asend(None) + with self.assertRaisesRegex(RuntimeError, 'ignored GeneratorExit'): + await g.aclose() + DONE += 10 + + run_until_complete(run()) + self.assertEqual(DONE, 10) + + def test_async_gen_asyncio_athrow_03(self): + DONE = 0 + + # test synchronous generators + def foo(): + try: + yield + except: + pass + g = foo() + g.send(None) + with self.assertRaises(StopIteration): + g.throw(ValueError) + + # now with asynchronous generators + + async def gen(): + nonlocal DONE + try: + yield + except: + pass + DONE = 1 + + async def run(): + nonlocal DONE + g = gen() + await g.asend(None) + with self.assertRaises(StopAsyncIteration): + await g.athrow(ValueError) + DONE += 10 + + run_until_complete(run()) + self.assertEqual(DONE, 11) + + def test_async_gen_asyncio_athrow_tuple(self): + async def gen(): + try: + yield 1 + except ZeroDivisionError: + yield (2,) + + async def run(): + g = gen() + v = await g.asend(None) + self.assertEqual(v, 1) + v = await g.athrow(ZeroDivisionError) + self.assertEqual(v, (2,)) + with self.assertRaises(StopAsyncIteration): + await g.asend(None) + + run_until_complete(run()) + + def test_async_gen_asyncio_athrow_stopiteration(self): + async def gen(): + try: + yield 1 + except ZeroDivisionError: + yield StopIteration(2) + + async def run(): + g = gen() + v = await g.asend(None) + self.assertEqual(v, 1) + v = await g.athrow(ZeroDivisionError) + self.assertIsInstance(v, StopIteration) + self.assertEqual(v.value, 2) + with self.assertRaises(StopAsyncIteration): + await g.asend(None) + + run_until_complete(run()) + + def test_async_gen_await_same_anext_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + async def run(): + it = async_iterate() + nxt = it.__anext__() + await nxt + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited __anext__\(\)/asend\(\)" + ): + await nxt + + await it.aclose() # prevent unfinished iterator warning + + run_until_complete(run()) + + def test_async_gen_await_same_aclose_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + async def run(): + it = async_iterate() + nxt = it.aclose() + await nxt + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + await nxt + + run_until_complete(run()) + + def test_async_gen_aclose_twice_with_different_coros(self): + # Regression test for https://bugs.python.org/issue39606 + async def async_iterate(): + yield 1 + yield 2 + + async def run(): + it = async_iterate() + await it.aclose() + await it.aclose() + + run_until_complete(run()) + + def test_async_gen_aclose_after_exhaustion(self): + # Regression test for https://bugs.python.org/issue39606 + async def async_iterate(): + yield 1 + yield 2 + + async def run(): + it = async_iterate() + async for _ in it: + pass + await it.aclose() + + run_until_complete(run()) + + +@requires_working_socket() +class AsyncGenAsyncioTest(unittest.TestCase): + loop_used = False + + @functools.cached_property + def loop(self): + self.loop_used = True + loop = asyncio.EventLoop() + self.addCleanup(loop.close) + return loop + + def tearDown(self): + self.assertTrue(self.loop_used) + + def test_aiter_callable_cancel(self): + # Cancellation is delivered to the awaited callable result + cancelled = [] + async def spam(): + try: + await asyncio.sleep(10) + except asyncio.CancelledError: + cancelled.append(1) + raise + async def consume(): + async for _ in aiter(spam, None): + pass + async def main(): + task = asyncio.ensure_future(consume()) + await asyncio.sleep(0) + task.cancel() + with self.assertRaises(asyncio.CancelledError): + await task + self.loop.run_until_complete(main()) + self.assertEqual(cancelled, [1]) + + async def to_list(self, gen): + res = [] + async for i in gen: + res.append(i) + return res + + def test_async_gen_asyncio_01(self): + async def gen(): + yield 1 + await asyncio.sleep(0.01) + yield 2 + await asyncio.sleep(0.01) + return + yield 3 + + res = self.loop.run_until_complete(self.to_list(gen())) + self.assertEqual(res, [1, 2]) + + def test_async_gen_asyncio_02(self): + async def gen(): + yield 1 + await asyncio.sleep(0.01) + yield 2 + 1 / 0 + yield 3 + + with self.assertRaises(ZeroDivisionError): + self.loop.run_until_complete(self.to_list(gen())) + + def test_async_gen_asyncio_03(self): + loop = self.loop + + class Gen: + async def __aiter__(self): + yield 1 + await asyncio.sleep(0.01) + yield 2 + + res = loop.run_until_complete(self.to_list(Gen())) + self.assertEqual(res, [1, 2]) - def test_async_gen_asyncio_anext_stopiteration(self): + def test_async_gen_asyncio_anext_04(self): async def foo(): + yield 1 + await asyncio.sleep(0.01) try: - yield StopIteration(1) + yield 2 + yield 3 except ZeroDivisionError: - yield StopIteration(3) + yield 1000 + await asyncio.sleep(0.01) + yield 4 - async def run(): + async def run1(): it = foo().__aiter__() - v = await it.__anext__() - self.assertIsInstance(v, StopIteration) - self.assertEqual(v.value, 1) - with self.assertRaises(StopIteration) as cm: + self.assertEqual(await it.__anext__(), 1) + self.assertEqual(await it.__anext__(), 2) + self.assertEqual(await it.__anext__(), 3) + self.assertEqual(await it.__anext__(), 4) + with self.assertRaises(StopAsyncIteration): + await it.__anext__() + with self.assertRaises(StopAsyncIteration): + await it.__anext__() + + async def run2(): + it = foo().__aiter__() + + self.assertEqual(await it.__anext__(), 1) + self.assertEqual(await it.__anext__(), 2) + try: it.__anext__().throw(ZeroDivisionError) - v = cm.exception.args[0] - self.assertIsInstance(v, StopIteration) - self.assertEqual(v.value, 3) + except StopIteration as ex: + self.assertEqual(ex.args[0], 1000) + else: + self.fail('StopIteration was not raised') + self.assertEqual(await it.__anext__(), 4) with self.assertRaises(StopAsyncIteration): await it.__anext__() - self.loop.run_until_complete(run()) + self.loop.run_until_complete(run1()) + self.loop.run_until_complete(run2()) def test_async_gen_asyncio_aclose_06(self): async def foo(): @@ -1545,76 +1745,6 @@ async def run(): self.loop.run_until_complete(run()) self.assertEqual(DONE, 1) - def test_async_gen_asyncio_aclose_10(self): - DONE = 0 - - # test synchronous generators - def foo(): - try: - yield - except: - pass - g = foo() - g.send(None) - g.close() - - # now with asynchronous generators - - async def gen(): - nonlocal DONE - try: - yield - except: - pass - DONE = 1 - - async def run(): - nonlocal DONE - g = gen() - await g.asend(None) - await g.aclose() - DONE += 10 - - self.loop.run_until_complete(run()) - self.assertEqual(DONE, 11) - - def test_async_gen_asyncio_aclose_11(self): - DONE = 0 - - # test synchronous generators - def foo(): - try: - yield - except: - pass - yield - g = foo() - g.send(None) - with self.assertRaisesRegex(RuntimeError, 'ignored GeneratorExit'): - g.close() - - # now with asynchronous generators - - async def gen(): - nonlocal DONE - try: - yield - except: - pass - yield - DONE += 1 - - async def run(): - nonlocal DONE - g = gen() - await g.asend(None) - with self.assertRaisesRegex(RuntimeError, 'ignored GeneratorExit'): - await g.aclose() - DONE += 10 - - self.loop.run_until_complete(run()) - self.assertEqual(DONE, 10) - def test_async_gen_asyncio_aclose_12(self): DONE = 0 @@ -1849,78 +1979,6 @@ async def run(): self.loop.run_until_complete(run()) self.assertEqual(DONE, 1) - def test_async_gen_asyncio_athrow_03(self): - DONE = 0 - - # test synchronous generators - def foo(): - try: - yield - except: - pass - g = foo() - g.send(None) - with self.assertRaises(StopIteration): - g.throw(ValueError) - - # now with asynchronous generators - - async def gen(): - nonlocal DONE - try: - yield - except: - pass - DONE = 1 - - async def run(): - nonlocal DONE - g = gen() - await g.asend(None) - with self.assertRaises(StopAsyncIteration): - await g.athrow(ValueError) - DONE += 10 - - self.loop.run_until_complete(run()) - self.assertEqual(DONE, 11) - - def test_async_gen_asyncio_athrow_tuple(self): - async def gen(): - try: - yield 1 - except ZeroDivisionError: - yield (2,) - - async def run(): - g = gen() - v = await g.asend(None) - self.assertEqual(v, 1) - v = await g.athrow(ZeroDivisionError) - self.assertEqual(v, (2,)) - with self.assertRaises(StopAsyncIteration): - await g.asend(None) - - self.loop.run_until_complete(run()) - - def test_async_gen_asyncio_athrow_stopiteration(self): - async def gen(): - try: - yield 1 - except ZeroDivisionError: - yield StopIteration(2) - - async def run(): - g = gen() - v = await g.asend(None) - self.assertEqual(v, 1) - v = await g.athrow(ZeroDivisionError) - self.assertIsInstance(v, StopIteration) - self.assertEqual(v.value, 2) - with self.assertRaises(StopAsyncIteration): - await g.asend(None) - - self.loop.run_until_complete(run()) - def test_async_gen_asyncio_shutdown_01(self): finalized = 0 @@ -1986,69 +2044,6 @@ async def run(): res = self.loop.run_until_complete(run()) self.assertEqual(res, [i * 2 for i in range(1, 10)]) - def test_async_gen_await_same_anext_coro_twice(self): - async def async_iterate(): - yield 1 - yield 2 - - async def run(): - it = async_iterate() - nxt = it.__anext__() - await nxt - with self.assertRaisesRegex( - RuntimeError, - r"cannot reuse already awaited __anext__\(\)/asend\(\)" - ): - await nxt - - await it.aclose() # prevent unfinished iterator warning - - self.loop.run_until_complete(run()) - - def test_async_gen_await_same_aclose_coro_twice(self): - async def async_iterate(): - yield 1 - yield 2 - - async def run(): - it = async_iterate() - nxt = it.aclose() - await nxt - with self.assertRaisesRegex( - RuntimeError, - r"cannot reuse already awaited aclose\(\)/athrow\(\)" - ): - await nxt - - self.loop.run_until_complete(run()) - - def test_async_gen_aclose_twice_with_different_coros(self): - # Regression test for https://bugs.python.org/issue39606 - async def async_iterate(): - yield 1 - yield 2 - - async def run(): - it = async_iterate() - await it.aclose() - await it.aclose() - - self.loop.run_until_complete(run()) - - def test_async_gen_aclose_after_exhaustion(self): - # Regression test for https://bugs.python.org/issue39606 - async def async_iterate(): - yield 1 - yield 2 - - async def run(): - it = async_iterate() - async for _ in it: - pass - await it.aclose() - - self.loop.run_until_complete(run()) - def test_async_gen_aclose_compatible_with_get_stack(self): async def async_generator(): yield object() From 2c1bab5e11be64974d3ac342c165a77a8af3f2d0 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 2 Sep 2026 09:48:21 +0100 Subject: [PATCH 9/9] use a list comprehension in the to_list calls --- Lib/test/test_asyncgen.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 7da5544428c8dea..ce26b799a6e47c9 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -40,10 +40,7 @@ def run_until_complete(coro): def to_list(gen): async def iterate(): - res = [] - async for i in gen: - res.append(i) - return res + return [i async for i in gen] return run_until_complete(iterate()) @@ -1562,10 +1559,7 @@ async def main(): self.assertEqual(cancelled, [1]) async def to_list(self, gen): - res = [] - async for i in gen: - res.append(i) - return res + return [i async for i in gen] def test_async_gen_asyncio_01(self): async def gen():