Skip to content

Prevent premature closing of scoped event loops #714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions docs/source/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Changelog
=========

0.23.3 (UNRELEASED)
===================
- Fixes a bug that caused event loops to be closed prematurely when using async generator fixtures with class scope or wider in a function-scoped test `#708 <https://github.com/pytest-dev/pytest-asyncio/issues/708>`_


0.23.2 (2023-12-04)
===================
- Fixes a bug that caused an internal pytest error when collecting .txt files `#703 <https://github.com/pytest-dev/pytest-asyncio/issues/703>`_
Expand Down
5 changes: 4 additions & 1 deletion pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ def scoped_event_loop(
new_loop_policy = event_loop_policy
with _temporary_event_loop_policy(new_loop_policy):
loop = asyncio.new_event_loop()
loop.__pytest_asyncio = True # type: ignore[attr-defined]
asyncio.set_event_loop(loop)
yield loop
loop.close()
Expand Down Expand Up @@ -749,7 +750,8 @@ def pytest_fixture_setup(
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
old_loop = policy.get_event_loop()
if old_loop is not loop:
is_pytest_asyncio_loop = getattr(old_loop, "__pytest_asyncio", False)
if old_loop is not loop and not is_pytest_asyncio_loop:
old_loop.close()
except RuntimeError:
# Either the current event loop has been set to None
Expand Down Expand Up @@ -965,6 +967,7 @@ def _session_event_loop(
new_loop_policy = event_loop_policy
with _temporary_event_loop_policy(new_loop_policy):
loop = asyncio.new_event_loop()
loop.__pytest_asyncio = True # type: ignore[attr-defined]
asyncio.set_event_loop(loop)
yield loop
loop.close()
Expand Down
30 changes: 30 additions & 0 deletions tests/markers/test_module_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,36 @@ async def test_runs_in_different_loop_as_fixture(async_fixture):
result.assert_outcomes(passed=1)


def test_allows_combining_module_scoped_asyncgen_fixture_with_function_scoped_test(
pytester: Pytester,
):
pytester.makepyfile(
dedent(
"""\
import asyncio

import pytest
import pytest_asyncio

loop: asyncio.AbstractEventLoop

@pytest_asyncio.fixture(scope="module")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
yield

@pytest.mark.asyncio(scope="function")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""
),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)


def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
pytester: Pytester,
):
Expand Down
31 changes: 31 additions & 0 deletions tests/markers/test_session_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,37 @@ async def test_runs_in_different_loop_as_fixture(async_fixture):
result.assert_outcomes(passed=1)


def test_allows_combining_session_scoped_asyncgen_fixture_with_function_scoped_test(
pytester: Pytester,
):
pytester.makepyfile(
__init__="",
test_mixed_scopes=dedent(
"""\
import asyncio

import pytest
import pytest_asyncio

loop: asyncio.AbstractEventLoop

@pytest_asyncio.fixture(scope="session")
async def async_fixture():
global loop
loop = asyncio.get_running_loop()
yield

@pytest.mark.asyncio
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
"""
),
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=1)


def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
pytester: Pytester,
):
Expand Down