Skip to content

Commit 9b79717

Browse files
committed
shutdown generators
1 parent 623ab74 commit 9b79717

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

pytest_asyncio/plugin.py

+22-15
Original file line numberDiff line numberDiff line change
@@ -708,11 +708,9 @@ def scoped_event_loop(
708708
event_loop_policy,
709709
) -> Iterator[asyncio.AbstractEventLoop]:
710710
new_loop_policy = event_loop_policy
711-
with _temporary_event_loop_policy(new_loop_policy):
712-
loop = _make_pytest_asyncio_loop(asyncio.new_event_loop())
711+
with _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop:
713712
asyncio.set_event_loop(loop)
714713
yield loop
715-
loop.close()
716714

717715
# @pytest.fixture does not register the fixture anywhere, so pytest doesn't
718716
# know it exists. We work around this by attaching the fixture function to the
@@ -1147,28 +1145,37 @@ def _retrieve_scope_root(item: Collector | Item, scope: str) -> Collector:
11471145
def event_loop(request: FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]:
11481146
"""Create an instance of the default event loop for each test case."""
11491147
new_loop_policy = request.getfixturevalue(event_loop_policy.__name__)
1150-
with _temporary_event_loop_policy(new_loop_policy):
1151-
loop = asyncio.get_event_loop_policy().new_event_loop()
1152-
# Add a magic value to the event loop, so pytest-asyncio can determine if the
1153-
# event_loop fixture was overridden. Other implementations of event_loop don't
1154-
# set this value.
1155-
# The magic value must be set as part of the function definition, because pytest
1156-
# seems to have multiple instances of the same FixtureDef or fixture function
1157-
loop = _make_pytest_asyncio_loop(loop)
1148+
with _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop:
11581149
yield loop
1159-
loop.close()
1150+
1151+
1152+
1153+
@contextlib.contextmanager
1154+
def _provide_event_loop() -> Iterator[asyncio.AbstractEventLoop]:
1155+
loop = asyncio.get_event_loop_policy().new_event_loop()
1156+
# Add a magic value to the event loop, so pytest-asyncio can determine if the
1157+
# event_loop fixture was overridden. Other implementations of event_loop don't
1158+
# set this value.
1159+
# The magic value must be set as part of the function definition, because pytest
1160+
# seems to have multiple instances of the same FixtureDef or fixture function
1161+
loop = _make_pytest_asyncio_loop(loop)
1162+
try:
1163+
yield loop
1164+
finally:
1165+
try:
1166+
loop.run_until_complete(loop.shutdown_asyncgens())
1167+
finally:
1168+
loop.close()
11601169

11611170

11621171
@pytest.fixture(scope="session")
11631172
def _session_event_loop(
11641173
request: FixtureRequest, event_loop_policy: AbstractEventLoopPolicy
11651174
) -> Iterator[asyncio.AbstractEventLoop]:
11661175
new_loop_policy = event_loop_policy
1167-
with _temporary_event_loop_policy(new_loop_policy):
1168-
loop = _make_pytest_asyncio_loop(asyncio.new_event_loop())
1176+
with _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop:
11691177
asyncio.set_event_loop(loop)
11701178
yield loop
1171-
loop.close()
11721179

11731180

11741181
@pytest.fixture(scope="session", autouse=True)

tests/test_event_loop_fixture.py

+27
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,30 @@ async def test_custom_policy_is_not_overwritten():
5353
)
5454
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
5555
result.assert_outcomes(passed=2)
56+
57+
58+
def test_event_loop_fixture_handles_unclosed_async_gen(
59+
pytester: Pytester,
60+
):
61+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
62+
pytester.makepyfile(
63+
dedent(
64+
"""\
65+
import asyncio
66+
import pytest
67+
68+
pytest_plugins = 'pytest_asyncio'
69+
70+
@pytest.mark.asyncio
71+
async def test_something():
72+
async def generator_fn():
73+
yield
74+
yield
75+
76+
gen = generator_fn()
77+
await gen.__anext__()
78+
"""
79+
)
80+
)
81+
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
82+
result.assert_outcomes(passed=1, warnings=0)

0 commit comments

Comments
 (0)