diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 7656fca2f5b..cf916e230d8 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1275,6 +1275,14 @@ def execute(self, request: SubRequest) -> FixtureValue: for parent_fixture in requested_fixtures_that_should_finalize_us: parent_fixture.addfinalizer(finalizer) + # If a previous call to execute() raised before caching the result + # (e.g. because pytest_fixture_setup turned a warning into an error), + # self._finalizers may still contain stale entries: finish() is a + # no-op when cached_result is None (it assumes "already finished"), + # so those finalizers are never drained. Clear them here so this + # fresh execution starts from a clean slate. + # See https://github.com/pytest-dev/pytest/issues/14775 + self._finalizers.clear() # Register the pytest_fixture_post_finalizer as the first finalizer, # which is executed last. assert not self._finalizers diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index ee91880d23a..ae6bf888d90 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -123,6 +123,49 @@ def test_foo(self, fix): ) +def test_class_scoped_instance_method_werror_multiple_tests( + pytester: Pytester, +) -> None: + """Multiple tests sharing a class-scoped instance-method fixture must each report + the deprecation warning (not an internal AssertionError) when -Werror is active. + + Regression test for https://github.com/pytest-dev/pytest/issues/14775. + When -Werror turns the PytestRemovedIn10Warning into an error during fixture + setup, FixtureDef.execute() exited without caching the result, leaving stale + entries in self._finalizers. The second test then hit + ``assert not self._finalizers`` with an internal AssertionError instead of the + expected deprecation error. + """ + pytester.makepyfile( + """ + import pytest + + class TestFixt: + @pytest.fixture(scope="class") + def fixt(self): + yield + + def test_1(self, fixt): + pass + + def test_2(self, fixt): + pass + """ + ) + result = pytester.runpytest("-Werror::pytest.PytestRemovedIn10Warning") + result.assert_outcomes(errors=2) + # Both tests should report the deprecation warning, not an AssertionError. + result.stdout.fnmatch_lines( + [ + "*ERROR at setup of TestFixt.test_1*", + "*PytestRemovedIn10Warning*", + "*ERROR at setup of TestFixt.test_2*", + "*PytestRemovedIn10Warning*", + ] + ) + result.stdout.no_fnmatch_line("*AssertionError*") + + @pytest.mark.parametrize( "scope", [Scope.Class, Scope.Module, Scope.Package, Scope.Session] )