From c38173253d3512bc1a5e1fd6124cda0f99a26231 Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Fri, 9 Oct 2026 13:00:04 +0530 Subject: [PATCH 1/8] Fix async fixture autouse=True support - In handle_fixture, detect autouse fixtures (via fixturedef._autouse) and store them on the test item (request.node._trio_autouse_fixtures) - In _bootstrap_fixtures_and_run_test, add autouse fixtures to test's kwargs so test waits for them, and include them in the nursery for execution - Filter out autouse fixture kwargs when calling the test function - Add comprehensive tests for autouse async fixtures --- pytest_trio/_tests/test_autouse.py | 178 +++++++++++++++++++++++++++++ pytest_trio/plugin.py | 42 ++++++- 2 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 pytest_trio/_tests/test_autouse.py diff --git a/pytest_trio/_tests/test_autouse.py b/pytest_trio/_tests/test_autouse.py new file mode 100644 index 0000000..a6bea3b --- /dev/null +++ b/pytest_trio/_tests/test_autouse.py @@ -0,0 +1,178 @@ +"""Test to reproduce the autouse async fixture bug.""" +import pytest +import trio + + +def test_autouse_async_fixture_with_trio_mark(testdir): + """Test that autouse=True async fixture runs with @pytest.mark.trio.""" + testdir.makepyfile( + """ + import pytest + import trio + + autouse_ran = [] + + @pytest.fixture(autouse=True) + async def autouse_fixture(): + await trio.sleep(0) + autouse_ran.append(True) + + @pytest.mark.trio + async def test_with_autouse(): + assert autouse_ran == [True] + """ + ) + + result = testdir.runpytest("-v", "-s") + print("STDOUT:", result.stdout.str()) + print("STDERR:", result.stderr.str()) + result.assert_outcomes(passed=1) + + +def test_autouse_async_fixture_in_trio_mode(testdir): + """Test that autouse=True async fixture runs in trio_mode.""" + testdir.makefile(".ini", pytest="[pytest]\ntrio_mode = true\n") + testdir.makepyfile( + """ + import pytest + import trio + + autouse_ran = [] + + @pytest.fixture(autouse=True) + async def autouse_fixture(): + await trio.sleep(0) + autouse_ran.append(True) + + async def test_with_autouse(): + assert autouse_ran == [True] + """ + ) + + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + +def test_multiple_autouse_async_fixtures(testdir): + """Test that multiple autouse=True async fixtures all run.""" + testdir.makepyfile( + """ + import pytest + import trio + + autouse_ran = [] + + @pytest.fixture(autouse=True) + async def autouse_fixture1(): + await trio.sleep(0) + autouse_ran.append("fixture1") + + @pytest.fixture(autouse=True) + async def autouse_fixture2(): + await trio.sleep(0) + autouse_ran.append("fixture2") + + @pytest.mark.trio + async def test_with_autouse(): + assert set(autouse_ran) == {"fixture1", "fixture2"} + """ + ) + + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + +def test_autouse_async_fixture_with_dependency(testdir): + """Test that autouse async fixture can depend on other fixtures.""" + testdir.makepyfile( + """ + import pytest + import trio + + autouse_ran = [] + regular_ran = [] + + @pytest.fixture + async def regular_fixture(): + await trio.sleep(0) + regular_ran.append("regular") + return "regular_value" + + @pytest.fixture(autouse=True) + async def autouse_fixture(regular_fixture): + await trio.sleep(0) + autouse_ran.append(regular_fixture) + + @pytest.mark.trio + async def test_with_autouse(): + assert regular_ran == ["regular"] + assert autouse_ran == ["regular_value"] + """ + ) + + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + +def test_autouse_async_yield_fixture(testdir): + """Test that autouse=True async yield fixture runs setup and teardown.""" + testdir.makepyfile( + """ + import pytest + import trio + + autouse_ran = [] + + @pytest.fixture(autouse=True) + async def autouse_fixture(): + await trio.sleep(0) + autouse_ran.append("setup") + yield + autouse_ran.append("teardown") + + @pytest.mark.trio + async def test_with_autouse(): + assert autouse_ran == ["setup"] + """ + ) + + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + +def test_autouse_async_fixture_with_regular_test_dependency(testdir): + """Test that autouse async fixture works when test also has regular fixture deps.""" + testdir.makepyfile( + """ + import pytest + import trio + + autouse_ran = [] + regular_ran = [] + + @pytest.fixture + def regular_fixture(): + regular_ran.append("regular") + return "regular_value" + + @pytest.fixture(autouse=True) + async def autouse_fixture(): + await trio.sleep(0) + autouse_ran.append("autouse") + + @pytest.mark.trio + async def test_with_both(regular_fixture): + assert regular_ran == ["regular"] + assert autouse_ran == ["autouse"] + assert regular_fixture == "regular_value" + """ + ) + + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + +if __name__ == "__main__": + # Run with pytest directly for debugging + import sys + sys.exit(pytest.main([__file__, "-v"])) \ No newline at end of file diff --git a/pytest_trio/plugin.py b/pytest_trio/plugin.py index 3bc517c..3ba526f 100644 --- a/pytest_trio/plugin.py +++ b/pytest_trio/plugin.py @@ -234,7 +234,10 @@ async def run(self, test_ctx, contextvars_ctx): assert not self.user_done_events func_value = None assert not test_ctx.crashed - await self._func(**resolved_kwargs) + # Filter out autouse fixtures (keys starting with _trio_autouse_) + test_kwargs = {k: v for k, v in resolved_kwargs.items() + if not k.startswith("_trio_autouse_")} + await self._func(**test_kwargs) else: func_value = self._func(**resolved_kwargs) if isinstance(func_value, Coroutine): @@ -388,11 +391,28 @@ async def _bootstrap_fixtures_and_run_test(**kwargs): "".format(testfunc.__name__), testfunc, kwargs, is_test=True ) + # Add autouse trio fixtures to test's kwargs so test waits for them + if hasattr(item, "_trio_autouse_fixtures"): + for fixture in item._trio_autouse_fixtures: + # Use a unique key that won't conflict with real fixture names + autouse_key = f"_trio_autouse_{fixture.name}" + test._pytest_kwargs[autouse_key] = fixture + contextvars_ctx = contextvars.copy_context() contextvars_ctx.run(canary.set, "in correct context") + # Collect all fixtures to run: test dependencies + autouse fixtures + all_fixtures = set(test.register_and_collect_dependencies()) + + # Add autouse trio fixtures from the test item + if hasattr(item, "_trio_autouse_fixtures"): + for fixture in item._trio_autouse_fixtures: + all_fixtures.add(fixture) + # Also register their dependencies + all_fixtures.update(fixture.register_and_collect_dependencies()) + async with trio.open_nursery() as nursery: - for fixture in test.register_and_collect_dependencies(): + for fixture in all_fixtures: nursery.start_soon( fixture.run, test_ctx, contextvars_ctx, name=fixture.name ) @@ -463,6 +483,7 @@ def _is_trio_fixture(func, coerce_async, kwargs): def handle_fixture(fixturedef, request, force_trio_mode): + # print(f"DEBUG handle_fixture called: fixturedef.argname={fixturedef.argname}, fixturedef.func={fixturedef.func}, fixturedef.autouse={getattr(fixturedef, 'autouse', 'N/A')}") is_trio_test = request.node.get_closest_marker("trio") is not None if force_trio_mode: is_trio_mode = True @@ -470,7 +491,9 @@ def handle_fixture(fixturedef, request, force_trio_mode): is_trio_mode = request.node.config.getini("trio_mode") coerce_async = is_trio_test or is_trio_mode kwargs = {name: request.getfixturevalue(name) for name in fixturedef.argnames} + # print(f"DEBUG: coerce_async={coerce_async}, kwargs={kwargs}") if _is_trio_fixture(fixturedef.func, coerce_async, kwargs): + # print(f"DEBUG: IS TRIO FIXTURE") if request.scope != "function": raise RuntimeError("Trio fixtures must be function-scope") if not is_trio_test: @@ -481,13 +504,28 @@ def handle_fixture(fixturedef, request, force_trio_mode): kwargs, ) fixturedef.cached_result = (fixture, request.param_index, None) + + # If this is an autouse fixture, store it on the test item so it gets run + if getattr(fixturedef, "_autouse", False): + if not hasattr(request.node, "_trio_autouse_fixtures"): + request.node._trio_autouse_fixtures = [] + request.node._trio_autouse_fixtures.append(fixture) + return fixture + else: + pass + # print(f"DEBUG: NOT A TRIO FIXTURE") def pytest_fixture_setup(fixturedef, request): return handle_fixture(fixturedef, request, force_trio_mode=False) +def pytest_collection_modifyitems(config, items): + if config.getini("trio_mode"): + automark(items, run=choose_run(config=config)) + + ################################################################ # Trio mode ################################################################ From d3180395c53ff14400ff358386e7638be3d9cb86 Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Fri, 9 Oct 2026 14:01:01 +0530 Subject: [PATCH 2/8] fix: fix autouse async fixture support + ruff lint fixes --- pytest_trio/plugin.py | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/pytest_trio/plugin.py b/pytest_trio/plugin.py index 3ba526f..78e55b9 100644 --- a/pytest_trio/plugin.py +++ b/pytest_trio/plugin.py @@ -48,7 +48,7 @@ def pytest_configure(config): ################################################################ # This is more complicated than you might expect. - +# # The first complication is that all of pytest's machinery for setting up, # running a test, and then tearing it down again is synchronous. But we want # to have async setup, async tests, and async teardown. @@ -65,7 +65,7 @@ def pytest_configure(config): # - trio fixtures have to be test-scoped # - normally pytest considers a fixture crash to be an ERROR, but when a trio # fixture crashes, it gets classified as a FAIL. - +# # The other major complication is that we really want to allow trio fixtures # to yield inside a nursery. (See gh-55 for more discussion.) And then while # the fixture function is suspended, a task inside that nursery might crash. @@ -98,13 +98,13 @@ def pytest_configure(config): # Then we use trio.Event objects to implement the ordering described above. # # If a fixture crashes, whether during setup, teardown, or in a background -# task at any other point, then we mark the whole test run as "crashed". When +# task at any moment, then we mark the whole test run as "crashed". When # a run is "crashed", two things happen: (1) if any fixtures or the test # itself haven't started yet, then we don't start them, and treat them as if # they've already exited. (2) if the test is running, we cancel it. That's # all. In particular, if a fixture has a background crash, we don't propagate # that to any other fixtures, we still follow the normal teardown sequence, -# and so on – but since the test is cancelled, the teardown sequence should +# and so on -- but since the test is cancelled, the teardown sequence should # start immediately. canary = contextvars.ContextVar("pytest-trio canary") @@ -235,7 +235,7 @@ async def run(self, test_ctx, contextvars_ctx): func_value = None assert not test_ctx.crashed # Filter out autouse fixtures (keys starting with _trio_autouse_) - test_kwargs = {k: v for k, v in resolved_kwargs.items() + test_kwargs = {k: v for k, v in resolved_kwargs.items() if not k.startswith("_trio_autouse_")} await self._func(**test_kwargs) else: @@ -268,7 +268,7 @@ async def run(self, test_ctx, contextvars_ctx): # "inside" that scope even though its with block is not on the # stack. In particular this means that if they get cancelled, then # our waiting might get a Cancelled error, that we cannot really - # deal with – it should get thrown back into the fixture + # deal with -- it should get thrown back into the fixture # generator, but pytest fixture generators don't work that way: # https://github.com/python-trio/pytest-trio/issues/55 # And besides, we can't start tearing down until all our users @@ -380,7 +380,7 @@ def _trio_test_runner_factory(item, testfunc=None): return testfunc if not iscoroutinefunction(testfunc): - pytest.fail("test function `%r` is marked trio but is not async" % item) + pytest.fail(f"test function `{item!r}` is marked trio but is not async") @_trio_test(run=run) async def _bootstrap_fixtures_and_run_test(**kwargs): @@ -388,7 +388,7 @@ async def _bootstrap_fixtures_and_run_test(**kwargs): test_ctx = TrioTestContext() test = TrioFixture( - "".format(testfunc.__name__), testfunc, kwargs, is_test=True + f"", testfunc, kwargs, is_test=True ) # Add autouse trio fixtures to test's kwargs so test waits for them @@ -403,7 +403,7 @@ async def _bootstrap_fixtures_and_run_test(**kwargs): # Collect all fixtures to run: test dependencies + autouse fixtures all_fixtures = set(test.register_and_collect_dependencies()) - + # Add autouse trio fixtures from the test item if hasattr(item, "_trio_autouse_fixtures"): for fixture in item._trio_autouse_fixtures: @@ -417,15 +417,13 @@ async def _bootstrap_fixtures_and_run_test(**kwargs): fixture.run, test_ctx, contextvars_ctx, name=fixture.name ) - silent_cancellers = ( - test_ctx.fixtures_with_cancel - test_ctx.fixtures_with_errors - ) + silent_cancellers = test_ctx.fixtures_with_cancel - test_ctx.fixtures_with_errors if silent_cancellers: for fixture in silent_cancellers: test_ctx.error_list.append( RuntimeError( - "{} cancelled the test but didn't " - "raise an error".format(fixture.name) + f"{fixture.name} cancelled the test but didn't " + "raise an error" ) ) @@ -455,8 +453,8 @@ def pytest_runtest_call(item): ) elif getattr(item.obj, "is_hypothesis_test", False): # pragma: no cover pytest.fail( - "test function `%r` is using Hypothesis, but pytest-trio " - "only works with Hypothesis 3.64.0 or later." % item + f"test function `{item!r}` is using Hypothesis, but pytest-trio " + "only works with Hypothesis 3.64.0 or later." ) else: item.obj = _trio_test_runner_factory(item) @@ -499,18 +497,18 @@ def handle_fixture(fixturedef, request, force_trio_mode): if not is_trio_test: raise RuntimeError("Trio fixtures can only be used by Trio tests") fixture = TrioFixture( - "".format(fixturedef.argname), + f"", fixturedef.func, kwargs, ) fixturedef.cached_result = (fixture, request.param_index, None) - + # If this is an autouse fixture, store it on the test item so it gets run if getattr(fixturedef, "_autouse", False): if not hasattr(request.node, "_trio_autouse_fixtures"): request.node._trio_autouse_fixtures = [] request.node._trio_autouse_fixtures.append(fixture) - + return fixture else: pass @@ -562,11 +560,6 @@ def choose_run(config): return run -def pytest_collection_modifyitems(config, items): - if config.getini("trio_mode"): - automark(items, run=choose_run(config=config)) - - ################################################################ # Built-in fixtures ################################################################ @@ -588,4 +581,4 @@ def autojump_clock(): @trio_fixture def nursery(request): - return NURSERY_FIXTURE_PLACEHOLDER + return NURSERY_FIXTURE_PLACEHOLDER \ No newline at end of file From 72bc6ca3d36844fc518a098b4e28e4550bfa8936 Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Fri, 9 Oct 2026 14:10:55 +0530 Subject: [PATCH 3/8] fix: fix autouse async fixture + ruff lint fixes (BLE001, RUF015, SIM103) --- pytest_trio/plugin.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/pytest_trio/plugin.py b/pytest_trio/plugin.py index 78e55b9..8d0cb3d 100644 --- a/pytest_trio/plugin.py +++ b/pytest_trio/plugin.py @@ -1,17 +1,18 @@ """pytest-trio implementation.""" +import contextvars import sys -from functools import wraps, partial from collections.abc import Coroutine, Generator from contextlib import asynccontextmanager +from functools import partial, wraps from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction -import contextvars + import outcome import pytest import trio +from _pytest.outcomes import Skipped, XFailed from trio.abc import Clock, Instrument from trio.testing import MockClock -from _pytest.outcomes import Skipped, XFailed if sys.version_info[:2] < (3, 11): from exceptiongroup import BaseExceptionGroup @@ -182,7 +183,7 @@ async def _fixture_manager(self, test_ctx): yield nursery_fixture finally: nursery_fixture.cancel_scope.cancel() - except BaseException as exc: + except Exception as exc: # noqa: BLE001 test_ctx.crash(self, exc) finally: self.setup_done.set() @@ -285,8 +286,7 @@ async def run(self, test_ctx, contextvars_ctx): try: for event in self.user_done_events: await event.wait() - except BaseException as exc: - assert isinstance(exc, trio.Cancelled) # noqa: PT017 + except trio.Cancelled as exc: yield_outcome = outcome.Error(exc) test_ctx.crash(self, None) with trio.CancelScope(shield=True): @@ -328,7 +328,7 @@ def wrapper(**kwargs): if not clocks: clock = None elif len(clocks) == 1: - clock = list(clocks.values())[0] + clock = next(iter(clocks.values())) else: raise ValueError( f"Expected at most one Clock in kwargs, got {clocks!r}" @@ -471,13 +471,11 @@ def trio_fixture(func): def _is_trio_fixture(func, coerce_async, kwargs): - if getattr(func, "_force_trio_fixture", False): - return True - if coerce_async and (iscoroutinefunction(func) or isasyncgenfunction(func)): - return True - if any(isinstance(value, TrioFixture) for value in kwargs.values()): - return True - return False + return ( + getattr(func, "_force_trio_fixture", False) + or (coerce_async and (iscoroutinefunction(func) or isasyncgenfunction(func))) + or any(isinstance(value, TrioFixture) for value in kwargs.values()) + ) def handle_fixture(fixturedef, request, force_trio_mode): From c94be307ac5fb51a8a12855757cba3ecba12fd0f Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Fri, 9 Oct 2026 14:17:20 +0530 Subject: [PATCH 4/8] fix: lint fixes in test files and docs --- docs/source/conf.py | 5 ++--- pytest_trio/_tests/test_autouse.py | 1 - pytest_trio/_tests/test_contextvars.py | 5 +++-- pytest_trio/_tests/test_fixture_mistakes.py | 1 + pytest_trio/_tests/test_fixture_names.py | 3 ++- pytest_trio/_tests/test_hypothesis_interaction.py | 3 ++- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 5611320..3a20386 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # Documentation build configuration file, created by # sphinx-quickstart on Sat Jan 21 19:11:14 2017. @@ -101,7 +100,7 @@ def setup(app): # built documents. # # The short X.Y version. -import pytest_trio # noqa: E402 +import pytest_trio version = pytest_trio.__version__ # The full version, including alpha/beta/rc tags. @@ -143,7 +142,7 @@ def setup(app): # We have to set this ourselves, not only because it's useful for local # testing, but also because if we don't then RTD will throw away our # html_theme_options. -import sphinx_rtd_theme # noqa: E402 +import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/pytest_trio/_tests/test_autouse.py b/pytest_trio/_tests/test_autouse.py index a6bea3b..4e5dd7a 100644 --- a/pytest_trio/_tests/test_autouse.py +++ b/pytest_trio/_tests/test_autouse.py @@ -1,6 +1,5 @@ """Test to reproduce the autouse async fixture bug.""" import pytest -import trio def test_autouse_async_fixture_with_trio_mark(testdir): diff --git a/pytest_trio/_tests/test_contextvars.py b/pytest_trio/_tests/test_contextvars.py index 33bf147..7081c11 100644 --- a/pytest_trio/_tests/test_contextvars.py +++ b/pytest_trio/_tests/test_contextvars.py @@ -1,7 +1,8 @@ +import contextvars + import pytest -from pytest_trio import trio_fixture -import contextvars +from pytest_trio import trio_fixture cv = contextvars.ContextVar("cv", default=None) diff --git a/pytest_trio/_tests/test_fixture_mistakes.py b/pytest_trio/_tests/test_fixture_mistakes.py index 27646ae..a9744ea 100644 --- a/pytest_trio/_tests/test_fixture_mistakes.py +++ b/pytest_trio/_tests/test_fixture_mistakes.py @@ -1,4 +1,5 @@ import pytest # noqa: F401 + from pytest_trio import trio_fixture # noqa: F401 from .helpers import enable_trio_mode diff --git a/pytest_trio/_tests/test_fixture_names.py b/pytest_trio/_tests/test_fixture_names.py index 54c531c..9baf739 100644 --- a/pytest_trio/_tests/test_fixture_names.py +++ b/pytest_trio/_tests/test_fixture_names.py @@ -1,7 +1,8 @@ import pytest -from pytest_trio import trio_fixture import trio +from pytest_trio import trio_fixture + @trio_fixture def fixture_with_unique_name(nursery): diff --git a/pytest_trio/_tests/test_hypothesis_interaction.py b/pytest_trio/_tests/test_hypothesis_interaction.py index cb95a96..e4312cc 100644 --- a/pytest_trio/_tests/test_hypothesis_interaction.py +++ b/pytest_trio/_tests/test_hypothesis_interaction.py @@ -1,6 +1,7 @@ import pytest import trio -from hypothesis import given, settings, strategies as st +from hypothesis import given, settings +from hypothesis import strategies as st from pytest_trio.plugin import _trio_test_runner_factory From 0865256c781ad2750b8d44d485975c18e98893cf Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Fri, 9 Oct 2026 14:24:13 +0530 Subject: [PATCH 5/8] fix: remove shebang from docs/source/conf.py (EXE001) --- docs/source/conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3a20386..113a746 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # Documentation build configuration file, created by # sphinx-quickstart on Sat Jan 21 19:11:14 2017. # From a44b34cacd89a00483cd174ae58cd6cf6cb883ef Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Fri, 9 Oct 2026 14:38:40 +0530 Subject: [PATCH 6/8] fix: check trio marker on node and ancestors for autouse fixtures (fixes #123 on older pytest) --- pytest_trio/plugin.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pytest_trio/plugin.py b/pytest_trio/plugin.py index 8d0cb3d..2f9bf31 100644 --- a/pytest_trio/plugin.py +++ b/pytest_trio/plugin.py @@ -470,6 +470,15 @@ def trio_fixture(func): return pytest.fixture(func) +def _has_trio_marker(node): + """Check if node or any ancestor has the trio marker.""" + while node is not None: + if node.get_closest_marker("trio") is not None: + return True + node = getattr(node, "parent", None) + return False + + def _is_trio_fixture(func, coerce_async, kwargs): return ( getattr(func, "_force_trio_fixture", False) @@ -480,7 +489,7 @@ def _is_trio_fixture(func, coerce_async, kwargs): def handle_fixture(fixturedef, request, force_trio_mode): # print(f"DEBUG handle_fixture called: fixturedef.argname={fixturedef.argname}, fixturedef.func={fixturedef.func}, fixturedef.autouse={getattr(fixturedef, 'autouse', 'N/A')}") - is_trio_test = request.node.get_closest_marker("trio") is not None + is_trio_test = _has_trio_marker(request.node) if force_trio_mode: is_trio_mode = True else: From 00dfc0741aa3c06c4af7c4df7504771594cdde5d Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Sat, 10 Oct 2026 09:43:02 +0530 Subject: [PATCH 7/8] fix: check module for trio tests when processing autouse fixtures (fixes #123 on older pytest) --- pytest_trio/plugin.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pytest_trio/plugin.py b/pytest_trio/plugin.py index 2f9bf31..742adf8 100644 --- a/pytest_trio/plugin.py +++ b/pytest_trio/plugin.py @@ -479,6 +479,20 @@ def _has_trio_marker(node): return False +def _module_has_trio_tests(module_node): + """Check if module has any tests with the trio marker.""" + if module_node is None: + return False + # Check if module itself has the marker (unlikely but possible) + if _has_trio_marker(module_node): + return True + # Check all items in the module + for item in getattr(module_node, "items", []): + if _has_trio_marker(item): + return True + return False + + def _is_trio_fixture(func, coerce_async, kwargs): return ( getattr(func, "_force_trio_fixture", False) @@ -495,6 +509,16 @@ def handle_fixture(fixturedef, request, force_trio_mode): else: is_trio_mode = request.node.config.getini("trio_mode") coerce_async = is_trio_test or is_trio_mode + + # If not a trio test but fixture is autouse and async, check if module has trio tests + if not coerce_async and getattr(fixturedef, "_autouse", False): + # Find the module node (root of the tree) + module_node = request.node + while module_node is not None and getattr(module_node, "parent", None) is not None: + module_node = module_node.parent + if _module_has_trio_tests(module_node): + coerce_async = True + kwargs = {name: request.getfixturevalue(name) for name in fixturedef.argnames} # print(f"DEBUG: coerce_async={coerce_async}, kwargs={kwargs}") if _is_trio_fixture(fixturedef.func, coerce_async, kwargs): From 1d9c4877895b721671dd77a7adc1c9b04ce3db03 Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Sat, 10 Oct 2026 09:50:06 +0530 Subject: [PATCH 8/8] fix: check module for trio tests for ALL async fixtures (fixes autouse fixture dependencies on older pytest) --- pytest_trio/plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pytest_trio/plugin.py b/pytest_trio/plugin.py index 742adf8..76ed776 100644 --- a/pytest_trio/plugin.py +++ b/pytest_trio/plugin.py @@ -510,8 +510,9 @@ def handle_fixture(fixturedef, request, force_trio_mode): is_trio_mode = request.node.config.getini("trio_mode") coerce_async = is_trio_test or is_trio_mode - # If not a trio test but fixture is autouse and async, check if module has trio tests - if not coerce_async and getattr(fixturedef, "_autouse", False): + # If not a trio test but fixture is async, check if module has trio tests + # This handles cases where fixtures are processed at module scope (older pytest) + if not coerce_async and (iscoroutinefunction(fixturedef.func) or isasyncgenfunction(fixturedef.func)): # Find the module node (root of the tree) module_node = request.node while module_node is not None and getattr(module_node, "parent", None) is not None: