Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pytest-dev/pytest-asyncio
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.25.2
Choose a base ref
...
head repository: pytest-dev/pytest-asyncio
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.25.3
Choose a head ref
  • 1 commit
  • 3 files changed
  • 1 contributor

Commits on Jan 28, 2025

  1. fix: Avoid errors in cleanup of async generators when event loop is a…

    …lready closed
    
    check `is_closed()` before calling cleanup methods
    and degrade exceptions to warnings during cleanup to avoid problems
    minrk authored and seifertm committed Jan 28, 2025
    Copy the full SHA
    7c50192 View commit details
Showing with 72 additions and 5 deletions.
  1. +6 −1 docs/reference/changelog.rst
  2. +8 −4 pytest_asyncio/plugin.py
  3. +58 −0 tests/test_event_loop_fixture.py
7 changes: 6 additions & 1 deletion docs/reference/changelog.rst
Original file line number Diff line number Diff line change
@@ -2,11 +2,17 @@
Changelog
=========

0.25.3 (2025-01-28)
===================
- Avoid errors in cleanup of async generators when event loop is already closed `#1040 <https://github.com/pytest-dev/pytest-asyncio/issues/1040>`_


0.25.2 (2025-01-08)
===================

- Call ``loop.shutdown_asyncgens()`` before closing the event loop to ensure async generators are closed in the same manner as ``asyncio.run`` does `#1034 <https://github.com/pytest-dev/pytest-asyncio/pull/1034>`_


0.25.1 (2025-01-02)
===================
- Fixes an issue that caused a broken event loop when a function-scoped test was executed in between two tests with wider loop scope `#950 <https://github.com/pytest-dev/pytest-asyncio/issues/950>`_
@@ -22,7 +28,6 @@ Changelog
- Propagates `contextvars` set in async fixtures to other fixtures and tests on Python 3.11 and above. `#1008 <https://github.com/pytest-dev/pytest-asyncio/pull/1008>`_



0.24.0 (2024-08-22)
===================
- BREAKING: Updated minimum supported pytest version to v8.2.0
12 changes: 8 additions & 4 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
@@ -1164,10 +1164,14 @@ def _provide_event_loop() -> Iterator[asyncio.AbstractEventLoop]:
try:
yield loop
finally:
try:
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
loop.close()
# cleanup the event loop if it hasn't been cleaned up already
if not loop.is_closed():
try:
loop.run_until_complete(loop.shutdown_asyncgens())
except Exception as e:
warnings.warn(f"Error cleaning up asyncio loop: {e}", RuntimeWarning)
finally:
loop.close()


@pytest.fixture(scope="session")
58 changes: 58 additions & 0 deletions tests/test_event_loop_fixture.py
Original file line number Diff line number Diff line change
@@ -80,3 +80,61 @@ async def generator_fn():
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=0)


def test_event_loop_already_closed(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
import pytest_asyncio
pytest_plugins = 'pytest_asyncio'
@pytest_asyncio.fixture
async def _event_loop():
return asyncio.get_running_loop()
@pytest.fixture
def cleanup_after(_event_loop):
yield
# fixture has its own cleanup code
_event_loop.close()
@pytest.mark.asyncio
async def test_something(cleanup_after):
await asyncio.sleep(0.01)
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=0)


def test_event_loop_fixture_asyncgen_error(
pytester: Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
pytest_plugins = 'pytest_asyncio'
@pytest.mark.asyncio
async def test_something():
# mock shutdown_asyncgen failure
loop = asyncio.get_running_loop()
async def fail():
raise RuntimeError("mock error cleaning up...")
loop.shutdown_asyncgens = fail
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=1)