From ed5b22b66430f0c990f1f2a458c84064a21ef643 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 31 Oct 2024 15:19:42 +0100 Subject: [PATCH] fixup: correct missed legacy codepath --- changelog/544.bugfix.rst | 9 +++++---- src/pluggy/_callers.py | 14 +++++++++++++- testing/test_multicall.py | 9 +++++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/changelog/544.bugfix.rst b/changelog/544.bugfix.rst index 6e7fe01..87aa1bc 100644 --- a/changelog/544.bugfix.rst +++ b/changelog/544.bugfix.rst @@ -1,5 +1,6 @@ -Correctly pass StopIteration trough wrappers. +Correctly pass :class:`StopIteration` trough hook 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. +Raising a :class:`StopIteration` in a generator triggers a :class:`RuntimeError`. + +If the :class:`RuntimeError` of a generator has the passed in :class:`StopIteration` as cause +resume with that :class:`StopIteration` as normal exception instead of failing with the :class:`RuntimeError`. diff --git a/src/pluggy/_callers.py b/src/pluggy/_callers.py index 6b5b90e..0bf4df4 100644 --- a/src/pluggy/_callers.py +++ b/src/pluggy/_callers.py @@ -122,6 +122,7 @@ def _multicall( try: teardown.throw(exception) # type: ignore[union-attr] except RuntimeError as re: + # Stopiteration from generator causes RuntimeError even for corutine usage if ( isinstance(exception, StopIteration) and re.__cause__ is exception @@ -174,7 +175,18 @@ def _multicall( else: try: if outcome._exception is not None: - teardown.throw(outcome._exception) + try: + teardown.throw(outcome._exception) + except RuntimeError as re: + # Stopiteration from generator causes RuntimeError even for corutine usage + if ( + isinstance(outcome._exception, StopIteration) + and re.__cause__ is outcome._exception + ): + teardown.close() + continue + else: + raise else: teardown.send(outcome._result) # Following is unreachable for a well behaved hook wrapper. diff --git a/testing/test_multicall.py b/testing/test_multicall.py index 231dbf4..eb81230 100644 --- a/testing/test_multicall.py +++ b/testing/test_multicall.py @@ -416,7 +416,8 @@ def m2(): ] -def test_wrapper_stopiteration_passtrough(): +@pytest.mark.parametrize("has_hookwrapper", [True, False]) +def test_wrapper_stopiteration_passtrough(has_hookwrapper: bool) -> None: out = [] @hookimpl(wrapper=True) @@ -427,6 +428,10 @@ def wrap(): finally: out.append("wrap done") + @hookimpl(wrapper=not has_hookwrapper, hookwrapper=has_hookwrapper) + def wrap_path2(): + yield + @hookimpl def stop(): out.append("stop") @@ -434,7 +439,7 @@ def stop(): with pytest.raises(StopIteration): try: - MC([stop, wrap], {}) + MC([stop, wrap, wrap_path2], {}) finally: out.append("finally")