Skip to content

🐛 fix: pytest 9 compatibility for the class-scoped d fixture - #68

Merged
nstarman merged 2 commits into
mainfrom
fix/pytest9-class-fixture
Sep 7, 2026
Merged

🐛 fix: pytest 9 compatibility for the class-scoped d fixture#68
nstarman merged 2 commits into
mainfrom
fix/pytest9-class-fixture

Conversation

@nstarman

@nstarman nstarman commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Problem

The test suite fails under pytest 9.x (12 ERRORs at fixture setup) while passing under 8.x. Since pyproject.toml declares pytest>=8.4.2 with no upper bound, CI breaks the moment uv.lock picks up pytest 9.

Root cause

The visible AssertionError at _pytest/fixtures.py:1221 (assert not self._finalizers) is a cascade, not the cause. The real error is on the first test in the class:

PytestRemovedIn10Warning('Class-scoped fixture defined as instance method is deprecated.
Instance attributes set in this fixture will NOT be visible to test methods,
as each test gets a new instance while the fixture runs only once per class.
Use @classmethod decorator and set attributes on cls instead.')

pytest 9 added this deprecation (_pytest/deprecated.py, CLASS_FIXTURE_INSTANCE_METHOD). Because we set filterwarnings = ["error"], it is raised during FixtureDef.execute, which leaves the FixtureDef half-initialised — every subsequent test in the class then trips the internal assertion. Hence 1 real error plus 11 cascading ones.

So this is a deprecated pattern on our side, not a pytest regression.

Fix

Drop scope="class" from the d fixture. It just builds ImmutableMap(a=1, b=2), so class scope bought nothing.

Preventing a repeat

CI runs uv run --frozen, so a breaking release is invisible until the lock is refreshed. Added a tests-latest job that re-resolves to the newest versions (uv run --upgrade pytest) on every run. It is deliberately not in the status gate, so a bad upstream release can't block merges — it's an early-warning signal, not a required check.

No upper bound was added to pytest: the cap would have hidden a genuine deprecation rather than surfaced it, and uv.lock already gives CI reproducibility.

Verification

Full suite (README + src/ + tests/):

pytest result
8.4.2 76 passed
9.1.1 76 passed

The canary command was also run locally: resolves pytest 9.1.1, 76 passed. pre-commit passes on both changed files.

Note for #66 / #67

Based on main rather than on the #66#67 stack. The d fixture is byte-identical in main, #66 and #67, so basing this on the stack would block a CI fix behind two feature PRs. Merging here first leaves both PRs a one-line rebase.

Possible upstream report

The cascading internal assertion is arguably a pytest bug worth filing separately — pytest's own deprecation warning corrupts fixture state when raised as an error, and the resulting AssertionError completely hides the actionable message. Minimal repro under pytest 9.1.1 with -W error:

import pytest

class TestX:
    @pytest.fixture(scope="class")
    def d(self):
        return 1

    def test_a(self, d): assert d == 1
    def test_b(self, d): assert d == 1

test_a shows the deprecation; test_b shows only AssertionError.

🤖 Generated with Claude Code

nstarman and others added 2 commits September 7, 2026 14:32
pytest 9 deprecates class-scoped fixtures defined as instance methods
(PytestRemovedIn10Warning). With `filterwarnings = ["error"]` the warning
is raised during fixture setup, which errors the first test and leaves the
FixtureDef half-initialised, so the remaining 11 tests fail on pytest's
internal `assert not self._finalizers`.

`d` just builds `ImmutableMap(a=1, b=2)`, so class scope buys nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI runs against uv.lock, so a breaking release (such as pytest 9) is only
noticed when the lock is refreshed. This job re-resolves to the newest
versions on every run. It is deliberately excluded from the `status` gate
so an upstream release cannot block merges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nstarman nstarman added this to the v0.2.x milestone Sep 7, 2026
@github-code-quality

Copy link
Copy Markdown

Code Coverage Overview

Languages: Python

Python / code-coverage-agent

The overall line coverage in commit 5b6d8c3 in the fix/pytest9-class-fi... branch remains at 100%, unchanged from commit 7fc1341 in the main branch.

@nstarman
nstarman merged commit bc38c15 into main Sep 7, 2026
18 checks passed
@nstarman
nstarman deleted the fix/pytest9-class-fixture branch September 7, 2026 18:41
nstarman added a commit that referenced this pull request Sep 7, 2026
…d` fixture (#69)

Co-authored-by: Nathaniel Starkman <nstarman@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant