|
1 | 1 | """pytest-trio implementation."""
|
| 2 | +import sys |
2 | 3 | from functools import wraps, partial
|
3 |
| -from traceback import format_exception |
4 | 4 | from collections.abc import Coroutine, Generator
|
5 | 5 | from contextlib import asynccontextmanager
|
6 | 6 | from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction
|
|
10 | 10 | import trio
|
11 | 11 | from trio.abc import Clock, Instrument
|
12 | 12 | from trio.testing import MockClock
|
| 13 | +from _pytest.outcomes import Skipped, XFailed |
| 14 | + |
| 15 | +if sys.version_info[:2] < (3, 11): |
| 16 | + from exceptiongroup import BaseExceptionGroup |
13 | 17 |
|
14 | 18 | ################################################################
|
15 | 19 | # Basic setup
|
@@ -52,13 +56,6 @@ def pytest_configure(config):
|
52 | 56 | )
|
53 | 57 |
|
54 | 58 |
|
55 |
| -@pytest.hookimpl(tryfirst=True) |
56 |
| -def pytest_exception_interact(node, call, report): |
57 |
| - if issubclass(call.excinfo.type, trio.MultiError): |
58 |
| - # TODO: not really elegant (pytest cannot output color with this hack) |
59 |
| - report.longrepr = "".join(format_exception(*call.excinfo._excinfo)) |
60 |
| - |
61 |
| - |
62 | 59 | ################################################################
|
63 | 60 | # Core support for trio fixtures and trio tests
|
64 | 61 | ################################################################
|
@@ -347,7 +344,25 @@ def wrapper(**kwargs):
|
347 | 344 | f"Expected at most one Clock in kwargs, got {clocks!r}"
|
348 | 345 | )
|
349 | 346 | instruments = [i for i in kwargs.values() if isinstance(i, Instrument)]
|
350 |
| - return run(partial(fn, **kwargs), clock=clock, instruments=instruments) |
| 347 | + try: |
| 348 | + return run(partial(fn, **kwargs), clock=clock, instruments=instruments) |
| 349 | + except BaseExceptionGroup as eg: |
| 350 | + queue = [eg] |
| 351 | + leaves = [] |
| 352 | + while queue: |
| 353 | + ex = queue.pop() |
| 354 | + if isinstance(ex, BaseExceptionGroup): |
| 355 | + queue.extend(ex.exceptions) |
| 356 | + else: |
| 357 | + leaves.append(ex) |
| 358 | + if len(leaves) == 1: |
| 359 | + if isinstance(leaves[0], XFailed): |
| 360 | + pytest.xfail() |
| 361 | + if isinstance(leaves[0], Skipped): |
| 362 | + pytest.skip() |
| 363 | + # Since our leaf exceptions don't consist of exactly one 'magic' |
| 364 | + # skipped or xfailed exception, re-raise the whole group. |
| 365 | + raise |
351 | 366 |
|
352 | 367 | return wrapper
|
353 | 368 |
|
@@ -407,8 +422,12 @@ async def _bootstrap_fixtures_and_run_test(**kwargs):
|
407 | 422 | )
|
408 | 423 | )
|
409 | 424 |
|
410 |
| - if test_ctx.error_list: |
411 |
| - raise trio.MultiError(test_ctx.error_list) |
| 425 | + if len(test_ctx.error_list) == 1: |
| 426 | + raise test_ctx.error_list[0] |
| 427 | + elif test_ctx.error_list: |
| 428 | + raise BaseExceptionGroup( |
| 429 | + "errors in async test and trio fixtures", test_ctx.error_list |
| 430 | + ) |
412 | 431 |
|
413 | 432 | _bootstrap_fixtures_and_run_test._trio_test_runner_wrapped = True
|
414 | 433 | return _bootstrap_fixtures_and_run_test
|
|
0 commit comments