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

View File

@ -3397,6 +3397,28 @@ class TestErrors:
["*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:
def test_funcarg_compat(self, pytester: Pytester) -> None: