Skip to content

Commit

Permalink
fix pytest-dev#544: Correctly pass StopIteration trough wrappers
Browse files Browse the repository at this point in the history
Raising a StopIteration in a generator triggers a RuntimeError.
If the RuntimeError of a generator has the passed in StopIteration as cause
resume with that StopIteration as normal exception instead of failing with the RuntimeError.
  • Loading branch information
RonnyPfannschmidt committed Oct 31, 2024
1 parent 145eea8 commit 4cf313d
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 1 deletion.
5 changes: 5 additions & 0 deletions changelog/544.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Correctly pass StopIteration trough wrappers.

Raising a StopIteration in a generator triggers a RuntimeError.
If the RuntimeError of a generator has the passed in StopIteration as cause
resume with that StopIteration as normal exception instead of failing with the RuntimeError.
12 changes: 11 additions & 1 deletion src/pluggy/_callers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,17 @@ def _multicall(
for teardown in reversed(teardowns):
try:
if exception is not None:
teardown.throw(exception) # type: ignore[union-attr]
try:
teardown.throw(exception) # type: ignore[union-attr]
except RuntimeError as re:
if (
isinstance(exception, StopIteration)
and re.__cause__ is exception
):
teardown.close() # type: ignore[union-attr]
continue
else:
raise
else:
teardown.send(result) # type: ignore[union-attr]
# Following is unreachable for a well behaved hook wrapper.
Expand Down
25 changes: 25 additions & 0 deletions testing/test_multicall.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,31 @@ def m2():
]


def test_wrapper_stopiteration_passtrough():
out = []

@hookimpl(wrapper=True)
def wrap():
out.append("wrap")
try:
yield
finally:
out.append("wrap done")

@hookimpl
def stop():
out.append("stop")
raise StopIteration

with pytest.raises(StopIteration):
try:
MC([stop, wrap], {})
finally:
out.append("finally")

assert out == ["wrap", "stop", "wrap done", "finally"]


def test_suppress_inner_wrapper_teardown_exc() -> None:
out = []

Expand Down

0 comments on commit 4cf313d

Please sign in to comment.