Skip to content

9.1.1: nested conftest fixtures are not found when file arguments interleave a nested directory with its parent #14971

Closed

Description

@rennf93

Passing test files on the command line in the order nested/a.py parent/top.py nested/b.py makes fixtures defined in nested/conftest.py invisible to the last file on pytest 9.1.1. The same command passes on pytest 8.4.2, and passes on 9.1.1 when the two nested files are adjacent or when the directory is given instead of the files.

Minimal reproduction (Python 3.10.19, macOS, no plugins beyond the defaults):

tests/conftest.py
    import pytest
    @pytest.fixture
    def root_fixture():
        return "root"

tests/services/conftest.py
    import pytest
    @pytest.fixture
    def nested_fixture():
        return "nested"

tests/services/test_a.py
    def test_a(nested_fixture):
        assert nested_fixture == "nested"

tests/services/test_b.py
    def test_b(nested_fixture):
        assert nested_fixture == "nested"

tests/test_top.py
    def test_top(root_fixture):
        assert root_fixture == "root"
$ python -m pytest tests/services/test_a.py tests/test_top.py tests/services/test_b.py -q -p no:cacheprovider
pytest 9.1.1 -> 2 passed, 1 error      (ERROR at setup of test_b: fixture 'nested_fixture' not found)
pytest 8.4.2 -> 3 passed

What we traced (please treat as a pointer, not a diagnosis): with a bare file argument directly under an ancestor directory, Session.collect sets handle_dupes=False, so _collect_one_node bypasses its cache and re-collects tests/services, producing a second Directory node instance for the same path. Since 9.x matches fixture visibility by node identity in FixtureManager._matchfactories rather than by nodeid prefix, fixtures registered on the first tests/services instance are invisible to items collected under the second one. Two real projects hit this from developers running a hand-picked file list; pytest tests/ is unaffected.

Activity

  1. kokhlo commented on Sep 7, 2026

    @kokhlo

    Hi! I can take this one.

    Trace confirms the pointer: Session.collect passes handle_dupes=False for bare file args, so _collect_one_node re-collects tests/services and creates a second Directory node for the same path. FixtureManager._matchfactories then matches by node identity (nodeid equality of the collected parent chain), so fixtures defined in nested/conftest.py and registered under the first Directory instance are invisible to items collected under the second one.

    Plan:

    1. Regression test from the minimal repro (interleaved file args: nested/a.py parent/top.py nested/b.py).
    2. Fix in collection — either make the handle_dupes=False path reuse the existing cached Directory node when it already exists, or match fixtures by nodeid prefix in _matchfactories instead of node identity.

    I'll follow the contributor guidelines and aim to keep the diff minimal.

  2. kokhlo commented on Sep 7, 2026

    @kokhlo

    Update after verification — retracting my claim, this is already fixed on main.

    Verified on a clean venv: the repro fails on 9.1.1 exactly as reported (2 passed, 1 error, fixture lost on the interleaved run), but passes 3/3 on current main (built from source).

    The fix is #14635 (commit fc8f56bd2, merged 2026-07-22): _collect_one_node now reconciles re-collected Directory children with cached ones so fixture registration keyed by node identity stays valid — precisely the mechanism traced in this report. It landed after the 9.1.1 release (2026-06-19), which is why the reporter still sees it; there is no 9.1.x maintenance branch, so it ships with 9.2.0.

    Sorry for the noise on the claim — hope the verification still saves a triage round. Thanks for the excellent root-cause tracing, it made this a five-minute check.

  3. RonnyPfannschmidt commented on Sep 11, 2026

    @RonnyPfannschmidt
    Member

    #14968 is the backport

  4. rennf93 commented on Sep 11, 2026

    @rennf93
    Author
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