Skip to content

Commit

Permalink
result: keep original traceback and reraise with it
Browse files Browse the repository at this point in the history
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 (fbc4442).
  • Loading branch information
bluetech committed Apr 29, 2024
1 parent 480c975 commit 93ac1e9
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog/504.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a regression in pluggy 1.1.0 where using :func:`result.get_result() <pluggy.Result.get_result>` on the same failed :class:`~pluggy.Result` causes the exception's traceback to get longer and longer.
11 changes: 8 additions & 3 deletions src/pluggy/_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Result(Generic[ResultType]):
"""An object used to inspect and set the result in a :ref:`hook wrapper
<hookwrappers>`."""

__slots__ = ("_result", "_exception")
__slots__ = ("_result", "_exception", "_traceback")

def __init__(
self,
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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``.
Expand All @@ -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.
Expand All @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions testing/test_result.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 93ac1e9

Please sign in to comment.