diff --git a/AUTHORS b/AUTHORS index 229e4315078..2f3db95cb54 100644 --- a/AUTHORS +++ b/AUTHORS @@ -276,6 +276,7 @@ Kojo Idrissa Kostis Anagnostopoulos Kristoffer Nordström Kyle Altendorf +Kyue Lawrence Mitchell Lee Kamentsky Leonardus Chen diff --git a/changelog/14775.bugfix.rst b/changelog/14775.bugfix.rst new file mode 100644 index 00000000000..3aba430f9d9 --- /dev/null +++ b/changelog/14775.bugfix.rst @@ -0,0 +1 @@ +Fixed an internal ``AssertionError`` that could occur when a fixture's setup fails before its underlying function is even called (e.g. when the class-scoped-fixture-as-instance-method deprecation warning is turned into an error via ``-W error``). The failure is now cached like any other setup error instead of leaking finalizer state into the next test. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index b516c0a75d1..d7a43e2741f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1320,21 +1320,22 @@ def pytest_fixture_setup( for argname in fixturedef.argnames: kwargs[argname] = request.getfixturevalue(argname) - fixturefunc = resolve_fixture_function(fixturedef, request) my_cache_key = fixturedef.cache_key(request) - if inspect.isasyncgenfunction(fixturefunc) or inspect.iscoroutinefunction( - fixturefunc - ): - auto_str = " with autouse=True" if fixturedef._autouse else "" - fail( - f"{request.node.name!r} requested an async fixture {request.fixturename!r}{auto_str}, " - "with no plugin or hook that handled it. This is an error, as pytest does not natively support it.\n" - "See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture", - pytrace=False, - ) - try: + fixturefunc = resolve_fixture_function(fixturedef, request) + + if inspect.isasyncgenfunction(fixturefunc) or inspect.iscoroutinefunction( + fixturefunc + ): + auto_str = " with autouse=True" if fixturedef._autouse else "" + fail( + f"{request.node.name!r} requested an async fixture {request.fixturename!r}{auto_str}, " + "with no plugin or hook that handled it. This is an error, as pytest does not natively support it.\n" + "See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture", + pytrace=False, + ) + result = call_fixture_func(fixturefunc, request, kwargs) except TEST_OUTCOME as e: if isinstance(e, skip.Exception): diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 69044226b6d..fdd79b1ade9 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3801,6 +3801,42 @@ def test_3(bad): pass lines2 = failures[2].longrepr.reprtraceback.reprentries[0].lines assert len(lines1) == len(lines2) + def test_exception_before_fixture_func_called_is_cached( + self, pytester: Pytester + ) -> None: + """A failure that happens while resolving the fixture function itself + (e.g. a deprecation warning promoted to an error by ``-W error``, before + the fixture body ever runs) must be cached on the FixtureDef like any + other setup failure. Otherwise leftover finalizer state leaks into the + next test using the same class-scoped fixture and crashes pytest + internals instead of reporting the (repeated) original error (#14775). + """ + 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") + result.assert_outcomes(errors=2) + assert "AssertionError" not in result.stdout.str() + result.stdout.fnmatch_lines( + [ + "*PytestRemovedIn10Warning: Class-scoped fixtures defined as " + "instance methods are deprecated.*", + ] + ) + class TestShowFixtures: def test_funcarg_compat(self, pytester: Pytester) -> None: