Skip to content

Commit

Permalink
improve raisesgroup code coverage (#13275)
Browse files Browse the repository at this point in the history
* improve raisesgroup code coverage

* fix coverage of a weird branch in python_api, explicitify match in other test

* remove comment
  • Loading branch information
jakkdl authored Mar 7, 2025
1 parent 31d64fc commit a98d093
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 19 deletions.
3 changes: 2 additions & 1 deletion src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,8 @@ def stringify_exception(
HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ())
if sys.version_info < (3, 12) and isinstance(exc, HTTPError):
notes = []
else:
else: # pragma: no cover
# exception not related to above bug, reraise
raise
if not include_subexception_msg and isinstance(exc, BaseExceptionGroup):
message = exc.message
Expand Down
6 changes: 0 additions & 6 deletions src/_pytest/raises_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ def _parse_exc(
self.is_baseexception = True
return cast(type[BaseExcT_1], origin_exc)
else:
# I kinda think this should be a TypeError...
raise ValueError(
f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseExeption]` "
f"are accepted as generic types but got `{exc}`. "
Expand Down Expand Up @@ -424,11 +423,6 @@ def __enter__(self) -> ExceptionInfo[BaseExcT_co_default]:
self.excinfo: ExceptionInfo[BaseExcT_co_default] = ExceptionInfo.for_later()
return self.excinfo

def expected_type(self) -> str:
if self.expected_exceptions == ():
return "BaseException"
return _exception_type_name(self.expected_exceptions)

# TODO: move common code into superclass
def __exit__(
self,
Expand Down
24 changes: 21 additions & 3 deletions testing/python/raises.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ def test_raises_does_not_allow_none(self):
# so we can ignore Mypy telling us that None is invalid.
pytest.raises(expected_exception=None) # type: ignore

# it's unclear if this message is helpful, and if it is, should it trigger more
# liberally? Usually you'd get a TypeError here
def test_raises_false_and_arg(self):
with pytest.raises(
ValueError,
match=wrap_escape(
"Expected an exception type or a tuple of exception types, but got `False`. "
"Raising exceptions is already understood as failing the test, so you don't need "
"any special code to say 'this should never raise an exception'."
),
):
pytest.raises(False, int) # type: ignore[call-overload]

def test_raises_does_not_allow_empty_tuple(self):
with pytest.raises(
ValueError,
Expand Down Expand Up @@ -219,7 +232,7 @@ def __call__(self):
elif method == "with_group":
with pytest.RaisesGroup(ValueError, allow_unwrapped=True):
t()
else:
else: # pragma: no cover
raise AssertionError("bad parametrization")

# ensure both forms of pytest.raises don't leave exceptions in sys.exc_info()
Expand Down Expand Up @@ -337,10 +350,15 @@ def __class__(self):
def test_raises_context_manager_with_kwargs(self):
with pytest.raises(expected_exception=ValueError):
raise ValueError
with pytest.raises(TypeError) as excinfo:
with pytest.raises(
TypeError,
match=wrap_escape(
"Unexpected keyword arguments passed to pytest.raises: foo\n"
"Use context-manager form instead?"
),
):
with pytest.raises(OSError, foo="bar"): # type: ignore[call-overload]
pass
assert "Unexpected keyword arguments" in str(excinfo.value)

def test_expected_exception_is_not_a_baseexception(self) -> None:
with pytest.raises(TypeError) as excinfo:
Expand Down
29 changes: 20 additions & 9 deletions testing/python/raises_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,12 +458,16 @@ def my_check(e: object) -> bool: # pragma: no cover
assert RaisesGroup(ValueError, match="bar").matches(exc.value)


def test_RaisesGroup_matches() -> None:
def test_matches() -> None:
rg = RaisesGroup(ValueError)
assert not rg.matches(None)
assert not rg.matches(ValueError())
assert rg.matches(ExceptionGroup("", (ValueError(),)))

re = RaisesExc(ValueError)
assert not re.matches(None)
assert re.matches(ValueError())


def test_message() -> None:
def check_message(
Expand Down Expand Up @@ -884,11 +888,13 @@ def test_assert_message_nested() -> None:
)


# CI always runs with hypothesis, but this is not a critical test - it overlaps
# with several others
@pytest.mark.skipif(
"hypothesis" in sys.modules,
reason="hypothesis may have monkeypatched _check_repr",
)
def test_check_no_patched_repr() -> None:
def test_check_no_patched_repr() -> None: # pragma: no cover
# We make `_check_repr` monkeypatchable to avoid this very ugly and verbose
# repr. The other tests that use `check` make use of `_check_repr` so they'll
# continue passing in case it is patched - but we have this one test that
Expand Down Expand Up @@ -1288,15 +1294,20 @@ def test_parametrizing_conditional_raisesgroup(
def test_annotated_group() -> None:
# repr depends on if exceptiongroup backport is being used or not
t = repr(ExceptionGroup[ValueError])
fail_msg = wrap_escape(
f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseExeption]` are accepted as generic types but got `{t}`. As `raises` will catch all instances of the specified group regardless of the generic argument specific nested exceptions has to be checked with `RaisesGroup`."
)
msg = "Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseExeption]` are accepted as generic types but got `{}`. As `raises` will catch all instances of the specified group regardless of the generic argument specific nested exceptions has to be checked with `RaisesGroup`."

fail_msg = wrap_escape(msg.format(t))
with pytest.raises(ValueError, match=fail_msg):
with RaisesGroup(ExceptionGroup[ValueError]):
... # pragma: no cover
RaisesGroup(ExceptionGroup[ValueError])
with pytest.raises(ValueError, match=fail_msg):
with RaisesExc(ExceptionGroup[ValueError]):
... # pragma: no cover
RaisesExc(ExceptionGroup[ValueError])
with pytest.raises(
ValueError,
match=wrap_escape(msg.format(repr(BaseExceptionGroup[KeyboardInterrupt]))),
):
with RaisesExc(BaseExceptionGroup[KeyboardInterrupt]):
raise BaseExceptionGroup("", [KeyboardInterrupt()])

with RaisesGroup(ExceptionGroup[Exception]):
raise ExceptionGroup(
"", [ExceptionGroup("", [ValueError(), ValueError(), ValueError()])]
Expand Down

0 comments on commit a98d093

Please sign in to comment.