From 480c975ebcacece75b43000cff7df07bc8afdf40 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 12:32:55 +0300 Subject: [PATCH 1/2] Remove useless `with_traceback` calls These do nothing, I was just confused. --- src/pluggy/_callers.py | 2 +- src/pluggy/_result.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pluggy/_callers.py b/src/pluggy/_callers.py index d01f925..f4a2ace 100644 --- a/src/pluggy/_callers.py +++ b/src/pluggy/_callers.py @@ -136,7 +136,7 @@ def _multicall( _raise_wrapfail(teardown, "has second yield") # type: ignore[arg-type] if exception is not None: - raise exception.with_traceback(exception.__traceback__) + raise exception else: return result diff --git a/src/pluggy/_result.py b/src/pluggy/_result.py index f9a081c..15f7687 100644 --- a/src/pluggy/_result.py +++ b/src/pluggy/_result.py @@ -97,7 +97,7 @@ def get_result(self) -> ResultType: if exc is None: return cast(ResultType, self._result) else: - raise exc.with_traceback(exc.__traceback__) + raise exc # Historical name (pluggy<=1.2), kept for backward compatibility. From 93ac1e9c468c733fb632237836766795f647d755 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 12:36:07 +0300 Subject: [PATCH 2/2] result: keep original traceback and reraise with it The `Result` API allows the same exception to be raised multiple times. In Python, `Exception.__traceback__` becomes longer everytime an exception is raised. This means that on every raise, the exception's traceback gets longer and longer. To prevent this, save the original traceback and always raise using it. Regressed in pluggy 1.1.0 (fbc444218c442dd8cbe29bd68cde8fea52b56baf). --- changelog/504.bugfix.rst | 1 + src/pluggy/_result.py | 11 ++++++++--- testing/test_result.py | 27 +++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 changelog/504.bugfix.rst create mode 100644 testing/test_result.py diff --git a/changelog/504.bugfix.rst b/changelog/504.bugfix.rst new file mode 100644 index 0000000..17a08ff --- /dev/null +++ b/changelog/504.bugfix.rst @@ -0,0 +1 @@ +Fix a regression in pluggy 1.1.0 where using :func:`result.get_result() ` on the same failed :class:`~pluggy.Result` causes the exception's traceback to get longer and longer. diff --git a/src/pluggy/_result.py b/src/pluggy/_result.py index 15f7687..aa3912c 100644 --- a/src/pluggy/_result.py +++ b/src/pluggy/_result.py @@ -28,7 +28,7 @@ class Result(Generic[ResultType]): """An object used to inspect and set the result in a :ref:`hook wrapper `.""" - __slots__ = ("_result", "_exception") + __slots__ = ("_result", "_exception", "_traceback") def __init__( self, @@ -38,6 +38,8 @@ def __init__( """:meta private:""" self._result = result self._exception = exception + # Exception __traceback__ is mutable, this keeps the original. + self._traceback = exception.__traceback__ if exception is not None else None @property def excinfo(self) -> _ExcInfo | None: @@ -46,7 +48,7 @@ def excinfo(self) -> _ExcInfo | None: if exc is None: return None else: - return (type(exc), exc, exc.__traceback__) + return (type(exc), exc, self._traceback) @property def exception(self) -> BaseException | None: @@ -75,6 +77,7 @@ def force_result(self, result: ResultType) -> None: """ self._result = result self._exception = None + self._traceback = None def force_exception(self, exception: BaseException) -> None: """Force the result to fail with ``exception``. @@ -85,6 +88,7 @@ def force_exception(self, exception: BaseException) -> None: """ self._result = None self._exception = exception + self._traceback = exception.__traceback__ if exception is not None else None def get_result(self) -> ResultType: """Get the result(s) for this hook call. @@ -94,10 +98,11 @@ def get_result(self) -> ResultType: """ __tracebackhide__ = True exc = self._exception + tb = self._traceback if exc is None: return cast(ResultType, self._result) else: - raise exc + raise exc.with_traceback(tb) # Historical name (pluggy<=1.2), kept for backward compatibility. diff --git a/testing/test_result.py b/testing/test_result.py new file mode 100644 index 0000000..c4a3392 --- /dev/null +++ b/testing/test_result.py @@ -0,0 +1,27 @@ +import traceback + +from pluggy import Result + + +def test_exceptions_traceback_doesnt_get_longer_and_longer() -> None: + def bad() -> None: + 1 / 0 + + result = Result.from_call(bad) + + try: + result.get_result() + except Exception as exc: + tb1 = traceback.extract_tb(exc.__traceback__) + + try: + result.get_result() + except Exception as exc: + tb2 = traceback.extract_tb(exc.__traceback__) + + try: + result.get_result() + except Exception as exc: + tb3 = traceback.extract_tb(exc.__traceback__) + + assert len(tb1) == len(tb2) == len(tb3)