Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

result: keep original traceback and reraise with it #504

Merged
merged 2 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.
2 changes: 1 addition & 1 deletion src/pluggy/_callers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to eventually elevate this to a group in a major release

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think so.

raise exception
else:
return result

Expand Down
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.with_traceback(exc.__traceback__)
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)