@@ -911,6 +911,115 @@ def test_uses_ancestor(ancestor_fixture):
911911 result .stdout .fnmatch_lines (["*test_uses_ancestor*PASSED*" , "*1 passed*" ])
912912
913913
914+ def test_fixture_closure_order_independence_with_parametrize (
915+ pytester : Pytester ,
916+ ) -> None :
917+ """Regression test for #14635.
918+
919+ A test's fixture closure (and thus parametrize validation) should be
920+ independent of which unrelated paths were collected earlier in the session.
921+
922+ The scenario: a test uses @pytest.mark.parametrize("fixture_param", [...])
923+ where fixture_param is NOT a direct arg of the test but IS an argname of a
924+ fixture the test depends on transitively. Collecting unrelated directories
925+ before the test's directory should not cause the fixture_param to drop out
926+ of the closure.
927+ """
928+ root = pytester .path
929+ tests = root / "tests"
930+ tests .mkdir ()
931+ tests .joinpath ("__init__.py" ).write_text ("" , encoding = "utf-8" )
932+
933+ # tests/conftest.py - empty (or with some unrelated fixture)
934+ tests .joinpath ("conftest.py" ).write_text (
935+ textwrap .dedent ("""\
936+ import pytest
937+ """ ),
938+ encoding = "utf-8" ,
939+ )
940+
941+ # tests/components/ with its conftest
942+ components = tests / "components"
943+ components .mkdir ()
944+ components .joinpath ("__init__.py" ).write_text ("" , encoding = "utf-8" )
945+ components .joinpath ("conftest.py" ).write_text (
946+ textwrap .dedent ("""\
947+ import pytest
948+
949+ @pytest.fixture
950+ def cache_dir_side_effect():
951+ return None
952+
953+ @pytest.fixture
954+ def mock_init_cache_dir(cache_dir_side_effect):
955+ return cache_dir_side_effect
956+
957+ @pytest.fixture
958+ def mock_cache_dir(mock_init_cache_dir):
959+ return mock_init_cache_dir
960+ """ ),
961+ encoding = "utf-8" ,
962+ )
963+
964+ # tests/components/water_heater/ - unrelated test directory
965+ water_heater = components / "water_heater"
966+ water_heater .mkdir ()
967+ water_heater .joinpath ("__init__.py" ).write_text ("" , encoding = "utf-8" )
968+ water_heater .joinpath ("test_water_heater.py" ).write_text (
969+ textwrap .dedent ("""\
970+ def test_water_heater():
971+ pass
972+ """ ),
973+ encoding = "utf-8" ,
974+ )
975+
976+ # tests/components/tts/ - the problematic test directory
977+ tts = components / "tts"
978+ tts .mkdir ()
979+ tts .joinpath ("__init__.py" ).write_text ("" , encoding = "utf-8" )
980+ tts .joinpath ("conftest.py" ).write_text (
981+ textwrap .dedent ("""\
982+ import pytest
983+
984+ @pytest.fixture(autouse=True)
985+ def mock_cache_dir(mock_cache_dir):
986+ # Autouse override that requests the parent fixture of same name
987+ return mock_cache_dir
988+ """ ),
989+ encoding = "utf-8" ,
990+ )
991+ tts .joinpath ("test_init.py" ).write_text (
992+ textwrap .dedent ("""\
993+ import pytest
994+
995+ @pytest.mark.parametrize("cache_dir_side_effect", ["error_value"])
996+ async def test_setup_no_access(mock_init_cache_dir):
997+ assert mock_init_cache_dir == "error_value"
998+ """ ),
999+ encoding = "utf-8" ,
1000+ )
1001+
1002+ # tests/test_config_entries.py - another unrelated test
1003+ tests .joinpath ("test_config_entries.py" ).write_text (
1004+ textwrap .dedent ("""\
1005+ def test_config():
1006+ pass
1007+ """ ),
1008+ encoding = "utf-8" ,
1009+ )
1010+
1011+ # This order triggers the bug: collecting water_heater and config_entries
1012+ # BEFORE tts causes the fixture closure to be wrong.
1013+ result = pytester .runpytest (
1014+ "--collect-only" ,
1015+ str (water_heater ),
1016+ str (tests / "test_config_entries.py" ),
1017+ str (tts / "test_init.py" ),
1018+ )
1019+ result .stdout .fnmatch_lines (["*test_setup_no_access*" ])
1020+ assert result .ret == ExitCode .OK
1021+
1022+
9141023def test_required_option_help (pytester : Pytester ) -> None :
9151024 pytester .makeconftest ("assert 0" )
9161025 x = pytester .mkdir ("x" )
0 commit comments