fixtures: fix tracebacks for higher-scoped failed fixtures getting longer and longer

Fix #12204.
This commit is contained in:
Ran Benita 2024-04-28 11:44:55 +03:00
parent 127a372928
commit 0b91d5e3e8
3 changed files with 32 additions and 5 deletions

View File

@ -0,0 +1,4 @@
Fix a regression in pytest 8.0 where tracebacks get longer and longer when multiple tests fail due to a shared higher-scope fixture which raised.
The fix necessitated internal changes which may affect some plugins:
- ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)`` instead of ``exc``.

View File

@ -8,6 +8,7 @@ import inspect
import os import os
from pathlib import Path from pathlib import Path
import sys import sys
import types
from typing import AbstractSet from typing import AbstractSet
from typing import Any from typing import Any
from typing import Callable from typing import Callable
@ -104,8 +105,8 @@ _FixtureCachedResult = Union[
None, None,
# Cache key. # Cache key.
object, object,
# Exception if raised. # The exception and the original traceback.
BaseException, Tuple[BaseException, Optional[types.TracebackType]],
], ],
] ]
@ -1049,8 +1050,8 @@ class FixtureDef(Generic[FixtureValue]):
# numpy arrays (#6497). # numpy arrays (#6497).
if my_cache_key is cache_key: if my_cache_key is cache_key:
if self.cached_result[2] is not None: if self.cached_result[2] is not None:
exc = self.cached_result[2] exc, exc_tb = self.cached_result[2]
raise exc raise exc.with_traceback(exc_tb)
else: else:
result = self.cached_result[0] result = self.cached_result[0]
return result return result
@ -1126,7 +1127,7 @@ def pytest_fixture_setup(
# Don't show the fixture as the skip location, as then the user # Don't show the fixture as the skip location, as then the user
# wouldn't know which test skipped. # wouldn't know which test skipped.
e._use_item_location = True e._use_item_location = True
fixturedef.cached_result = (None, my_cache_key, e) fixturedef.cached_result = (None, my_cache_key, (e, e.__traceback__))
raise raise
fixturedef.cached_result = (result, my_cache_key, None) fixturedef.cached_result = (result, my_cache_key, None)
return result return result

View File

@ -3397,6 +3397,28 @@ class TestErrors:
["*def gen(qwe123):*", "*fixture*qwe123*not found*", "*1 error*"] ["*def gen(qwe123):*", "*fixture*qwe123*not found*", "*1 error*"]
) )
def test_cached_exception_doesnt_get_longer(self, pytester: Pytester) -> None:
"""Regression test for #12204."""
pytester.makepyfile(
"""
import pytest
@pytest.fixture(scope="session")
def bad(): 1 / 0
def test_1(bad): pass
def test_2(bad): pass
def test_3(bad): pass
"""
)
result = pytester.runpytest_inprocess("--tb=native")
assert result.ret == ExitCode.TESTS_FAILED
failures = result.reprec.getfailures() # type: ignore[attr-defined]
assert len(failures) == 3
lines1 = failures[1].longrepr.reprtraceback.reprentries[0].lines
lines2 = failures[2].longrepr.reprtraceback.reprentries[0].lines
assert len(lines1) == len(lines2)
class TestShowFixtures: class TestShowFixtures:
def test_funcarg_compat(self, pytester: Pytester) -> None: def test_funcarg_compat(self, pytester: Pytester) -> None: