@@ -2534,60 +2534,73 @@ async def test_cancel_scope_no_cancellederror() -> None:
2534
2534
assert not scope .cancelled_caught
2535
2535
2536
2536
2537
- """These tests are for fixing https://github.com/python-trio/trio/issues/2611
2538
- where exceptions raised before `task_status.started()` got wrapped twice.
2539
- """
2540
-
2541
-
2542
- async def raise_before (* , task_status : _core .TaskStatus [None ]) -> None :
2543
- raise ValueError
2544
-
2545
-
2546
- async def raise_after_started (* , task_status : _core .TaskStatus [None ]) -> None :
2547
- task_status .started ()
2548
- raise ValueError
2549
-
2550
-
2551
- async def raise_custom_exception_group_before (
2552
- * , task_status : _core .TaskStatus [None ]
2537
+ @pytest .mark .parametrize ("run_strict" , [False , True ])
2538
+ @pytest .mark .parametrize ("start_raiser_strict" , [False , True , None ])
2539
+ @pytest .mark .parametrize ("raise_after_started" , [False , True ])
2540
+ @pytest .mark .parametrize ("raise_custom_exc_grp" , [False , True ])
2541
+ def test_trio_run_strict_before_started (
2542
+ run_strict : bool ,
2543
+ start_raiser_strict : bool | None ,
2544
+ raise_after_started : bool ,
2545
+ raise_custom_exc_grp : bool ,
2553
2546
) -> None :
2554
- raise ExceptionGroup ("my group" , [ValueError ()])
2555
-
2556
-
2557
- def _check_exception (exc : pytest .ExceptionInfo [BaseException ]) -> None :
2558
- assert isinstance (exc .value , BaseExceptionGroup )
2559
- assert len (exc .value .exceptions ) == 1
2560
- assert isinstance (exc .value .exceptions [0 ], ValueError )
2561
-
2547
+ """
2548
+ Regression tests for #2611, where exceptions raised before
2549
+ `TaskStatus.started()` caused `Nursery.start()` to wrap them in an
2550
+ ExceptionGroup when using `run(..., strict_exception_groups=True)`.
2562
2551
2563
- async def _start_raiser (
2564
- raiser : Callable [[], Awaitable [None ]], strict : bool | None = None
2565
- ) -> None :
2566
- async with _core .open_nursery (strict_exception_groups = strict ) as nursery :
2567
- await nursery .start (raiser )
2552
+ Regression tests for #2844, where #2611 was initially fixed in a way that
2553
+ had unintended side effects.
2554
+ """
2568
2555
2556
+ raiser_exc : ValueError | ExceptionGroup [ValueError ]
2557
+ if raise_custom_exc_grp :
2558
+ raiser_exc = ExceptionGroup ("my group" , [ValueError ()])
2559
+ else :
2560
+ raiser_exc = ValueError ()
2569
2561
2570
- @pytest .mark .parametrize ("strict" , [False , True ])
2571
- @pytest .mark .parametrize ("raiser" , [raise_before , raise_after_started ])
2572
- async def test_strict_before_started (
2573
- strict : bool , raiser : Callable [[], Awaitable [None ]]
2574
- ) -> None :
2575
- with pytest .raises (BaseExceptionGroup if strict else ValueError ) as exc :
2576
- await _start_raiser (raiser , strict )
2577
- if strict :
2578
- _check_exception (exc )
2562
+ async def raiser (* , task_status : _core .TaskStatus [None ]) -> None :
2563
+ if raise_after_started :
2564
+ task_status .started ()
2565
+ raise raiser_exc
2579
2566
2567
+ async def start_raiser () -> None :
2568
+ try :
2569
+ async with _core .open_nursery (
2570
+ strict_exception_groups = start_raiser_strict
2571
+ ) as nursery :
2572
+ await nursery .start (raiser )
2573
+ except BaseExceptionGroup as exc_group :
2574
+ if start_raiser_strict :
2575
+ # Iff the code using the nursery *forced* it to be strict
2576
+ # (overriding the runner setting) then it may replace the bland
2577
+ # exception group raised by trio with a more specific one (subtype,
2578
+ # different message, etc.).
2579
+ raise BaseExceptionGroup (
2580
+ "start_raiser nursery custom message" , exc_group .exceptions
2581
+ ) from None
2582
+ raise
2580
2583
2581
- # it was only when run from `trio.run` that the double wrapping happened
2582
- @pytest .mark .parametrize ("strict" , [False , True ])
2583
- @pytest .mark .parametrize (
2584
- "raiser" , [raise_before , raise_after_started , raise_custom_exception_group_before ]
2585
- )
2586
- def test_trio_run_strict_before_started (
2587
- strict : bool , raiser : Callable [[], Awaitable [None ]]
2588
- ) -> None :
2589
- expect_group = strict or raiser is raise_custom_exception_group_before
2590
- with pytest .raises (BaseExceptionGroup if expect_group else ValueError ) as exc :
2591
- _core .run (_start_raiser , raiser , strict_exception_groups = strict )
2592
- if expect_group :
2593
- _check_exception (exc )
2584
+ with pytest .raises (BaseException ) as exc_info :
2585
+ _core .run (start_raiser , strict_exception_groups = run_strict )
2586
+
2587
+ if start_raiser_strict or (run_strict and start_raiser_strict is None ):
2588
+ # start_raiser's nursery was strict.
2589
+ assert isinstance (exc_info .value , BaseExceptionGroup )
2590
+ if start_raiser_strict :
2591
+ # start_raiser didn't unknowingly inherit its nursery strictness
2592
+ # from `run`---it explicitly chose for its nursery to be strict.
2593
+ assert exc_info .value .message == "start_raiser nursery custom message"
2594
+ assert len (exc_info .value .exceptions ) == 1
2595
+ should_be_raiser_exc = exc_info .value .exceptions [0 ]
2596
+ else :
2597
+ # start_raiser's nursery was not strict.
2598
+ should_be_raiser_exc = exc_info .value
2599
+ if isinstance (raiser_exc , ValueError ):
2600
+ assert should_be_raiser_exc is raiser_exc
2601
+ else :
2602
+ # Check attributes, not identity, because should_be_raiser_exc may be a
2603
+ # copy of raiser_exc rather than raiser_exc by identity.
2604
+ assert type (should_be_raiser_exc ) == type (raiser_exc )
2605
+ assert should_be_raiser_exc .message == raiser_exc .message
2606
+ assert should_be_raiser_exc .exceptions == raiser_exc .exceptions
0 commit comments