Skip to content

Commit

Permalink
Unwrap magic exceptions from single-leaf groups
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Oct 31, 2022
1 parent f2d9093 commit db1b214
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
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>`__.
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)
21 changes: 20 additions & 1 deletion pytest_trio/plugin.py
Expand Up @@ -10,6 +10,7 @@
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
Expand Down Expand Up @@ -343,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

0 comments on commit db1b214

Please sign in to comment.