Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to testing.RaisesGroup for catching unwrapped exceptions #2989

Merged
merged 30 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4497b3d
Add support to testing.RaisesGroup for catching unwrapped exceptions …
jakkdl Apr 15, 2024
62c0ea0
Merge branch 'master' into looser_excgroups
jakkdl Apr 15, 2024
f50f808
add test case to get full coverage
jakkdl Apr 15, 2024
f5c755f
fix type error by adding covariance to typevar
jakkdl Apr 16, 2024
cf64533
rewrite RaisesGroup docstring
jakkdl Apr 16, 2024
38c950b
Merge branch 'master' into looser_excgroups
jakkdl Apr 16, 2024
c506a89
Work around +E typevar issue in docs for _raises_group
TeamSpen210 Apr 17, 2024
0bff4e6
Fix docs issue with type property in _ExceptionInfo
TeamSpen210 Apr 17, 2024
62246f1
Apply suggestions from code review
jakkdl Apr 18, 2024
3a56911
split 'strict' into 'flatten_subgroups' and 'allow_unwrapped', fix bu…
jakkdl Apr 18, 2024
cc5d980
add deprecation of strict, add newsfragments
jakkdl Apr 18, 2024
1a89cd7
the great flattening ...
jakkdl Apr 18, 2024
5af79ac
kinda weird that verifytypes thought this was ambiguous
jakkdl Apr 18, 2024
8f72967
update newsfragments after review
jakkdl Apr 18, 2024
ff3e5fc
update docstring to match new parameter names
jakkdl Apr 18, 2024
2df4c1c
sphinx does not like `...`s
jakkdl Apr 18, 2024
c4dbb78
moar newsfragment improvements
jakkdl Apr 18, 2024
b0d3408
bump exceptiongroup to 1.2.1
jakkdl Apr 22, 2024
36e757a
minor test changes after review
jakkdl Apr 23, 2024
5179221
fix ^$ matching on exceptiongroups
jakkdl Apr 24, 2024
ff5d4eb
Merge branch 'master' into looser_excgroups
jakkdl Apr 24, 2024
8b7aefc
use warn_deprecated instead of DeprecationWarning, disallow allow_unw…
jakkdl May 1, 2024
2183e70
Merge remote-tracking branch 'origin/master' into looser_excgroups
jakkdl May 1, 2024
8c3f1f6
mention $ in bugfix newsfragment, fix test
jakkdl May 1, 2024
f736120
fix coverage
jakkdl May 3, 2024
0c7591a
add test case for nested exceptiongroup + allow_unwrapped
jakkdl May 14, 2024
553df3d
add signature overloads for RaisesGroup to raise type errors when doi…
jakkdl May 14, 2024
04636ac
add pytest.deprecated_call() test
jakkdl May 16, 2024
6f9a8a1
add type tests for narrowing of check argument
jakkdl May 16, 2024
6ef442b
Merge branch 'master' into looser_excgroups
jakkdl May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/xxx.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:ref:`testing.RaisesGroup` can now catch unwrapped exceptions with ``strict=False``, similar to ``except*``.
30 changes: 27 additions & 3 deletions src/trio/_tests/test_testing_raisesgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,33 @@ def test_raises_group() -> None:
):
RaisesGroup(RaisesGroup(ValueError), strict=False)

# currently not fully identical in behaviour to expect*, which would also catch an unwrapped exception
with pytest.raises(ValueError, match="^value error text$"):
with RaisesGroup(ValueError, strict=False):

def test_catch_unwrapped_exceptions() -> None:
# Catches lone exceptions with strict=False
# just as expect* would
jakkdl marked this conversation as resolved.
Show resolved Hide resolved
with RaisesGroup(ValueError, strict=False):
raise ValueError

with pytest.raises(ValueError, match="foo"):
# This not being caught is perhaps confusing for users used to pytest.raises
with RaisesGroup(SyntaxError, ValueError, strict=False):
raise ValueError("foo")
with pytest.raises(ExceptionGroup, match="foo"):
# but if we made that work, then it would be unexpected that wrapping the exception
# does not get caught
with RaisesGroup(SyntaxError, ValueError, strict=False):
raise ExceptionGroup("foo", (ValueError(),))
# and changing *that* is not really an option, as the entire point of RaisesGroup is
# to verify *all* exceptions specified are there and *no others*.
# Users are required to instead do
with RaisesGroup(
Matcher(check=lambda x: isinstance(x, (SyntaxError, ValueError))), strict=False
):
raise ValueError("foo")

# with strict=True (default) it will not be caught
with pytest.raises(ValueError, match="value error text"):
jakkdl marked this conversation as resolved.
Show resolved Hide resolved
with RaisesGroup(ValueError):
raise ValueError("value error text")


Expand Down
25 changes: 24 additions & 1 deletion src/trio/_tests/type_tests/raisesgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ def check_filenotfound(exc: FileNotFoundError) -> bool:
Matcher(exception_type=ValueError)
Matcher(match="regex")
Matcher(check=check_exc)
Matcher(check=check_filenotfound) # type: ignore
Matcher(ValueError, match="regex")
Matcher(FileNotFoundError, check=check_filenotfound)
Matcher(check=check_filenotfound) # type: ignore # not narrowed
CoolCat467 marked this conversation as resolved.
Show resolved Hide resolved
Matcher(match="regex", check=check_exc)
Matcher(FileNotFoundError, match="regex", check=check_filenotfound)

Expand Down Expand Up @@ -134,3 +134,26 @@ def check_nested_raisesgroups_matches() -> None:
# has the same problems as check_nested_raisesgroups_contextmanager
if RaisesGroup(RaisesGroup(ValueError)).matches(exc):
assert_type(exc, BaseExceptionGroup[RaisesGroup[ValueError]])


def check_multiple_exceptions_1() -> None:
a = RaisesGroup(ValueError, ValueError)
b = RaisesGroup(Matcher(ValueError), Matcher(ValueError))
c = RaisesGroup(ValueError, Matcher(ValueError))

d: BaseExceptionGroup[ValueError]
d = a
d = b
d = c
assert d


def check_multiple_exceptions_2() -> None:
# "Cannot infer argument 1 of RaisesGroup"
# if removing overloads from Matcher then pyright stops warning, but mypy still
# complains
jakkdl marked this conversation as resolved.
Show resolved Hide resolved
RaisesGroup(Matcher(ValueError), Matcher(TypeError)) # type: ignore
RaisesGroup(Matcher(ValueError), TypeError) # type: ignore
# so it requires explicit type
RaisesGroup[Exception](Matcher(ValueError), Matcher(TypeError))
RaisesGroup[Exception](Matcher(ValueError), TypeError)
14 changes: 12 additions & 2 deletions src/trio/testing/_raises_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,9 @@ class RaisesGroup(ContextManager[ExceptionInfo[BaseExceptionGroup[E]]], SuperCla
This works similar to ``pytest.raises``, and a version of it will hopefully be added upstream, after which this can be deprecated and removed. See https://github.com/pytest-dev/pytest/issues/11538


This differs from :ref:`except* <except_star>` in that all specified exceptions must be present, *and no others*. It will similarly not catch exceptions *not* wrapped in an exceptiongroup.
If you don't care for the nesting level of the exceptions you can pass ``strict=False``.
This differs from :ref:`except* <except_star>` in that all specified exceptions must be present, *and no others*. It will by default not catch exceptions *not* wrapped in an exceptiongroup.
belm0 marked this conversation as resolved.
Show resolved Hide resolved
To match :ref:`except* <except_star>` you can pass ``strict=False``. This will make it ignore the nesting level, and also catch an exception not wrapped in an ExceptionGroup.
If you want to catch unwrapped exceptions and expect one of several different exceptions you need to use a :ref:`Matcher` object.
It currently does not care about the order of the exceptions, so ``RaisesGroups(ValueError, TypeError)`` is equivalent to ``RaisesGroups(TypeError, ValueError)``.

This class is not as polished as ``pytest.raises``, and is currently not as helpful in e.g. printing diffs when strings don't match, suggesting you use ``re.escape``, etc.
Expand Down Expand Up @@ -383,6 +384,15 @@ def matches(
# maybe have a list of strings logging failed matches, that __exit__ can
# recursively step through and print on a failing match.
if not isinstance(exc_val, BaseExceptionGroup):
if not self.strict and len(self.expected_exceptions) == 1:
exp_exc = self.expected_exceptions[0]
if isinstance(exp_exc, Matcher) and exp_exc.matches(exc_val):
return True
if isinstance(exp_exc, type) and isinstance(exc_val, exp_exc):
return True
# Consider printing a helpful message on unwrapped exception, strict=False and
# `len(self.expected_exceptions) > 1`; since they might expect the exceptions
# to be 'or'd like with `pytest.raises`.
TeamSpen210 marked this conversation as resolved.
Show resolved Hide resolved
return False
if len(exc_val.exceptions) != len(self.expected_exceptions):
return False
Expand Down