fixtures: fix tracebacks for higher-scoped failed fixtures getting longer and longer
Fix #12204.
This commit is contained in:
		
							parent
							
								
									127a372928
								
							
						
					
					
						commit
						0b91d5e3e8
					
				| 
						 | 
					@ -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``.
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue