diff --git a/docs/source/reference/changelog.rst b/docs/source/reference/changelog.rst index 851bbea0..ec681a24 100644 --- a/docs/source/reference/changelog.rst +++ b/docs/source/reference/changelog.rst @@ -5,7 +5,10 @@ Changelog UNRELEASED ================= - Drop compatibility with pytest 6.1. Pytest-asyncio now depends on pytest 7.0 or newer. -- event_loop fixture teardown emits a ResourceWarning when the current event loop has not been closed. +- pytest-asyncio cleans up any stale event loops when setting up and tearing down the + event_loop fixture. This behavior has been deprecated and pytest-asyncio emits a + DeprecationWarning when tearing down the event_loop fixture and current event loop + has not been closed. 0.20.3 (22-12-08) ================= diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 0b6fa9db..c0aa4261 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -397,7 +397,9 @@ def pytest_fixture_setup( if old_loop is not loop: old_loop.close() except RuntimeError: - # Swallow this, since it's probably bad event loop hygiene. + # Either the current event loop has been set to None + # or the loop policy doesn't specify to create new loops + # or we're not in the main thread pass policy.set_event_loop(loop) return @@ -420,11 +422,13 @@ def _add_finalizers(fixturedef: FixtureDef, *finalizers: Callable[[], object]) - _UNCLOSED_EVENT_LOOP_WARNING = dedent( """\ - unclosed event loop %r. - Possible causes are: - 1. A custom "event_loop" fixture is used which doesn't close the loop - 2. Your code or one of your dependencies created a new event loop during - the test run + pytest-asyncio detected an unclosed event loop when tearing down the event_loop + fixture: %r + pytest-asyncio will close the event loop for you, but future versions of the + library will no longer do so. In order to ensure compatibility with future + versions, please make sure that: + 1. Any custom "event_loop" fixture properly closes the loop after yielding it + 2. Your code does not modify the event loop in async fixtures or tests """ ) @@ -436,14 +440,10 @@ def _close_event_loop() -> None: except RuntimeError: loop = None if loop is not None: - # Emit ResourceWarnings in the context of the fixture/test case - # rather than waiting for the interpreter to trigger the warning when - # garbage collecting the event loop. if not loop.is_closed(): warnings.warn( _UNCLOSED_EVENT_LOOP_WARNING % loop, - ResourceWarning, - source=loop, + DeprecationWarning, ) loop.close() diff --git a/tests/test_event_loop_fixture_finalizer.py b/tests/test_event_loop_fixture_finalizer.py index 2d12f7f4..b676df2d 100644 --- a/tests/test_event_loop_fixture_finalizer.py +++ b/tests/test_event_loop_fixture_finalizer.py @@ -88,7 +88,7 @@ async def test_async_with_explicit_fixture_request(event_loop): result.assert_outcomes(passed=1) -def test_event_loop_fixture_finalizer_raises_warning_when_loop_is_unclosed( +def test_event_loop_fixture_finalizer_raises_warning_when_fixture_leaves_loop_unclosed( pytester: Pytester, ): pytester.makepyfile( @@ -96,7 +96,6 @@ def test_event_loop_fixture_finalizer_raises_warning_when_loop_is_unclosed( """\ import asyncio import pytest - import pytest_asyncio pytest_plugins = 'pytest_asyncio' @@ -114,3 +113,25 @@ async def test_ends_with_unclosed_loop(): result = pytester.runpytest("--asyncio-mode=strict", "-W", "default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines("*unclosed event loop*") + + +def test_event_loop_fixture_finalizer_raises_warning_when_test_leaves_loop_unclosed( + pytester: Pytester, +): + pytester.makepyfile( + dedent( + """\ + import asyncio + import pytest + + pytest_plugins = 'pytest_asyncio' + + @pytest.mark.asyncio + async def test_ends_with_unclosed_loop(): + asyncio.set_event_loop(asyncio.new_event_loop()) + """ + ) + ) + result = pytester.runpytest("--asyncio-mode=strict", "-W", "default") + result.assert_outcomes(passed=1, warnings=1) + result.stdout.fnmatch_lines("*unclosed event loop*")