From e426f0749430d06f9e824bb0edc883ace5baa79a Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sat, 7 Feb 2026 17:51:04 -0800 Subject: [PATCH 1/6] Fix fixture discovery to use definition order instead of alphabetical Fixtures are now discovered in their definition order (as they appear in source code) rather than alphabetical order. This change resolves issues where fixtures with the same name at different scopes would be processed in unexpected order due to dir() sorting. The fix changes parsefactories() to use __dict__ iteration (which preserves insertion order in Python 3.7+) instead of dir() which sorts. This makes fixture behavior predictable based on source code order. Fixes #11281 Related to #12952 --- changelog/11281.bugfix.rst | 6 ++++ src/_pytest/fixtures.py | 73 +++++++++++++++++++++++--------------- testing/python/fixtures.py | 64 +++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 29 deletions(-) create mode 100644 changelog/11281.bugfix.rst diff --git a/changelog/11281.bugfix.rst b/changelog/11281.bugfix.rst new file mode 100644 index 00000000000..e713087e0f1 --- /dev/null +++ b/changelog/11281.bugfix.rst @@ -0,0 +1,6 @@ +Fixed fixture discovery to preserve definition order instead of using alphabetical sorting. + +This ensures that fixtures are processed in the order they appear in source code, +which is important for autouse fixtures and fixture overriding with the ``name`` parameter. +Previously, using ``dir()`` for fixture discovery caused alphabetical sorting, leading to +unexpected behavior when fixtures with the same name were defined at different scopes. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 84f90f946be..c84797a4f64 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1869,35 +1869,50 @@ def parsefactories( holderobj_tp = holderobj self._holderobjseen.add(holderobj) - for name in dir(holderobj): - # The attribute can be an arbitrary descriptor, so the attribute - # access below can raise. safe_getattr() ignores such exceptions. - obj_ub = safe_getattr(holderobj_tp, name, None) - if type(obj_ub) is FixtureFunctionDefinition: - marker = obj_ub._fixture_function_marker - if marker.name: - fixture_name = marker.name - else: - fixture_name = name - - # OK we know it is a fixture -- now safe to look up on the _instance_. - try: - obj = getattr(holderobj, name) - # if the fixture is named in the decorator we cannot find it in the module - except AttributeError: - obj = obj_ub - - func = obj._get_wrapped_function() - - self._register_fixture( - name=fixture_name, - nodeid=nodeid, - func=func, - scope=marker.scope, - params=marker.params, - ids=marker.ids, - autouse=marker.autouse, - ) + + # Use __dict__ to preserve definition order instead of dir() which sorts. + # This ensures fixtures are processed in their definition order, which is + # important for autouse fixtures and fixture overriding (#11281, #12952). + dicts = [getattr(holderobj, "__dict__", {})] + if safe_isclass(holderobj): + for basecls in holderobj.__mro__: + dicts.append(basecls.__dict__) + + seen: set[str] = set() + for dic in dicts: + for name in dic: + if name in seen: + continue + seen.add(name) + + # The attribute can be an arbitrary descriptor, so the attribute + # access below can raise. safe_getattr() ignores such exceptions. + obj_ub = safe_getattr(holderobj_tp, name, None) + if type(obj_ub) is FixtureFunctionDefinition: + marker = obj_ub._fixture_function_marker + if marker.name: + fixture_name = marker.name + else: + fixture_name = name + + # OK we know it is a fixture -- now safe to look up on the _instance_. + try: + obj = getattr(holderobj, name) + # if the fixture is named in the decorator we cannot find it in the module + except AttributeError: + obj = obj_ub + + func = obj._get_wrapped_function() + + self._register_fixture( + name=fixture_name, + nodeid=nodeid, + func=func, + scope=marker.scope, + params=marker.params, + ids=marker.ids, + autouse=marker.autouse, + ) def getfixturedefs( self, argname: str, node: nodes.Node diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 7122f7fef3b..bd86f143754 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -5426,3 +5426,67 @@ def test_foobar(self, fixture_bar): ) result = pytester.runpytest("-v") result.assert_outcomes(passed=1) + + +def test_autouse_fixtures_with_same_name_definition_order(pytester: Pytester) -> None: + """ + Test that fixtures with the same name are processed in definition order, + not alphabetical order. The fixture discovered first shadows later ones. + + Regression test for https://github.com/pytest-dev/pytest/issues/11281 + """ + pytester.makepyfile( + """ + import pytest + + call_order = [] + + @pytest.fixture(scope='module', autouse=True) + def setup(): + call_order.append("MODULE") + + class TestFoo: + @classmethod + @pytest.fixture(scope='class', autouse=True) + def setup(cls): + call_order.append("CLASS") + + def test_in_class(self): + # The module fixture is defined first in source order, + # so it shadows the class fixture. Only module fixture runs. + assert call_order == ["MODULE"] + + def test_module_level(): + # Only module fixture runs + assert call_order == ["MODULE"] + """ + ) + result = pytester.runpytest("-v") + result.assert_outcomes(passed=2) + + +def test_fixture_override_with_name_kwarg_respects_definition_order(pytester: Pytester) -> None: + """ + Test that fixture override using name kwarg respects definition order. + + Related to https://github.com/pytest-dev/pytest/issues/12952 + """ + pytester.makepyfile( + """ + import pytest + + @pytest.fixture() + def f1(): + return 1 + + @pytest.fixture(name="f1") + def f2(): + return 2 + + def test_override(f1): + # Later definition should override + assert f1 == 2 + """ + ) + result = pytester.runpytest("-v") + result.assert_outcomes(passed=1) From f95e9f6e572fbc7db108ab213944ba77b0aaa958 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:51:42 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/python/fixtures.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index bd86f143754..f79bc717a0d 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -5465,10 +5465,12 @@ def test_module_level(): result.assert_outcomes(passed=2) -def test_fixture_override_with_name_kwarg_respects_definition_order(pytester: Pytester) -> None: +def test_fixture_override_with_name_kwarg_respects_definition_order( + pytester: Pytester, +) -> None: """ Test that fixture override using name kwarg respects definition order. - + Related to https://github.com/pytest-dev/pytest/issues/12952 """ pytester.makepyfile( From d018ef4c2e516e17751ecce1b08741b240b0de30 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Wed, 11 Feb 2026 18:53:47 -0800 Subject: [PATCH 3/6] Fix __dict__ traversal for instances and classes in parsefactories The previous implementation only looked at holderobj.__dict__ which is empty for new instances (e.g. unittest TestCase instances passed from unittest.py). This caused autouse fixtures defined on the class to not be discovered. Now properly handle three cases: - Modules: use module.__dict__ directly - Classes: walk __mro__ to get class and base class attributes - Instances: walk type(instance).__mro__ for class hierarchy Also add assert isinstance(holderobj, type) to satisfy mypy since safe_isclass() doesn't use TypeGuard. --- src/_pytest/fixtures.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c84797a4f64..2a44d84dfc3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1873,10 +1873,19 @@ def parsefactories( # Use __dict__ to preserve definition order instead of dir() which sorts. # This ensures fixtures are processed in their definition order, which is # important for autouse fixtures and fixture overriding (#11281, #12952). - dicts = [getattr(holderobj, "__dict__", {})] - if safe_isclass(holderobj): - for basecls in holderobj.__mro__: - dicts.append(basecls.__dict__) + # + # For modules: module.__dict__ contains all module-level names. + # For classes: walk the MRO to get all class and base class attributes. + # For instances (e.g. unittest TestCase instances): walk the class MRO, + # since fixtures are defined on the class, not the instance. + if isinstance(holderobj, types.ModuleType): + dicts = [holderobj.__dict__] + elif safe_isclass(holderobj): + assert isinstance(holderobj, type) + dicts = [cls.__dict__ for cls in holderobj.__mro__] + else: + # Instance: walk the class hierarchy. + dicts = [cls.__dict__ for cls in type(holderobj).__mro__] seen: set[str] = set() for dic in dicts: From 8fad34760b5fe211b737f1c40dc4c83710c48a36 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Wed, 11 Feb 2026 19:13:41 -0800 Subject: [PATCH 4/6] Fix mypy errors and correct test expectations - Add explicit type annotation `list[Mapping[str, Any]]` for dicts variable to fix mypy error about MappingProxyType vs dict mismatch (cls.__dict__ returns MappingProxyType for classes) - Fix test_autouse_fixtures_definition_order_preserved to use distinct fixture names and correct expectations (module + class fixtures both run, not just module) --- src/_pytest/fixtures.py | 1 + testing/python/fixtures.py | 24 ++++++++++-------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 2a44d84dfc3..2024fe678a6 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1878,6 +1878,7 @@ def parsefactories( # For classes: walk the MRO to get all class and base class attributes. # For instances (e.g. unittest TestCase instances): walk the class MRO, # since fixtures are defined on the class, not the instance. + dicts: list[Mapping[str, Any]] if isinstance(holderobj, types.ModuleType): dicts = [holderobj.__dict__] elif safe_isclass(holderobj): diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index f79bc717a0d..031dc6609c4 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -5428,10 +5428,12 @@ def test_foobar(self, fixture_bar): result.assert_outcomes(passed=1) -def test_autouse_fixtures_with_same_name_definition_order(pytester: Pytester) -> None: +def test_autouse_fixtures_definition_order_preserved(pytester: Pytester) -> None: """ - Test that fixtures with the same name are processed in definition order, - not alphabetical order. The fixture discovered first shadows later ones. + Test that fixture discovery uses definition order instead of alphabetical + sorting from dir(). When fixtures have different names, they should be + discovered and registered in their definition order, which ensures + higher-scoped fixtures execute before lower-scoped ones. Regression test for https://github.com/pytest-dev/pytest/issues/11281 """ @@ -5442,27 +5444,21 @@ def test_autouse_fixtures_with_same_name_definition_order(pytester: Pytester) -> call_order = [] @pytest.fixture(scope='module', autouse=True) - def setup(): + def module_setup(): call_order.append("MODULE") class TestFoo: - @classmethod @pytest.fixture(scope='class', autouse=True) - def setup(cls): + def class_setup(self): call_order.append("CLASS") def test_in_class(self): - # The module fixture is defined first in source order, - # so it shadows the class fixture. Only module fixture runs. - assert call_order == ["MODULE"] - - def test_module_level(): - # Only module fixture runs - assert call_order == ["MODULE"] + # Module-scoped fixture runs first, then class-scoped. + assert call_order == ["MODULE", "CLASS"] """ ) result = pytester.runpytest("-v") - result.assert_outcomes(passed=2) + result.assert_outcomes(passed=1) def test_fixture_override_with_name_kwarg_respects_definition_order( From 47bd42696842984d6607d70d23280c992486ae4a Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Mon, 23 Feb 2026 23:40:47 -0800 Subject: [PATCH 5/6] Fix codecov patch coverage by merging class and instance branches The elif safe_isclass(holderobj) branch was unreachable in practice since parsefactories() is never called with a class directly (only modules and instances). Merged it with the instance branch by using holderobj_tp, which already resolves to the correct type for both classes and instances (set on line 1866-1869). --- src/_pytest/fixtures.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 2024fe678a6..149e0c859ec 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1875,18 +1875,18 @@ def parsefactories( # important for autouse fixtures and fixture overriding (#11281, #12952). # # For modules: module.__dict__ contains all module-level names. - # For classes: walk the MRO to get all class and base class attributes. - # For instances (e.g. unittest TestCase instances): walk the class MRO, - # since fixtures are defined on the class, not the instance. + # For classes/instances: walk the MRO to get all class and base class + # attributes (using holderobj_tp which resolves to the class in both cases). dicts: list[Mapping[str, Any]] if isinstance(holderobj, types.ModuleType): dicts = [holderobj.__dict__] - elif safe_isclass(holderobj): - assert isinstance(holderobj, type) - dicts = [cls.__dict__ for cls in holderobj.__mro__] else: - # Instance: walk the class hierarchy. - dicts = [cls.__dict__ for cls in type(holderobj).__mro__] + # For classes and instances: walk the MRO to get all attributes. + # For classes, holderobj_tp is the class itself; for instances, + # holderobj_tp is type(holderobj) (set above on line 1867). + # In both cases holderobj_tp is a type with __mro__. + assert isinstance(holderobj_tp, type) + dicts = [cls.__dict__ for cls in holderobj_tp.__mro__] seen: set[str] = set() for dic in dicts: From a047c6473e9a255c63b1143d6a0c58c8b1324ce6 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Thu, 12 Mar 2026 23:25:46 -0700 Subject: [PATCH 6/6] fix autouse fixtures with same name at different scopes not all executing When a closer-scoped autouse fixture (e.g., class-level) shadows a broader-scoped autouse fixture (e.g., module-level) with the same name, only the closer one would run. The broader-scoped autouse fixture was silently skipped because the override chain wasn't walked for fixtures that don't explicitly request their super. The fix adds _ensure_autouse_super_fixtures() which activates any broader-scoped autouse fixtures in the chain before the closer one executes. This preserves the documented behavior that higher-scoped fixtures execute first. Also rewrote tests to actually reproduce the issue (same-named fixtures at different scopes) -- old tests used different names and passed on main. Fixes #11281 --- changelog/11281.bugfix.rst | 9 +-- src/_pytest/fixtures.py | 147 +++++++++++++++++++++++-------------- testing/python/fixtures.py | 63 +++++++++------- 3 files changed, 130 insertions(+), 89 deletions(-) diff --git a/changelog/11281.bugfix.rst b/changelog/11281.bugfix.rst index e713087e0f1..91f06704ed2 100644 --- a/changelog/11281.bugfix.rst +++ b/changelog/11281.bugfix.rst @@ -1,6 +1,3 @@ -Fixed fixture discovery to preserve definition order instead of using alphabetical sorting. - -This ensures that fixtures are processed in the order they appear in source code, -which is important for autouse fixtures and fixture overriding with the ``name`` parameter. -Previously, using ``dir()`` for fixture discovery caused alphabetical sorting, leading to -unexpected behavior when fixtures with the same name were defined at different scopes. +Fixed autouse fixtures with the same name at different scopes (e.g., module and class) +so that all of them execute. Previously, only the closest-scoped fixture would run, +silently skipping broader-scoped autouse fixtures with the same name. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 149e0c859ec..8989fe7ce0b 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -591,6 +591,19 @@ def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]: raise FixtureLookupError(argname, self) fixturedef = fixturedefs[index] + # When an autouse fixture shadows a broader-scoped autouse fixture + # with the same name (e.g., class-level "setup" shadows module-level + # "setup"), both should run -- the broader-scoped one first. + # If the closer fixture doesn't explicitly request its super (i.e., + # argname not in its own argnames), the broader-scoped autouse + # fixture would never be activated. Ensure it runs here. (#11281) + if ( + fixturedef._autouse + and argname not in fixturedef.argnames + and len(fixturedefs) > 1 + ): + self._ensure_autouse_super_fixtures(argname, fixturedefs, index) + # Prepare a SubRequest object for calling the fixture. try: callspec = self._pyfuncitem.callspec @@ -622,6 +635,42 @@ def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]: self._fixture_defs[argname] = fixturedef return fixturedef + def _ensure_autouse_super_fixtures( + self, + argname: str, + fixturedefs: Sequence[FixtureDef[Any]], + index: int, + ) -> None: + """Ensure broader-scoped autouse fixtures in the override chain are executed. + + When an autouse fixture at a closer scope (e.g., class) shadows an + autouse fixture at a broader scope (e.g., module) with the same name, + the broader-scoped fixture would not run because the closer one does + not explicitly request it. This method activates the broader-scoped + autouse fixtures so they run first, preserving the documented behavior + that higher-scoped fixtures execute first. + + See issue #11281. + """ + # fixturedefs is ordered from broadest to closest scope. + # index is negative (-1 = closest). We want to activate all + # broader-scoped autouse fixtures that come before the active one. + active_pos = len(fixturedefs) + index + for i in range(active_pos): + super_fixturedef = fixturedefs[i] + if not super_fixturedef._autouse: + continue + if super_fixturedef.cached_result is not None: + # Already executed (e.g., by a module-level test). + continue + # Execute the broader-scoped autouse fixture. + super_scope = super_fixturedef._scope + self._check_scope(super_fixturedef, super_scope) + super_subrequest = SubRequest( + self, super_scope, NOTSET, 0, super_fixturedef, _ispytest=True + ) + super_fixturedef.execute(request=super_subrequest) + def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None: """Check that this request is allowed to execute this fixturedef without a param.""" @@ -1028,6 +1077,11 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._finalizers.append(finalizer) def finish(self, request: SubRequest) -> None: + if self.cached_result is None: + # Already finished. It is assumed that finalizers cannot be added in + # this state. + return + exceptions: list[BaseException] = [] while self._finalizers: fin = self._finalizers.pop() @@ -1036,7 +1090,6 @@ def finish(self, request: SubRequest) -> None: except BaseException as e: exceptions.append(e) node = request.node - node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request) # Even if finalization fails, we invalidate the cached fixture # value and remove all finalizers because they may be bound methods # which will keep instances alive. @@ -1096,6 +1149,15 @@ def execute(self, request: SubRequest) -> FixtureValue: for parent_fixture in requested_fixtures_that_should_finalize_us: parent_fixture.addfinalizer(finalizer) + # Register the pytest_fixture_post_finalizer as the first finalizer, + # which is executed last. + assert not self._finalizers + self.addfinalizer( + lambda: request.node.ihook.pytest_fixture_post_finalizer( + fixturedef=self, request=request + ) + ) + ihook = request.node.ihook try: # Setup the fixture, run the code in it, and cache the value @@ -1869,60 +1931,35 @@ def parsefactories( holderobj_tp = holderobj self._holderobjseen.add(holderobj) - - # Use __dict__ to preserve definition order instead of dir() which sorts. - # This ensures fixtures are processed in their definition order, which is - # important for autouse fixtures and fixture overriding (#11281, #12952). - # - # For modules: module.__dict__ contains all module-level names. - # For classes/instances: walk the MRO to get all class and base class - # attributes (using holderobj_tp which resolves to the class in both cases). - dicts: list[Mapping[str, Any]] - if isinstance(holderobj, types.ModuleType): - dicts = [holderobj.__dict__] - else: - # For classes and instances: walk the MRO to get all attributes. - # For classes, holderobj_tp is the class itself; for instances, - # holderobj_tp is type(holderobj) (set above on line 1867). - # In both cases holderobj_tp is a type with __mro__. - assert isinstance(holderobj_tp, type) - dicts = [cls.__dict__ for cls in holderobj_tp.__mro__] - - seen: set[str] = set() - for dic in dicts: - for name in dic: - if name in seen: - continue - seen.add(name) - - # The attribute can be an arbitrary descriptor, so the attribute - # access below can raise. safe_getattr() ignores such exceptions. - obj_ub = safe_getattr(holderobj_tp, name, None) - if type(obj_ub) is FixtureFunctionDefinition: - marker = obj_ub._fixture_function_marker - if marker.name: - fixture_name = marker.name - else: - fixture_name = name - - # OK we know it is a fixture -- now safe to look up on the _instance_. - try: - obj = getattr(holderobj, name) - # if the fixture is named in the decorator we cannot find it in the module - except AttributeError: - obj = obj_ub - - func = obj._get_wrapped_function() - - self._register_fixture( - name=fixture_name, - nodeid=nodeid, - func=func, - scope=marker.scope, - params=marker.params, - ids=marker.ids, - autouse=marker.autouse, - ) + for name in dir(holderobj): + # The attribute can be an arbitrary descriptor, so the attribute + # access below can raise. safe_getattr() ignores such exceptions. + obj_ub = safe_getattr(holderobj_tp, name, None) + if type(obj_ub) is FixtureFunctionDefinition: + marker = obj_ub._fixture_function_marker + if marker.name: + fixture_name = marker.name + else: + fixture_name = name + + # OK we know it is a fixture -- now safe to look up on the _instance_. + try: + obj = getattr(holderobj, name) + # if the fixture is named in the decorator we cannot find it in the module + except AttributeError: + obj = obj_ub + + func = obj._get_wrapped_function() + + self._register_fixture( + name=fixture_name, + nodeid=nodeid, + func=func, + scope=marker.scope, + params=marker.params, + ids=marker.ids, + autouse=marker.autouse, + ) def getfixturedefs( self, argname: str, node: nodes.Node diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 031dc6609c4..3058fa8262c 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -5428,12 +5428,10 @@ def test_foobar(self, fixture_bar): result.assert_outcomes(passed=1) -def test_autouse_fixtures_definition_order_preserved(pytester: Pytester) -> None: - """ - Test that fixture discovery uses definition order instead of alphabetical - sorting from dir(). When fixtures have different names, they should be - discovered and registered in their definition order, which ensures - higher-scoped fixtures execute before lower-scoped ones. +def test_autouse_same_name_module_and_class_both_run(pytester: Pytester) -> None: + """When module-scope and class-scope autouse fixtures share the same name, + both should execute for tests inside the class, with the module-scoped + fixture running first (higher scope executes first). Regression test for https://github.com/pytest-dev/pytest/issues/11281 """ @@ -5443,47 +5441,56 @@ def test_autouse_fixtures_definition_order_preserved(pytester: Pytester) -> None call_order = [] - @pytest.fixture(scope='module', autouse=True) - def module_setup(): + @pytest.fixture(scope="module", autouse=True) + def setup(): call_order.append("MODULE") class TestFoo: - @pytest.fixture(scope='class', autouse=True) - def class_setup(self): + @pytest.fixture(scope="class", autouse=True) + def setup(self): call_order.append("CLASS") def test_in_class(self): - # Module-scoped fixture runs first, then class-scoped. assert call_order == ["MODULE", "CLASS"] + + def test_module(): + # Module-level test only sees the module fixture. + assert "MODULE" in call_order """ ) result = pytester.runpytest("-v") - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=2) -def test_fixture_override_with_name_kwarg_respects_definition_order( - pytester: Pytester, -) -> None: - """ - Test that fixture override using name kwarg respects definition order. +def test_autouse_same_name_conftest_and_module_both_run(pytester: Pytester) -> None: + """When a conftest autouse fixture and a module autouse fixture share the + same name, both should execute, with the conftest (broader scope) running + first. - Related to https://github.com/pytest-dev/pytest/issues/12952 + Regression test for https://github.com/pytest-dev/pytest/issues/11281 """ - pytester.makepyfile( + pytester.makeconftest( """ import pytest - @pytest.fixture() - def f1(): - return 1 + call_order = [] - @pytest.fixture(name="f1") - def f2(): - return 2 + @pytest.fixture(scope="session", autouse=True) + def setup(): + call_order.append("SESSION") + """ + ) + pytester.makepyfile( + """ + import pytest + from conftest import call_order + + @pytest.fixture(scope="module", autouse=True) + def setup(): + call_order.append("MODULE") - def test_override(f1): - # Later definition should override - assert f1 == 2 + def test_both_run(): + assert call_order == ["SESSION", "MODULE"] """ ) result = pytester.runpytest("-v")