Skip to content

conftest.py fixtures silently missing for files collected after a file argument from an ancestor directory (pytest 9.1.0+, regression from #14004) #14964

Description

@ChrisKotsis

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions