Skip to content

Commit

Permalink
Merge pull request #130 from Zac-HD/use-exceptiongroup
Browse files Browse the repository at this point in the history
Use `ExceptionGroup` and fix magic-exception handling
  • Loading branch information
Zac-HD committed Nov 1, 2022
2 parents cbd6197 + 6fbe63e commit 2ba99e6
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 13 deletions.
10 changes: 10 additions & 0 deletions newsfragments/104.feature.rst
@@ -0,0 +1,10 @@
If a test raises an ``ExceptionGroup`` (or nested ``ExceptionGroup``\ s) with only
a single 'leaf' exception from ``pytest.xfail()`` or ``pytest.skip()``\ , we now
unwrap it to have the desired effect on Pytest. ``ExceptionGroup``\ s with two or
more leaf exceptions, even of the same type, are not changed and will be treated
as ordinary test failures.

See `pytest-dev/pytest#9680 <https://github.com/pytest-dev/pytest/issues/9680>`__
for design discussion. This feature is particularly useful if you've enabled
`the new strict_exception_groups=True option
<https://trio.readthedocs.io/en/stable/reference-core.html#strict-versus-loose-exceptiongroup-semantics>`__.
4 changes: 4 additions & 0 deletions newsfragments/128.misc.rst
@@ -0,0 +1,4 @@
Trio 0.22.0 deprecated ``MultiError`` in favor of the standard-library
(or `backported <https://pypi.org/project/exceptiongroup/>`__) ``ExceptionGroup``
type; ``pytest-trio`` now uses ``ExceptionGroup`` and therefore requires
Trio 0.22.0 or later.
5 changes: 5 additions & 0 deletions pytest.ini
@@ -1,2 +1,7 @@
[pytest]
addopts = -ra -v --pyargs pytest_trio --verbose --cov
filterwarnings =
error
default::pytest.PytestAssertRewriteWarning
default::pytest.PytestDeprecationWarning
default::pytest.PytestUnraisableExceptionWarning
4 changes: 3 additions & 1 deletion pytest_trio/_tests/test_async_yield_fixture.py
Expand Up @@ -300,4 +300,6 @@ def test_after():
result = testdir.runpytest()

result.assert_outcomes(failed=1, passed=2)
result.stdout.re_match_lines([r"E\W+RuntimeError: Crash during fixture teardown"])
result.stdout.re_match_lines(
[r"(E\W+| +\| )RuntimeError: Crash during fixture teardown"]
)
49 changes: 49 additions & 0 deletions pytest_trio/_tests/test_basic.py
Expand Up @@ -74,3 +74,52 @@ def test_invalid():
result = testdir.runpytest()

result.assert_outcomes(errors=1)


def test_skip_and_xfail(testdir):

testdir.makepyfile(
"""
import functools
import pytest
import trio
trio.run = functools.partial(trio.run, strict_exception_groups=True)
@pytest.mark.trio
async def test_xfail():
pytest.xfail()
@pytest.mark.trio
async def test_skip():
pytest.skip()
async def callback(fn):
fn()
async def fail():
raise RuntimeError
@pytest.mark.trio
async def test_xfail_and_fail():
async with trio.open_nursery() as nursery:
nursery.start_soon(callback, pytest.xfail)
nursery.start_soon(fail)
@pytest.mark.trio
async def test_skip_and_fail():
async with trio.open_nursery() as nursery:
nursery.start_soon(callback, pytest.skip)
nursery.start_soon(fail)
@pytest.mark.trio
async def test_xfail_and_skip():
async with trio.open_nursery() as nursery:
nursery.start_soon(callback, pytest.skip)
nursery.start_soon(callback, pytest.xfail)
"""
)

result = testdir.runpytest("-s")

result.assert_outcomes(skipped=1, xfailed=1, failed=3)
4 changes: 3 additions & 1 deletion pytest_trio/_tests/test_sync_fixture.py
Expand Up @@ -139,4 +139,6 @@ def test_after():
result = testdir.runpytest()

result.assert_outcomes(failed=1, passed=2)
result.stdout.re_match_lines([r"E\W+RuntimeError: Crash during fixture teardown"])
result.stdout.re_match_lines(
[r"(E\W+| +\| )RuntimeError: Crash during fixture teardown"]
)
41 changes: 30 additions & 11 deletions pytest_trio/plugin.py
@@ -1,6 +1,6 @@
"""pytest-trio implementation."""
import sys
from functools import wraps, partial
from traceback import format_exception
from collections.abc import Coroutine, Generator
from contextlib import asynccontextmanager
from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction
Expand All @@ -10,6 +10,10 @@
import trio
from trio.abc import Clock, Instrument
from trio.testing import MockClock
from _pytest.outcomes import Skipped, XFailed

if sys.version_info[:2] < (3, 11):
from exceptiongroup import BaseExceptionGroup

################################################################
# Basic setup
Expand Down Expand Up @@ -52,13 +56,6 @@ def pytest_configure(config):
)


@pytest.hookimpl(tryfirst=True)
def pytest_exception_interact(node, call, report):
if issubclass(call.excinfo.type, trio.MultiError):
# TODO: not really elegant (pytest cannot output color with this hack)
report.longrepr = "".join(format_exception(*call.excinfo._excinfo))


################################################################
# Core support for trio fixtures and trio tests
################################################################
Expand Down Expand Up @@ -347,7 +344,25 @@ def wrapper(**kwargs):
f"Expected at most one Clock in kwargs, got {clocks!r}"
)
instruments = [i for i in kwargs.values() if isinstance(i, Instrument)]
return run(partial(fn, **kwargs), clock=clock, instruments=instruments)
try:
return run(partial(fn, **kwargs), clock=clock, instruments=instruments)
except BaseExceptionGroup as eg:
queue = [eg]
leaves = []
while queue:
ex = queue.pop()
if isinstance(ex, BaseExceptionGroup):
queue.extend(ex.exceptions)
else:
leaves.append(ex)
if len(leaves) == 1:
if isinstance(leaves[0], XFailed):
pytest.xfail()
if isinstance(leaves[0], Skipped):
pytest.skip()
# Since our leaf exceptions don't consist of exactly one 'magic'
# skipped or xfailed exception, re-raise the whole group.
raise

return wrapper

Expand Down Expand Up @@ -407,8 +422,12 @@ async def _bootstrap_fixtures_and_run_test(**kwargs):
)
)

if test_ctx.error_list:
raise trio.MultiError(test_ctx.error_list)
if len(test_ctx.error_list) == 1:
raise test_ctx.error_list[0]
elif test_ctx.error_list:
raise BaseExceptionGroup(
"errors in async test and trio fixtures", test_ctx.error_list
)

_bootstrap_fixtures_and_run_test._trio_test_runner_wrapped = True
return _bootstrap_fixtures_and_run_test
Expand Down

0 comments on commit 2ba99e6

Please sign in to comment.