diff --git a/newsfragments/3275.bugfix.rst b/newsfragments/3275.bugfix.rst new file mode 100644 index 0000000000..b418b26d76 --- /dev/null +++ b/newsfragments/3275.bugfix.rst @@ -0,0 +1 @@ +Handle unwrapping SystemExit/KeyboardInterrupt exception gracefully in utility function ``raise_single_exception_from_group`` that reraises last exception from group. diff --git a/src/trio/_tests/test_util.py b/src/trio/_tests/test_util.py index 4182ba5db4..3a57b9cf21 100644 --- a/src/trio/_tests/test_util.py +++ b/src/trio/_tests/test_util.py @@ -323,25 +323,32 @@ async def test_raise_single_exception_from_group() -> None: [ ValueError("foo"), ValueError("bar"), - KeyboardInterrupt("this exc doesn't get reraised"), + KeyboardInterrupt("preserve error msg"), ], ) - with pytest.raises(KeyboardInterrupt, match=r"^$") as excinfo: + with pytest.raises( + KeyboardInterrupt, + match=r"^preserve error msg$", + ) as excinfo: raise_single_exception_from_group(eg_ki) + assert excinfo.value.__cause__ is eg_ki assert excinfo.value.__context__ is None - # and same for SystemExit + # and same for SystemExit but verify code too systemexit_ki = BaseExceptionGroup( "", [ ValueError("foo"), ValueError("bar"), - SystemExit("this exc doesn't get reraised"), + SystemExit(2), ], ) - with pytest.raises(SystemExit, match=r"^$") as excinfo: + + with pytest.raises(SystemExit) as excinfo: raise_single_exception_from_group(systemexit_ki) + + assert excinfo.value.code == 2 assert excinfo.value.__cause__ is systemexit_ki assert excinfo.value.__context__ is None diff --git a/src/trio/_util.py b/src/trio/_util.py index 106423e2aa..54d324cab3 100644 --- a/src/trio/_util.py +++ b/src/trio/_util.py @@ -398,7 +398,7 @@ def raise_single_exception_from_group( # immediately bail out if there's any KI or SystemExit for e in eg.exceptions: if isinstance(e, (KeyboardInterrupt, SystemExit)): - raise type(e) from eg + raise type(e)(*e.args) from eg cancelled_exception: trio.Cancelled | None = None noncancelled_exception: BaseException | None = None