|
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