Environment
pytest 9.1.1, Python 3.12.9, Linux (WSL2). Not reproducible on pytest 9.0.2. Reproduces with --import-mode=prepend (default) and
a package layout (tests/__init__.py); the conftest lives in the package.
Minimal reproduction
repo/
test_x.py # any test
tests/__init__.py
tests/conftest.py # @pytest.fixture(autouse=True) def guard(): raise RuntimeError("guard ran")
tests/a.py # def test_a(): pass
tests/b.py # def test_b(): pass
pytest tests/a.py test_x.py tests/b.py
Expected: test_a and test_b both error with "guard ran" (the autouse fixture applies to
everything under tests/). Observed on 9.1.1: test_a errors, test_b PASSES — the conftest's
fixtures are not in test_b's fixture closure at all. pytest tests/a.py tests/b.py test_x.py
and pytest test_x.py tests/a.py tests/b.py behave as expected. pytest 9.0.x behaves as expected
in every order.
Cause (read from 9.1.1 source)
Since #14004 conftest fixtures are parsed against the Directory NODE that collects the
conftest's directory: FixtureManager.pytest_plugin_registered stores the conftest in
_pending_conftests[dir], and pytest_make_collect_report pops it on that Directory node's
first collect report, registering fixtures into _node_autousenames[node] (keyed by node
object). _getautousenames walks node.listchain() and looks up each parent node object.
Session.collect() re-collects an ancestor directory WITHOUT the collection cache when an
argument is a file directly in it (handle_dupes = not (len(matchparts) == 1 and matchparts[0].is_file()) — "files given directly multiple times on the command line should
not be deduplicated"). That fresh collection creates NEW child Package/Dir node objects
and overwrites _collection_cache[ancestor]. A later argument inside the package is then
collected under the new Package object; its first collect report finds _pending_conftests
already popped, so the new node never receives the conftest's fixtures, and nothing warns.
The legacy _nodeid_autousenames fallback would have caught this (both nodes share the nodeid
tests), but it is only populated by the deprecated parsefactories(obj, nodeid) path.
Impact
Any autouse guard in a package conftest (isolation, monkeypatching of live resources) is
silently absent for the affected files. We found it because such a guard protects a real
mailbox and the fence test for the guard started draining it.
Suggested fix
Either key conftest registration by directory path (re-register on every Directory node whose
path matches a registered conftest, not only the first), or make _getautousenames /
_matchfactories also consult the nodeid-keyed table for Directory nodes, or reuse the cached
child collectors when re-collecting for the non-deduplicated file case.
Workaround we use (for reference)
Root conftest regroups positional arguments deepest-directory-first at pytest_sessionstart
(the trigger cannot occur) and refuses the run in pytest_collection_modifyitems when any
item's closure lacks an ancestor conftest's autouse fixtures.
Environment
pytest 9.1.1, Python 3.12.9, Linux (WSL2). Not reproducible on pytest 9.0.2. Reproduces with
--import-mode=prepend(default) anda package layout (
tests/__init__.py); the conftest lives in the package.Minimal reproduction
Expected:
test_aandtest_bboth error with "guard ran" (the autouse fixture applies toeverything under
tests/). Observed on 9.1.1:test_aerrors,test_bPASSES — the conftest'sfixtures are not in
test_b's fixture closure at all.pytest tests/a.py tests/b.py test_x.pyand
pytest test_x.py tests/a.py tests/b.pybehave as expected. pytest 9.0.x behaves as expectedin every order.
Cause (read from 9.1.1 source)
Since #14004 conftest fixtures are parsed against the Directory NODE that collects the
conftest's directory:
FixtureManager.pytest_plugin_registeredstores the conftest in_pending_conftests[dir], andpytest_make_collect_reportpops it on that Directory node'sfirst collect report, registering fixtures into
_node_autousenames[node](keyed by nodeobject).
_getautousenameswalksnode.listchain()and looks up each parent node object.Session.collect()re-collects an ancestor directory WITHOUT the collection cache when anargument is a file directly in it (
handle_dupes = not (len(matchparts) == 1 and matchparts[0].is_file())— "files given directly multiple times on the command line shouldnot be deduplicated"). That fresh collection creates NEW child
Package/Dirnode objectsand overwrites
_collection_cache[ancestor]. A later argument inside the package is thencollected under the new
Packageobject; its first collect report finds_pending_conftestsalready popped, so the new node never receives the conftest's fixtures, and nothing warns.
The legacy
_nodeid_autousenamesfallback would have caught this (both nodes share the nodeidtests), but it is only populated by the deprecatedparsefactories(obj, nodeid)path.Impact
Any autouse guard in a package conftest (isolation, monkeypatching of live resources) is
silently absent for the affected files. We found it because such a guard protects a real
mailbox and the fence test for the guard started draining it.
Suggested fix
Either key conftest registration by directory path (re-register on every Directory node whose
pathmatches a registered conftest, not only the first), or make_getautousenames/_matchfactoriesalso consult the nodeid-keyed table for Directory nodes, or reuse the cachedchild collectors when re-collecting for the non-deduplicated file case.
Workaround we use (for reference)
Root conftest regroups positional arguments deepest-directory-first at
pytest_sessionstart(the trigger cannot occur) and refuses the run in
pytest_collection_modifyitemswhen anyitem's closure lacks an ancestor conftest's autouse fixtures.