diff --git a/changelog.d/1344.fixed.rst b/changelog.d/1344.fixed.rst new file mode 100644 index 00000000..4982e915 --- /dev/null +++ b/changelog.d/1344.fixed.rst @@ -0,0 +1 @@ +Only warn about an unset ``asyncio_default_fixture_loop_scope`` configuration option when an async fixture is actually used. diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 38b75e41..13110d3a 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -52,6 +52,7 @@ PytestCollectionWarning, PytestDeprecationWarning, PytestPluginManager, + StashKey, ) if sys.version_info >= (3, 11): @@ -293,11 +294,19 @@ def _validate_scope(scope: str | None, option_name: str) -> None: ) +_DEFAULT_FIXTURE_LOOP_SCOPE_WARNING_EMITTED: StashKey[bool] = StashKey() + + +def _warn_default_fixture_loop_scope_unset(config: Config) -> None: + if config.stash.get(_DEFAULT_FIXTURE_LOOP_SCOPE_WARNING_EMITTED, False): + return + config.stash[_DEFAULT_FIXTURE_LOOP_SCOPE_WARNING_EMITTED] = True + warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) + + def pytest_configure(config: Config) -> None: default_fixture_loop_scope = config.getini("asyncio_default_fixture_loop_scope") _validate_scope(default_fixture_loop_scope, "asyncio_default_fixture_loop_scope") - if not default_fixture_loop_scope: - warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) default_test_loop_scope = config.getini("asyncio_default_test_loop_scope") _validate_scope(default_test_loop_scope, "asyncio_default_test_loop_scope") @@ -926,11 +935,10 @@ def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None: if not _is_coroutine_or_asyncgen(fixturedef.func): return (yield) default_loop_scope = request.config.getini("asyncio_default_fixture_loop_scope") - loop_scope = ( - getattr(fixturedef.func, "_loop_scope", None) - or default_loop_scope - or fixturedef.scope - ) + fixture_loop_scope = getattr(fixturedef.func, "_loop_scope", None) + loop_scope = fixture_loop_scope or default_loop_scope or fixturedef.scope + if not default_loop_scope and not fixture_loop_scope: + _warn_default_fixture_loop_scope_unset(request.config) runner_fixture_id = f"_{loop_scope}_scoped_runner" runner = request.getfixturevalue(runner_fixture_id) # Prevent the runner closing before the fixture's async teardown. diff --git a/tests/test_fixture_loop_scopes.py b/tests/test_fixture_loop_scopes.py index a25e56a0..2447d202 100644 --- a/tests/test_fixture_loop_scopes.py +++ b/tests/test_fixture_loop_scopes.py @@ -126,3 +126,48 @@ def test_invalid_default_fixture_loop_scope_raises_error(pytester: Pytester): "function, class, module, package, session." ] ) + + +def test_no_warning_for_unset_default_fixture_loop_scope_without_async_fixtures( + pytester: Pytester, +): + """Regression test for #1344.""" + pytester.makeini(dedent("""\ + [pytest] + asyncio_mode = strict + """)) + pytester.makepyfile(dedent("""\ + def test_no_async(): + pass + """)) + result = pytester.runpytest_subprocess("-Werror") + result.assert_outcomes(passed=1) + + +def test_warning_for_unset_default_fixture_loop_scope_with_async_fixture( + pytester: Pytester, +): + pytester.makeini(dedent("""\ + [pytest] + asyncio_mode = strict + """)) + pytester.makepyfile(dedent("""\ + import pytest + import pytest_asyncio + + @pytest_asyncio.fixture + async def async_fixture(): + return 42 + + @pytest.mark.asyncio + async def test_async(async_fixture): + assert async_fixture == 42 + """)) + result = pytester.runpytest_subprocess("-Wdefault") + result.assert_outcomes(passed=1, warnings=1) + result.stdout.fnmatch_lines( + [ + "*PytestDeprecationWarning: The configuration option " + '"asyncio_default_fixture_loop_scope" is unset.*' + ] + )