Fix loop factory parametrization for sync fixtures - #1553
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1553 +/- ##
==========================================
+ Coverage 94.50% 94.83% +0.32%
==========================================
Files 2 2
Lines 510 542 +32
Branches 62 69 +7
==========================================
+ Hits 482 514 +32
Misses 22 22
Partials 6 6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
tjkuson
left a comment
There was a problem hiding this comment.
Thank you for the PR, but as-written it duplicates, coerces, and undermines pytest's internals in a way that's error-prone and difficult to maintain.
| # before their values are supplied to a synchronous test. | ||
| fixtureinfo = metafunc.definition._fixtureinfo | ||
| if _asyncio_loop_factory.__name__ not in fixtureinfo.initialnames: | ||
| object.__setattr__( |
There was a problem hiding this comment.
We should not be manipulating pytest internals like this.
There was a problem hiding this comment.
Addressed in d86d02c. I removed the object.setattr call and no longer mutate FuncFixtureInfo.initialnames. After pytest has created and pruned the Function item, the existing collection wrapper places the already-parametrized internal factory fixture first in the item's fixture resolution order, so its cache key changes before managed fixtures are read.
| @pytest.hookimpl(tryfirst=True) | ||
| # Direct parametrization replaces same-named fixtures during this hook. Run after | ||
| # pytest and user hooks so the resolved fixture graph reflects those replacements. | ||
| @pytest.hookimpl(trylast=True) |
There was a problem hiding this comment.
This changes the node IDs which is breaking for users that match on test names.
There was a problem hiding this comment.
Addressed in d86d02c. The original async pytest_generate_tests(tryfirst=True) ordering is restored. Sync fixture detection now uses a separate post-yield wrapper, so existing async IDs retain the factory-user_parameter order. I added collect-only regression coverage for both the preserved async order and the new sync user_parameter-factory order.
| return None | ||
|
|
||
|
|
||
| def _get_managed_fixture_loop_scope( |
There was a problem hiding this comment.
This reimplements pytest's fixture traversal system in a way that's error-prone is difficult to maintain.
There was a problem hiding this comment.
Addressed in d86d02c. The recursive fixture traversal, FixtureManager fallback, and override-chain bookkeeping have been removed. Sync detection now only scans the pytest-computed metafunc.fixturenames closure and reads the active fixturedefs[-1]. The docs explicitly limit the behavior to dependencies represented in the static closure rather than reimplementing traversal for dynamic or same-name override cases.
|
Thanks for the review. I reworked the implementation in d86d02c around the three concerns: the custom recursive fixture traversal and FixtureManager fallback are gone; FuncFixtureInfo.initialnames is no longer mutated; and the original tryfirst ordering for async tests is restored with collect-only node-ID regression tests. Sync tests now use a separate post-yield generate wrapper, scan only the pytest-computed static fixture closure, and resolve the factory cache key before managed fixtures. The documented boundary is intentionally the closure pytest exposes: dynamic request.getfixturevalue dependencies and hidden same-name override chains are not rediscovered. The only remaining private access is a shallow, read-only lookup of the active FixtureDef through Metafunc._arg2fixturedefs because pytest 8.4-9.1 has no public equivalent. All CI checks pass, including pytest main, Python 3.10-3.14/3.14t on Windows and Linux, docs, lint, and packaging. Codecov reports all modified coverable lines covered. I would appreciate another review. |
Fixes #1501.
Summary
Why
Previously only async test items requested the parametrized loop-factory fixture. A synchronous test consuming a managed async fixture therefore requested an unparametrized/default factory, changed the factory cache key, and could tear down shared fixtures at a sync/async test boundary. Parametrizing the affected synchronous test items keeps the factory cache key and fixture lifetime consistent.
Validation
tests/test_loop_factory_parametrization.py: 55 passed with pytest 8.4.0tests/test_loop_factory_parametrization.py: 55 passed with pytest 9.1.1ruff check pytest_asyncio testspyright pytest_asyncio testsThe documentation build was not run locally because Sphinx was not installed in the test environment.