From 7499c3ddea48007e9cf51aafd4a4322cf3d68d71 Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Sun, 29 Mar 2026 03:19:00 +0100 Subject: [PATCH 1/5] Fix loop factory lifecycle --- changelog.d/1373.fixed.rst | 1 + pytest_asyncio/plugin.py | 17 +++- tests/test_loop_factory_parametrization.py | 107 +++++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1373.fixed.rst diff --git a/changelog.d/1373.fixed.rst b/changelog.d/1373.fixed.rst new file mode 100644 index 00000000..0e99d371 --- /dev/null +++ b/changelog.d/1373.fixed.rst @@ -0,0 +1 @@ +Fixed ``pytest_asyncio_loop_factories`` not installing the custom event loop as the current loop, and async fixture teardown/cache invalidation not being tied to the runner lifecycle. diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index a69350bd..5c116708 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -498,7 +498,15 @@ def _can_substitute(item: Function) -> bool: def setup(self) -> None: runner_fixture_id = f"_{self._loop_scope}_scoped_runner" - if runner_fixture_id not in self.fixturenames: + if runner_fixture_id in self.fixturenames: + return super().setup() + # The runner must be resolved before async fixtures when loop + # factories are configured. Otherwise, the async fixtures see a + # stale loop from the previous factory. + hook_caller = self.config.hook.pytest_asyncio_loop_factories + if hook_caller.get_hookimpls(): + self.fixturenames.insert(0, runner_fixture_id) + else: self.fixturenames.append(runner_fixture_id) return super().setup() @@ -846,6 +854,11 @@ def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None: ) runner_fixture_id = f"_{loop_scope}_scoped_runner" runner = request.getfixturevalue(runner_fixture_id) + # Prevent the runner closing before the fixture's async teardown. + runner_fixturedef = request._get_active_fixturedef(runner_fixture_id) + runner_fixturedef.addfinalizer( + functools.partial(fixturedef.finish, request=request) + ) synchronizer = _fixture_synchronizer(fixturedef, runner, request) _make_asyncio_fixture_function(synchronizer, loop_scope) with MonkeyPatch.context() as c: @@ -940,6 +953,8 @@ def _scoped_runner( debug=debug_mode, loop_factory=_asyncio_loop_factory, ).__enter__() + if _asyncio_loop_factory is not None: + _set_event_loop(runner.get_loop()) try: yield runner except Exception as e: diff --git a/tests/test_loop_factory_parametrization.py b/tests/test_loop_factory_parametrization.py index f6bac235..6d016338 100644 --- a/tests/test_loop_factory_parametrization.py +++ b/tests/test_loop_factory_parametrization.py @@ -571,3 +571,110 @@ async def test_b(): """)) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) + + +def test_sync_fixture_sees_same_loop_as_async_test_under_custom_factory( + pytester: Pytester, +) -> None: + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makeconftest(dedent("""\ + import asyncio + + class CustomEventLoop(asyncio.SelectorEventLoop): + pass + + def pytest_asyncio_loop_factories(config, item): + return {"custom": CustomEventLoop} + """)) + pytester.makepyfile(dedent("""\ + import asyncio + import pytest + import pytest_asyncio + + pytest_plugins = "pytest_asyncio" + + @pytest_asyncio.fixture(autouse=True) + def enable_debug_on_event_loop(): + asyncio.get_event_loop().set_debug(True) + + @pytest.mark.asyncio + async def test_debug_mode_visible(): + assert asyncio.get_running_loop().get_debug() + """)) + result = pytester.runpytest("--asyncio-mode=strict") + result.assert_outcomes(passed=1) + + +@pytest.mark.parametrize("loop_scope", ("module", "package", "session")) +def test_async_generator_fixture_teardown_runs_under_custom_factory( + pytester: Pytester, + loop_scope: str, +) -> None: + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makeconftest(dedent(f"""\ + import asyncio + import pytest_asyncio + + class CustomEventLoop(asyncio.SelectorEventLoop): + pass + + def pytest_asyncio_loop_factories(config, item): + return {{"custom": CustomEventLoop}} + + @pytest_asyncio.fixture( + autouse=True, scope="{loop_scope}", loop_scope="{loop_scope}" + ) + async def fixture_with_teardown(): + yield + print("TEARDOWN_EXECUTED") + """)) + pytester.makepyfile(dedent(f"""\ + import pytest + + pytest_plugins = "pytest_asyncio" + + @pytest.mark.asyncio(loop_scope="{loop_scope}") + async def test_passes(): + assert True + """)) + result = pytester.runpytest("--asyncio-mode=strict", "-s") + result.assert_outcomes(passed=1) + result.stdout.fnmatch_lines(["*TEARDOWN_EXECUTED*"]) + + +@pytest.mark.parametrize("loop_scope", ("module", "package", "session")) +def test_async_fixture_recreated_per_loop_factory_variant( + pytester: Pytester, + loop_scope: str, +) -> None: + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makeconftest(dedent(f"""\ + import asyncio + import pytest_asyncio + + class CustomLoopA(asyncio.SelectorEventLoop): + pass + + class CustomLoopB(asyncio.SelectorEventLoop): + pass + + def pytest_asyncio_loop_factories(config, item): + return {{"loop_a": CustomLoopA, "loop_b": CustomLoopB}} + + @pytest_asyncio.fixture(scope="{loop_scope}", loop_scope="{loop_scope}") + async def fixture_loop_type(): + return type(asyncio.get_running_loop()).__name__ + """)) + pytester.makepyfile(dedent(f"""\ + import asyncio + import pytest + + pytest_plugins = "pytest_asyncio" + + @pytest.mark.asyncio(loop_scope="{loop_scope}") + async def test_fixture_matches_running_loop(fixture_loop_type): + running_loop_type = type(asyncio.get_running_loop()).__name__ + assert fixture_loop_type == running_loop_type + """)) + result = pytester.runpytest("--asyncio-mode=strict", "-v") + result.assert_outcomes(passed=2) From adfd7fd11d046b80c14486fbfbb1e681da74ba1a Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Sun, 29 Mar 2026 15:47:20 +0100 Subject: [PATCH 2/5] Fix fixtures seeing the wrong event loop --- changelog.d/1373.fixed.rst | 2 +- pytest_asyncio/plugin.py | 60 ++++++++++- tests/test_loop_factory_parametrization.py | 113 +++++++++++++++++++++ tests/test_set_event_loop.py | 57 +++++++++++ 4 files changed, 230 insertions(+), 2 deletions(-) diff --git a/changelog.d/1373.fixed.rst b/changelog.d/1373.fixed.rst index 0e99d371..8858da3b 100644 --- a/changelog.d/1373.fixed.rst +++ b/changelog.d/1373.fixed.rst @@ -1 +1 @@ -Fixed ``pytest_asyncio_loop_factories`` not installing the custom event loop as the current loop, and async fixture teardown/cache invalidation not being tied to the runner lifecycle. +Fixed ``pytest_asyncio_loop_factories`` not installing the custom event loop as the current loop, and async fixture teardown/cache invalidation not being tied to the runner lifecycle, and sync ``@pytest_asyncio.fixture`` seeing the wrong event loop when multiple loop scopes are active. diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 5c116708..bd1ecaaf 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -328,8 +328,50 @@ def _fixture_synchronizer( return _wrap_asyncgen_fixture(fixture_function, runner, request) # type: ignore[arg-type] elif inspect.iscoroutinefunction(fixturedef.func): return _wrap_async_fixture(fixture_function, runner, request) # type: ignore[arg-type] + elif inspect.isgeneratorfunction(fixturedef.func): + return _wrap_syncgen_fixture(fixture_function, runner) # type: ignore[arg-type] else: - return fixturedef.func + return _wrap_sync_fixture(fixture_function, runner) # type: ignore[arg-type] + + +SyncGenFixtureParams = ParamSpec("SyncGenFixtureParams") +SyncGenFixtureYieldType = TypeVar("SyncGenFixtureYieldType") + + +def _wrap_syncgen_fixture( + fixture_function: Callable[ + SyncGenFixtureParams, Generator[SyncGenFixtureYieldType] + ], + runner: Runner, +) -> Callable[SyncGenFixtureParams, Generator[SyncGenFixtureYieldType]]: + @functools.wraps(fixture_function) + def _syncgen_fixture_wrapper( + *args: SyncGenFixtureParams.args, + **kwargs: SyncGenFixtureParams.kwargs, + ) -> Generator[SyncGenFixtureYieldType]: + with _temporary_event_loop(runner.get_loop()): + yield from fixture_function(*args, **kwargs) + + return _syncgen_fixture_wrapper + + +SyncFixtureParams = ParamSpec("SyncFixtureParams") +SyncFixtureReturnType = TypeVar("SyncFixtureReturnType") + + +def _wrap_sync_fixture( + fixture_function: Callable[SyncFixtureParams, SyncFixtureReturnType], + runner: Runner, +) -> Callable[SyncFixtureParams, SyncFixtureReturnType]: + @functools.wraps(fixture_function) + def _sync_fixture_wrapper( + *args: SyncFixtureParams.args, + **kwargs: SyncFixtureParams.kwargs, + ) -> SyncFixtureReturnType: + with _temporary_event_loop(runner.get_loop()): + return fixture_function(*args, **kwargs) + + return _sync_fixture_wrapper AsyncGenFixtureParams = ParamSpec("AsyncGenFixtureParams") @@ -729,6 +771,22 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: ) +@contextlib.contextmanager +def _temporary_event_loop(loop: AbstractEventLoop) -> Iterator[None]: + try: + old_loop = _get_event_loop_no_warn() + except RuntimeError: + old_loop = None + if old_loop is loop: + yield + return + _set_event_loop(loop) + try: + yield + finally: + _set_event_loop(old_loop) + + @contextlib.contextmanager def _temporary_event_loop_policy(policy: AbstractEventLoopPolicy) -> Iterator[None]: old_loop_policy = _get_event_loop_policy() diff --git a/tests/test_loop_factory_parametrization.py b/tests/test_loop_factory_parametrization.py index 6d016338..c6aec796 100644 --- a/tests/test_loop_factory_parametrization.py +++ b/tests/test_loop_factory_parametrization.py @@ -605,6 +605,119 @@ async def test_debug_mode_visible(): result.assert_outcomes(passed=1) +@pytest.mark.parametrize( + ("fixture_scope", "wider_scope"), + [ + ("function", "module"), + ("function", "package"), + ("function", "session"), + ("module", "session"), + ("package", "session"), + ], +) +def test_sync_fixture_sees_its_own_loop_when_wider_scoped_loop_active( + pytester: Pytester, + fixture_scope: str, + wider_scope: str, +) -> None: + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makeconftest(dedent(f"""\ + import asyncio + import pytest_asyncio + + class CustomEventLoop(asyncio.SelectorEventLoop): + pass + + def pytest_asyncio_loop_factories(config, item): + return {{"custom": CustomEventLoop}} + + @pytest_asyncio.fixture( + autouse=True, + scope="{wider_scope}", + loop_scope="{wider_scope}", + ) + async def wider_scoped_fixture(): + yield + + @pytest_asyncio.fixture( + autouse=True, + scope="{fixture_scope}", + loop_scope="{fixture_scope}", + ) + def sync_fixture_captures_loop(): + return id(asyncio.get_event_loop()) + """)) + pytester.makepyfile(dedent(f"""\ + import asyncio + import pytest + + pytest_plugins = "pytest_asyncio" + + @pytest.mark.asyncio(loop_scope="{fixture_scope}") + async def test_sync_fixture_and_test_see_same_loop(sync_fixture_captures_loop): + assert sync_fixture_captures_loop == id(asyncio.get_running_loop()) + """)) + result = pytester.runpytest("--asyncio-mode=strict") + result.assert_outcomes(passed=1) + + +@pytest.mark.parametrize( + ("fixture_scope", "wider_scope"), + [ + ("function", "module"), + ("function", "session"), + ("module", "session"), + ], +) +def test_sync_generator_fixture_teardown_sees_own_loop( + pytester: Pytester, + fixture_scope: str, + wider_scope: str, +) -> None: + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makeconftest(dedent(f"""\ + import asyncio + import pytest_asyncio + + class CustomEventLoop(asyncio.SelectorEventLoop): + pass + + def pytest_asyncio_loop_factories(config, item): + return {{"custom": CustomEventLoop}} + + @pytest_asyncio.fixture( + autouse=True, + scope="{wider_scope}", + loop_scope="{wider_scope}", + ) + async def wider_scoped_fixture(): + yield + + @pytest_asyncio.fixture( + autouse=True, + scope="{fixture_scope}", + loop_scope="{fixture_scope}", + ) + def sync_generator_fixture(): + loop_at_setup = id(asyncio.get_event_loop()) + yield loop_at_setup + loop_at_teardown = id(asyncio.get_event_loop()) + assert loop_at_setup == loop_at_teardown + """)) + pytester.makepyfile(dedent(f"""\ + import asyncio + import pytest + + pytest_plugins = "pytest_asyncio" + + @pytest.mark.asyncio(loop_scope="{fixture_scope}") + async def test_generator_fixture_sees_correct_loop(sync_generator_fixture): + assert sync_generator_fixture == id(asyncio.get_running_loop()) + """)) + result = pytester.runpytest("--asyncio-mode=strict") + result.assert_outcomes(passed=1) + + @pytest.mark.parametrize("loop_scope", ("module", "package", "session")) def test_async_generator_fixture_teardown_runs_under_custom_factory( pytester: Pytester, diff --git a/tests/test_set_event_loop.py b/tests/test_set_event_loop.py index 7f0d5dea..3854c04b 100644 --- a/tests/test_set_event_loop.py +++ b/tests/test_set_event_loop.py @@ -329,3 +329,60 @@ async def test_after_second(second_webserver): """)) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=5) + + +@pytest.mark.parametrize("test_loop_scope", ("module", "package", "session")) +@pytest.mark.parametrize( + "loop_breaking_action", + [ + "asyncio.set_event_loop(None)", + "asyncio.run(asyncio.sleep(0))", + pytest.param( + "with asyncio.Runner(): pass", + marks=pytest.mark.skipif( + sys.version_info < (3, 11), + reason="asyncio.Runner requires Python 3.11+", + ), + ), + ], +) +def test_sync_fixture_sees_correct_loop_after_loop_broken_with_factory( + pytester: Pytester, + test_loop_scope: str, + loop_breaking_action: str, +): + pytester.makeini(dedent(f"""\ + [pytest] + asyncio_default_test_loop_scope = {test_loop_scope} + asyncio_default_fixture_loop_scope = function + """)) + pytester.makepyfile(dedent(f"""\ + import asyncio + import pytest + import pytest_asyncio + + pytest_plugins = "pytest_asyncio" + + class CustomEventLoop(asyncio.SelectorEventLoop): + pass + + def pytest_asyncio_loop_factories(config, item): + return {{"custom": CustomEventLoop}} + + @pytest.mark.asyncio + async def test_before(): + pass + + def test_break_event_loop(): + {loop_breaking_action} + + @pytest_asyncio.fixture(loop_scope="{test_loop_scope}") + def sync_fixture_loop_id(): + return id(asyncio.get_event_loop()) + + @pytest.mark.asyncio + async def test_sync_fixture_sees_correct_loop(sync_fixture_loop_id): + assert sync_fixture_loop_id == id(asyncio.get_running_loop()) + """)) + result = pytester.runpytest_subprocess() + result.assert_outcomes(passed=3) From fb9fe7e8c951d336bbaab6ca7ee8d5570bb3a6f7 Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Tue, 31 Mar 2026 19:30:47 +0100 Subject: [PATCH 3/5] Fix event loop leak on Python <3.14 --- changelog.d/1373.fixed.rst | 2 +- pytest_asyncio/plugin.py | 20 +++++++++--- tests/test_loop_factory_parametrization.py | 36 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/changelog.d/1373.fixed.rst b/changelog.d/1373.fixed.rst index 8858da3b..793b3b06 100644 --- a/changelog.d/1373.fixed.rst +++ b/changelog.d/1373.fixed.rst @@ -1 +1 @@ -Fixed ``pytest_asyncio_loop_factories`` not installing the custom event loop as the current loop, and async fixture teardown/cache invalidation not being tied to the runner lifecycle, and sync ``@pytest_asyncio.fixture`` seeing the wrong event loop when multiple loop scopes are active. +Fixed ``pytest_asyncio_loop_factories`` not installing the custom event loop as the current loop, async fixture teardown/cache invalidation not being tied to the runner lifecycle, sync ``@pytest_asyncio.fixture`` seeing the wrong event loop when multiple loop scopes are active, and an event loop leak on Python 3.10-3.13. diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index bd1ecaaf..5378b711 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -788,12 +788,19 @@ def _temporary_event_loop(loop: AbstractEventLoop) -> Iterator[None]: @contextlib.contextmanager -def _temporary_event_loop_policy(policy: AbstractEventLoopPolicy) -> Iterator[None]: +def _temporary_event_loop_policy( + policy: AbstractEventLoopPolicy, + *, + has_custom_factory: bool, +) -> Iterator[None]: old_loop_policy = _get_event_loop_policy() - try: - old_loop = _get_event_loop_no_warn() - except RuntimeError: + if has_custom_factory: old_loop = None + else: + try: + old_loop = _get_event_loop_no_warn() + except RuntimeError: + old_loop = None _set_event_loop_policy(policy) try: yield @@ -1006,7 +1013,10 @@ def _scoped_runner( ) -> Iterator[Runner]: new_loop_policy = event_loop_policy debug_mode = _get_asyncio_debug(request.config) - with _temporary_event_loop_policy(new_loop_policy): + with _temporary_event_loop_policy( + new_loop_policy, + has_custom_factory=_asyncio_loop_factory is not None, + ): runner = Runner( debug=debug_mode, loop_factory=_asyncio_loop_factory, diff --git a/tests/test_loop_factory_parametrization.py b/tests/test_loop_factory_parametrization.py index c6aec796..7cc629ef 100644 --- a/tests/test_loop_factory_parametrization.py +++ b/tests/test_loop_factory_parametrization.py @@ -533,6 +533,42 @@ async def test_uses_custom_loop(): result.assert_outcomes(passed=1) +def test_no_event_loop_leak_with_custom_factory(pytester: Pytester) -> None: + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makeconftest(dedent("""\ + import asyncio + import pytest_asyncio + + class CustomEventLoop(asyncio.SelectorEventLoop): + pass + + def pytest_asyncio_loop_factories(config, item): + return {"custom": CustomEventLoop} + + @pytest_asyncio.fixture(autouse=True, scope="session", loop_scope="session") + async def session_fixture(): + yield + + @pytest_asyncio.fixture(autouse=True) + def sync_fixture(): + asyncio.get_event_loop() + """)) + pytester.makepyfile(dedent("""\ + import pytest + + pytest_plugins = "pytest_asyncio" + + @pytest.mark.asyncio + async def test_passes(): + assert True + """)) + result = pytester.runpytest_subprocess( + "--asyncio-mode=auto", "-W", "error::ResourceWarning" + ) + result.assert_outcomes(passed=1) + result.stderr.no_fnmatch_line("*unclosed event loop*") + + def test_function_loop_scope_allows_per_test_factories_with_session_default( pytester: Pytester, ) -> None: From dbe0009a921ecfaadcbd2ebe7add7b13ef6c592b Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Thu, 2 Apr 2026 01:08:22 +0100 Subject: [PATCH 4/5] Resolve fixture early --- pytest_asyncio/plugin.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 5378b711..610cf2b0 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -540,16 +540,14 @@ def _can_substitute(item: Function) -> bool: def setup(self) -> None: runner_fixture_id = f"_{self._loop_scope}_scoped_runner" - if runner_fixture_id in self.fixturenames: - return super().setup() - # The runner must be resolved before async fixtures when loop - # factories are configured. Otherwise, the async fixtures see a - # stale loop from the previous factory. + if runner_fixture_id not in self.fixturenames: + self.fixturenames.append(runner_fixture_id) + # When loop factories are configured, resolve the loop factory + # fixture early so that a factory variant change cascades cache + # invalidation before any async fixture checks its cache. hook_caller = self.config.hook.pytest_asyncio_loop_factories if hook_caller.get_hookimpls(): - self.fixturenames.insert(0, runner_fixture_id) - else: - self.fixturenames.append(runner_fixture_id) + _ = self._request.getfixturevalue(_asyncio_loop_factory.__name__) return super().setup() def runtest(self) -> None: From f819c496a55c95d65ec99352a8fe03962e955971 Mon Sep 17 00:00:00 2001 From: Tom Kuson Date: Fri, 3 Apr 2026 12:34:13 +0200 Subject: [PATCH 5/5] Skip ID for single factories --- pytest_asyncio/plugin.py | 4 ++- tests/test_loop_factory_parametrization.py | 29 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 610cf2b0..54f5e307 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -760,10 +760,12 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: metafunc.fixturenames.append(_asyncio_loop_factory.__name__) default_loop_scope = _get_default_test_loop_scope(metafunc.config) loop_scope = marker_loop_scope or default_loop_scope + # pytest.HIDDEN_PARAM was added in pytest 8.4 + hide_id = len(effective_factories) == 1 and hasattr(pytest, "HIDDEN_PARAM") metafunc.parametrize( _asyncio_loop_factory.__name__, effective_factories.values(), - ids=effective_factories.keys(), + ids=(pytest.HIDDEN_PARAM,) if hide_id else effective_factories.keys(), indirect=True, scope=loop_scope, ) diff --git a/tests/test_loop_factory_parametrization.py b/tests/test_loop_factory_parametrization.py index 7cc629ef..8221d135 100644 --- a/tests/test_loop_factory_parametrization.py +++ b/tests/test_loop_factory_parametrization.py @@ -6,6 +6,35 @@ from pytest import Pytester +@pytest.mark.skipif( + not hasattr(pytest, "HIDDEN_PARAM"), + reason="pytest.HIDDEN_PARAM requires pytest 9.0+", +) +def test_single_factory_does_not_add_suffix_to_test_name( + pytester: Pytester, +) -> None: + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makeconftest(dedent("""\ + import asyncio + + def pytest_asyncio_loop_factories(config, item): + return {"asyncio": asyncio.new_event_loop} + """)) + pytester.makepyfile(dedent("""\ + import pytest + + pytest_plugins = "pytest_asyncio" + + @pytest.mark.asyncio + async def test_example(): + assert True + """)) + result = pytester.runpytest("--asyncio-mode=strict", "--collect-only", "-q") + result.stdout.fnmatch_lines( + ["test_single_factory_does_not_add_suffix_to_test_name.py::test_example"] + ) + + def test_named_hook_factories_apply_to_async_tests(pytester: Pytester) -> None: pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makeconftest(dedent("""\