From 0b91d5e3e869d00c01c614d827745a9ed4cddb43 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 11:44:55 +0300 Subject: [PATCH 1/3] fixtures: fix tracebacks for higher-scoped failed fixtures getting longer and longer Fix #12204. --- changelog/12204.bugfix.rst | 4 ++++ src/_pytest/fixtures.py | 11 ++++++----- testing/python/fixtures.py | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 changelog/12204.bugfix.rst diff --git a/changelog/12204.bugfix.rst b/changelog/12204.bugfix.rst new file mode 100644 index 000000000..b89a04827 --- /dev/null +++ b/changelog/12204.bugfix.rst @@ -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``. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 09fd07422..5f10d565f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -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 diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 12ca6e926..77914fed7 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -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: From 3e81cb2f455fa091bc5a78742d190437f8281c68 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 11:47:37 +0300 Subject: [PATCH 2/3] runner: fix tracebacks for failed collectors getting longer and longer Refs https://github.com/pytest-dev/pytest/issues/12204#issuecomment-2081239376 --- changelog/12204.bugfix.rst | 3 +++ src/_pytest/runner.py | 14 ++++++++++---- testing/test_runner.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/changelog/12204.bugfix.rst b/changelog/12204.bugfix.rst index b89a04827..9690f513a 100644 --- a/changelog/12204.bugfix.rst +++ b/changelog/12204.bugfix.rst @@ -1,4 +1,7 @@ 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. +Also fix a similar regression in pytest 5.4 for collectors which raise during setup. + The fix necessitated internal changes which may affect some plugins: - ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)`` instead of ``exc``. +- ``SetupState.stack`` failures are now a tuple ``(exc, tb)`` instead of ``exc``. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index a551f715a..bf4d9a37f 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -5,6 +5,7 @@ import bdb import dataclasses import os import sys +import types from typing import Callable from typing import cast from typing import Dict @@ -488,8 +489,13 @@ class SetupState: Tuple[ # Node's finalizers. List[Callable[[], object]], - # Node's exception, if its setup raised. - Optional[Union[OutcomeException, Exception]], + # Node's exception and original traceback, if its setup raised. + Optional[ + Tuple[ + Union[OutcomeException, Exception], + Optional[types.TracebackType], + ] + ], ], ] = {} @@ -502,7 +508,7 @@ class SetupState: for col, (finalizers, exc) in self.stack.items(): assert col in needed_collectors, "previous item was not torn down properly" if exc: - raise exc + raise exc[0].with_traceback(exc[1]) for col in needed_collectors[len(self.stack) :]: assert col not in self.stack @@ -511,7 +517,7 @@ class SetupState: try: col.setup() except TEST_OUTCOME as exc: - self.stack[col] = (self.stack[col][0], exc) + self.stack[col] = (self.stack[col][0], (exc, exc.__traceback__)) raise def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: diff --git a/testing/test_runner.py b/testing/test_runner.py index 8b41ec28a..ecb98f2ff 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -142,6 +142,43 @@ class TestSetupState: assert isinstance(func.exceptions[0], TypeError) # type: ignore assert isinstance(func.exceptions[1], ValueError) # type: ignore + def test_cached_exception_doesnt_get_longer(self, pytester: Pytester) -> None: + """Regression test for #12204 (the "BTW" case).""" + pytester.makepyfile(test="") + # If the collector.setup() raises, all collected items error with this + # exception. + pytester.makeconftest( + """ + import pytest + + class MyItem(pytest.Item): + def runtest(self) -> None: pass + + class MyBadCollector(pytest.Collector): + def collect(self): + return [ + MyItem.from_parent(self, name="one"), + MyItem.from_parent(self, name="two"), + MyItem.from_parent(self, name="three"), + ] + + def setup(self): + 1 / 0 + + def pytest_collect_file(file_path, parent): + if file_path.name == "test.py": + return MyBadCollector.from_parent(parent, name='bad') + """ + ) + + 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 BaseFunctionalTests: def test_passfunction(self, pytester: Pytester) -> None: From cf90008a1a3924f323543bfdfc2e86c65eeae941 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:38:05 -0300 Subject: [PATCH 3/3] build(deps): Bump peter-evans/create-pull-request from 6.0.4 to 6.0.5 (#12268) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6.0.4 to 6.0.5. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/9153d834b60caba6d51c9b9510b087acf9f33f83...6d6857d36972b65feb161a90e484f2984215f83e) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/update-plugin-list.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 6943e2076..1015d01c9 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -46,7 +46,7 @@ jobs: run: python scripts/update-plugin-list.py - name: Create Pull Request - uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 + uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e with: commit-message: '[automated] Update plugin list' author: 'pytest bot '