|
24 | 24 | fixup_module_metadata,
|
25 | 25 | generic_function,
|
26 | 26 | is_main_thread,
|
| 27 | + raise_single_exception_from_group, |
27 | 28 | )
|
28 | 29 | from ..testing import wait_all_tasks_blocked
|
29 | 30 |
|
| 31 | +if sys.version_info < (3, 11): |
| 32 | + from exceptiongroup import BaseExceptionGroup, ExceptionGroup |
| 33 | + |
30 | 34 | if TYPE_CHECKING:
|
31 | 35 | from collections.abc import AsyncGenerator
|
32 | 36 |
|
@@ -267,3 +271,68 @@ def test_fixup_module_metadata() -> None:
|
267 | 271 | mod.some_func()
|
268 | 272 | mod._private()
|
269 | 273 | mod.SomeClass().method()
|
| 274 | + |
| 275 | + |
| 276 | +async def test_raise_single_exception_from_group() -> None: |
| 277 | + excinfo: pytest.ExceptionInfo[BaseException] |
| 278 | + |
| 279 | + exc = ValueError("foo") |
| 280 | + cause = SyntaxError("cause") |
| 281 | + context = TypeError("context") |
| 282 | + exc.__cause__ = cause |
| 283 | + exc.__context__ = context |
| 284 | + cancelled = trio.Cancelled._create() |
| 285 | + |
| 286 | + with pytest.raises(ValueError, match="foo") as excinfo: |
| 287 | + raise_single_exception_from_group(ExceptionGroup("", [exc])) |
| 288 | + assert excinfo.value.__cause__ == cause |
| 289 | + assert excinfo.value.__context__ == context |
| 290 | + |
| 291 | + with pytest.raises(ValueError, match="foo") as excinfo: |
| 292 | + raise_single_exception_from_group( |
| 293 | + ExceptionGroup("", [ExceptionGroup("", [exc])]) |
| 294 | + ) |
| 295 | + assert excinfo.value.__cause__ == cause |
| 296 | + assert excinfo.value.__context__ == context |
| 297 | + |
| 298 | + with pytest.raises(ValueError, match="foo") as excinfo: |
| 299 | + raise_single_exception_from_group( |
| 300 | + BaseExceptionGroup( |
| 301 | + "", [cancelled, BaseExceptionGroup("", [cancelled, exc])] |
| 302 | + ) |
| 303 | + ) |
| 304 | + assert excinfo.value.__cause__ == cause |
| 305 | + assert excinfo.value.__context__ == context |
| 306 | + |
| 307 | + # multiple non-cancelled |
| 308 | + eg = ExceptionGroup("", [ValueError("foo"), ValueError("bar")]) |
| 309 | + with pytest.raises( |
| 310 | + AssertionError, |
| 311 | + match=r"^Attempted to unwrap exceptiongroup with multiple non-cancelled exceptions. This is often caused by a bug in the caller.$", |
| 312 | + ) as excinfo: |
| 313 | + raise_single_exception_from_group(eg) |
| 314 | + assert excinfo.value.__cause__ is eg |
| 315 | + assert excinfo.value.__context__ is None |
| 316 | + |
| 317 | + # keyboardinterrupt overrides everything |
| 318 | + eg_ki = BaseExceptionGroup( |
| 319 | + "", |
| 320 | + [ |
| 321 | + ValueError("foo"), |
| 322 | + ValueError("bar"), |
| 323 | + KeyboardInterrupt("this exc doesn't get reraised"), |
| 324 | + ], |
| 325 | + ) |
| 326 | + with pytest.raises(KeyboardInterrupt, match=r"^$") as excinfo: |
| 327 | + raise_single_exception_from_group(eg_ki) |
| 328 | + assert excinfo.value.__cause__ is eg_ki |
| 329 | + assert excinfo.value.__context__ is None |
| 330 | + |
| 331 | + # if we only got cancelled, first one is reraised |
| 332 | + with pytest.raises(trio.Cancelled, match=r"^Cancelled$") as excinfo: |
| 333 | + raise_single_exception_from_group( |
| 334 | + BaseExceptionGroup("", [cancelled, trio.Cancelled._create()]) |
| 335 | + ) |
| 336 | + assert excinfo.value is cancelled |
| 337 | + assert excinfo.value.__cause__ is None |
| 338 | + assert excinfo.value.__context__ is None |
0 commit comments