Skip to content

Cache fixture setup failures that happen before the fixture function runs#14777

Open
Gooh456 wants to merge 1 commit into
pytest-dev:mainfrom
Gooh456:fix-fixture-setup-exception-caching
Open

Cache fixture setup failures that happen before the fixture function runs#14777
Gooh456 wants to merge 1 commit into
pytest-dev:mainfrom
Gooh456:fix-fixture-setup-exception-caching

Conversation

@Gooh456

@Gooh456 Gooh456 commented Jul 24, 2026

Copy link
Copy Markdown

yeah this one's a real crash, not just a bad error message.

execute() in fixtures.py registers the pytest_fixture_post_finalizer callback into self._finalizers (around line 1223) before calling pytest_fixture_setup(). But pytest_fixture_setup() only wraps call_fixture_func() in try/except TEST_OUTCOMEresolve_fixture_function() and the async-fixture fail() check both run outside that block. So if either of those raises, cached_result never gets set, and the finalizer already sitting in self._finalizers never gets popped (that only happens via finish(), which for a class-scoped fixture doesn't run until the class itself tears down, i.e. after the next test in the class already tried to set the fixture up again).

Next test hits assert not self._finalizers in execute() and pytest crashes internally instead of reporting the actual problem.

Easiest way to hit this right now: a class-scoped fixture defined as an instance method warns (the new deprecation from #14764), and under -W error that warning becomes an exception inside resolve_fixture_function(), before call_fixture_func() is ever reached. Repro is basically verbatim from the issue.

Fix: move fixturefunc = resolve_fixture_function(...) and the async check inside the existing try/except, so any setup failure — not just ones from inside the fixture body — gets cached the same way call_fixture_func failures already are. The second test then correctly re-raises the cached failure instead of tripping the assert.

Added a regression test in TestErrors (testing/python/fixtures.py) using the exact repro from the issue, asserting two clean errors instead of one clean error + one internal AssertionError. Checked it fails on main and passes with the fix by stashing/unstashing the fixtures.py change.

Ran testing/python/ and testing/deprecated_test.py locally, all green. Didn't run the full tox suite (mypy/pyright not installed here) — the diff is just a reindent/reorder of existing statements into the try block, no new types involved, but flagging that I haven't run those specific checks.

Fixes #14775

…runs

If resolve_fixture_function() (or the async-fixture check) raises before
call_fixture_func() is reached, pytest_fixture_setup() never wrote a
cached_result for the FixtureDef. The exception still propagates and
fails the current test correctly, but the FixtureDef is left in a
half-registered state: the pytest_fixture_post_finalizer callback added
in execute() is never popped, because that only happens via finish(),
and finish() for a class/module/session-scoped fixture doesn't run
until the owning scope's node tears down.

The next test that requests the same fixture instance then hits
`assert not self._finalizers` in FixtureDef.execute() and blows up with
an internal AssertionError instead of reporting the original problem.

This is easy to trigger now that resolving a class-scoped fixture
defined as an instance method emits a warning (deprecations.rst), which
becomes an exception under -W error before the fixture body ever runs.

Move fixturefunc = resolve_fixture_function(...) and the async-fixture
check inside the existing try/except TEST_OUTCOME block so any failure
during setup, not just failures inside the fixture body, gets recorded
in cached_result the same way. Subsequent requests then correctly
re-raise the cached failure instead of tripping the internal assert.

Fixes pytest-dev#14775

Signed-off-by: Kyue <164024549+Gooh456@users.noreply.github.com>
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Internal AssertionError after class scoped fixture warning

1 participant