From 3ab8a0897b571c78e4f71be3ebd221196fbc5272 Mon Sep 17 00:00:00 2001 From: Dresden Date: Tue, 28 Jul 2026 01:16:53 -0700 Subject: [PATCH 1/2] Fix finalizers after fixture setup errors --- AUTHORS | 1 + changelog/14775.bugfix.rst | 1 + src/_pytest/fixtures.py | 14 ++++++-------- testing/deprecated_test.py | 6 +++++- 4 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 changelog/14775.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 229e4315078..e2e0243de8d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -143,6 +143,7 @@ Denivy Braiam Rück Deysha Rivera Dheeraj C K Dhiren Serai +Dresden Diego Russo Dima Gerasimov Dmitry Dygalo diff --git a/changelog/14775.bugfix.rst b/changelog/14775.bugfix.rst new file mode 100644 index 00000000000..4b9ccc49df3 --- /dev/null +++ b/changelog/14775.bugfix.rst @@ -0,0 +1 @@ +Fixed an internal ``AssertionError`` that could occur when a class-scoped fixture defined as an instance method raised its deprecation warning as an error. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 1b1591d1f83..3913f625bb9 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1205,11 +1205,6 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._finalizers.append(finalizer) def finish(self, request: SubRequest) -> None: - if self.cached_result is None: - # Already finished. It is assumed that finalizers cannot be added in - # this state. - return - exceptions: list[BaseException] = [] while self._finalizers: fin = self._finalizers.pop() @@ -1280,11 +1275,11 @@ def execute(self, request: SubRequest) -> FixtureValue: # Register the pytest_fixture_post_finalizer as the first finalizer, # which is executed last. assert not self._finalizers - self.addfinalizer( - lambda: request.node.ihook.pytest_fixture_post_finalizer( + def post_finalizer() -> None: + request.node.ihook.pytest_fixture_post_finalizer( fixturedef=self, request=request ) - ) + self.addfinalizer(post_finalizer) ihook = request.node.ihook try: @@ -1293,6 +1288,9 @@ def execute(self, request: SubRequest) -> FixtureValue: result: FixtureValue = ihook.pytest_fixture_setup( fixturedef=self, request=request ) + except BaseException: + self._finalizers.remove(post_finalizer) + raise finally: # Schedule our finalizer, even if the setup failed. request.node.addfinalizer(finalizer) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 8eed1fb3149..eb7db1eefcd 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -99,15 +99,19 @@ def fix(self): def test_foo(self, fix): pass + + def test_bar(self, fix): + pass """ ) result = pytester.runpytest("-Werror::pytest.PytestRemovedIn10Warning") - result.assert_outcomes(errors=1) + result.assert_outcomes(errors=2) result.stdout.fnmatch_lines( [ "*PytestRemovedIn10Warning: Class-scoped fixtures defined as instance methods*" ] ) + result.stdout.no_fnmatch_line("*AssertionError*") def test_class_scope_classmethod_fixture_not_deprecated(pytester: Pytester) -> None: From 53cacf4518c56b66385dfe614640a9f62e38521b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 08:41:58 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/fixtures.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 3913f625bb9..c24e7f0cc7a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1275,10 +1275,12 @@ def execute(self, request: SubRequest) -> FixtureValue: # Register the pytest_fixture_post_finalizer as the first finalizer, # which is executed last. assert not self._finalizers + def post_finalizer() -> None: request.node.ihook.pytest_fixture_post_finalizer( fixturedef=self, request=request ) + self.addfinalizer(post_finalizer) ihook = request.node.ihook