pare down implementation to mimimum viable, add assert_matches that has assertions with descriptive outputs for why a match failed
This commit is contained in:
parent
2023fa7de8
commit
43f62eb6b5
|
@ -1,6 +1,5 @@
|
|||
import math
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Collection
|
||||
from collections.abc import Sized
|
||||
|
@ -12,7 +11,6 @@ from typing import Callable
|
|||
from typing import cast
|
||||
from typing import ContextManager
|
||||
from typing import final
|
||||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
|
@ -995,66 +993,39 @@ def raises( # noqa: F811
|
|||
raises.Exception = fail.Exception # type: ignore
|
||||
|
||||
|
||||
class Matcher(Generic[E]):
|
||||
def __init__(
|
||||
self,
|
||||
exception_type: Optional[Type[E]] = None,
|
||||
match: Optional[Union[str, Pattern[str]]] = None,
|
||||
check: Optional[Callable[[E], bool]] = None,
|
||||
):
|
||||
if exception_type is None and match is None and check is None:
|
||||
raise ValueError("You must specify at least one parameter to match on.")
|
||||
self.exception_type = exception_type
|
||||
self.match = match
|
||||
self.check = check
|
||||
|
||||
def matches(self, exception: E) -> "TypeGuard[E]":
|
||||
if self.exception_type is not None and not isinstance(
|
||||
exception, self.exception_type
|
||||
):
|
||||
return False
|
||||
if self.match is not None and not re.search(self.match, str(exception)):
|
||||
return False
|
||||
if self.check is not None and not self.check(exception):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
SuperClass = BaseExceptionGroup
|
||||
else:
|
||||
SuperClass = Generic
|
||||
|
||||
|
||||
@final
|
||||
class RaisesGroup(
|
||||
ContextManager[_pytest._code.ExceptionInfo[BaseExceptionGroup[E]]], SuperClass[E]
|
||||
):
|
||||
# My_T = TypeVar("My_T", bound=Union[Type[E], Matcher[E], "RaisesGroup[E]"])
|
||||
class RaisesGroup(ContextManager[_pytest._code.ExceptionInfo[BaseExceptionGroup[E]]]):
|
||||
"""Helper for catching exceptions wrapped in an ExceptionGroup.
|
||||
|
||||
Similar to pytest.raises, except:
|
||||
* It requires that the exception is inside an exceptiongroup
|
||||
* It is only able to be used as a contextmanager
|
||||
* Due to the above, is not split into a caller function and a cm class
|
||||
Similar to trio.RaisesGroup, except:
|
||||
* does not handle multiple levels of nested groups.
|
||||
* does not have trio.Matcher, to add matching on the sub-exception
|
||||
* does not handle multiple exceptions in the exceptiongroup.
|
||||
|
||||
TODO: copy over docstring example usage from trio.RaisesGroup
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
exceptions: Union[Type[E], Matcher[E], E],
|
||||
*args: Union[Type[E], Matcher[E], E],
|
||||
strict: bool = True,
|
||||
match: Optional[Union[str, Pattern[str]]] = None,
|
||||
exception: Type[E],
|
||||
check: Optional[Callable[[BaseExceptionGroup[E]], bool]] = None,
|
||||
):
|
||||
# could add parameter `notes: Optional[Tuple[str, Pattern[str]]] = None`
|
||||
self.expected_exceptions = (exceptions, *args)
|
||||
self.strict = strict
|
||||
self.match_expr = match
|
||||
self.message = f"DID NOT RAISE ExceptionGroup{repr(self.expected_exceptions)}" # type: ignore[misc]
|
||||
# copied from raises() above
|
||||
if not isinstance(exception, type) or not issubclass(exception, BaseException):
|
||||
msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable]
|
||||
not_a = (
|
||||
exception.__name__
|
||||
if isinstance(exception, type)
|
||||
else type(exception).__name__
|
||||
)
|
||||
raise TypeError(msg.format(not_a))
|
||||
|
||||
for exc in self.expected_exceptions:
|
||||
if not isinstance(exc, (Matcher, RaisesGroup)) and not (
|
||||
isinstance(exc, type) and issubclass(exc, BaseException)
|
||||
):
|
||||
raise ValueError(
|
||||
"Invalid argument {exc} must be exception type, Matcher, or RaisesGroup."
|
||||
)
|
||||
if isinstance(exc, RaisesGroup) and not strict: # type: ignore[unreachable]
|
||||
raise ValueError(
|
||||
"You cannot specify a nested structure inside a RaisesGroup with strict=False"
|
||||
)
|
||||
self.exception = exception
|
||||
self.check = check
|
||||
|
||||
def __enter__(self) -> _pytest._code.ExceptionInfo[BaseExceptionGroup[E]]:
|
||||
self.excinfo: _pytest._code.ExceptionInfo[
|
||||
|
@ -1078,41 +1049,33 @@ class RaisesGroup(
|
|||
self,
|
||||
exc_val: Optional[BaseException],
|
||||
) -> "TypeGuard[BaseExceptionGroup[E]]":
|
||||
if exc_val is None:
|
||||
return False
|
||||
if not isinstance(exc_val, BaseExceptionGroup):
|
||||
return False
|
||||
if not len(exc_val.exceptions) == len(self.expected_exceptions):
|
||||
return False
|
||||
remaining_exceptions = list(self.expected_exceptions)
|
||||
actual_exceptions: Iterable[BaseException] = exc_val.exceptions
|
||||
if not self.strict:
|
||||
actual_exceptions = self._unroll_exceptions(actual_exceptions)
|
||||
return (
|
||||
exc_val is not None
|
||||
and isinstance(exc_val, BaseExceptionGroup)
|
||||
and len(exc_val.exceptions) == 1
|
||||
and isinstance(exc_val.exceptions[0], self.exception)
|
||||
and (self.check is None or self.check(exc_val))
|
||||
)
|
||||
|
||||
def assert_matches(
|
||||
self,
|
||||
exc_val: Optional[BaseException],
|
||||
) -> "TypeGuard[BaseExceptionGroup[E]]":
|
||||
assert (
|
||||
exc_val is not None
|
||||
), "Internal Error: exc_type is not None but exc_val is"
|
||||
assert isinstance(
|
||||
exc_val, BaseExceptionGroup
|
||||
), f"Expected an ExceptionGroup, not {type(exc_val)}"
|
||||
assert (
|
||||
len(exc_val.exceptions) == 1
|
||||
), f"Wrong number of exceptions: got {len(exc_val.exceptions)}, expected 1."
|
||||
assert isinstance(
|
||||
exc_val.exceptions[0], self.exception
|
||||
), f"Wrong type in group: got {type(exc_val.exceptions[0])}, expected {self.exception}"
|
||||
if self.check is not None:
|
||||
assert self.check(exc_val), f"Check failed on {repr(exc_val)}."
|
||||
|
||||
# it should be possible to get RaisesGroup.matches typed so as not to
|
||||
# need these type: ignores, but I'm not sure that's possible while also having it
|
||||
# transparent for the end user.
|
||||
for e in actual_exceptions:
|
||||
for rem_e in remaining_exceptions:
|
||||
# TODO: how to print string diff on mismatch?
|
||||
# Probably accumulate them, and then if fail, print them
|
||||
# Further QoL would be to print how the exception structure differs on non-match
|
||||
if (
|
||||
(isinstance(rem_e, type) and isinstance(e, rem_e))
|
||||
or (
|
||||
isinstance(e, BaseExceptionGroup)
|
||||
and isinstance(rem_e, RaisesGroup)
|
||||
and rem_e.matches(e)
|
||||
)
|
||||
or (
|
||||
isinstance(rem_e, Matcher)
|
||||
and rem_e.matches(e) # type: ignore[arg-type]
|
||||
)
|
||||
):
|
||||
remaining_exceptions.remove(rem_e) # type: ignore[arg-type]
|
||||
break
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __exit__(
|
||||
|
@ -1123,11 +1086,10 @@ class RaisesGroup(
|
|||
) -> bool:
|
||||
__tracebackhide__ = True
|
||||
if exc_type is None:
|
||||
fail(self.message)
|
||||
assert self.excinfo is not None
|
||||
fail("DID NOT RAISE ANY EXCEPTION, expected " + self.expected_type())
|
||||
assert self.excinfo is not None, "__exit__ without __enter__"
|
||||
|
||||
if not self.matches(exc_val):
|
||||
return False
|
||||
self.assert_matches(exc_val)
|
||||
|
||||
# Cast to narrow the exception type now that it's verified.
|
||||
exc_info = cast(
|
||||
|
@ -1135,13 +1097,14 @@ class RaisesGroup(
|
|||
(exc_type, exc_val, exc_tb),
|
||||
)
|
||||
self.excinfo.fill_unfilled(exc_info)
|
||||
if self.match_expr is not None:
|
||||
self.excinfo.match(self.match_expr)
|
||||
return True
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# TODO: [Base]ExceptionGroup
|
||||
return f"ExceptionGroup{self.expected_exceptions}"
|
||||
def expected_type(self) -> str:
|
||||
if not issubclass(self.exception, Exception):
|
||||
base = "Base"
|
||||
else:
|
||||
base = ""
|
||||
return f"{base}ExceptionGroup({self.exception})"
|
||||
|
||||
|
||||
@final
|
||||
|
|
|
@ -57,6 +57,7 @@ from _pytest.python import Module
|
|||
from _pytest.python import Package
|
||||
from _pytest.python_api import approx
|
||||
from _pytest.python_api import raises
|
||||
from _pytest.python_api import RaisesGroup
|
||||
from _pytest.recwarn import deprecated_call
|
||||
from _pytest.recwarn import WarningsRecorder
|
||||
from _pytest.recwarn import warns
|
||||
|
@ -146,6 +147,7 @@ __all__ = [
|
|||
"PytestUnraisableExceptionWarning",
|
||||
"PytestWarning",
|
||||
"raises",
|
||||
"RaisesGroup",
|
||||
"RecordedHookCall",
|
||||
"register_assert_rewrite",
|
||||
"RunResult",
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import re
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from _pytest.python_api import Matcher
|
||||
from _pytest.python_api import RaisesGroup
|
||||
|
||||
# TODO: make a public export
|
||||
from _pytest.outcomes import Failed
|
||||
from pytest import RaisesGroup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import assert_type
|
||||
|
@ -14,237 +13,137 @@ if sys.version_info < (3, 11):
|
|||
from exceptiongroup import ExceptionGroup
|
||||
|
||||
|
||||
class TestRaisesGroup:
|
||||
def test_raises_group(self) -> None:
|
||||
def test_raises_group() -> None:
|
||||
# wrong type to constructor
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="^Invalid argument {exc} must be exception type, Matcher, or RaisesGroup.$",
|
||||
TypeError,
|
||||
match="^expected exception must be a BaseException type, not ValueError$",
|
||||
):
|
||||
RaisesGroup(ValueError())
|
||||
RaisesGroup(ValueError()) # type: ignore[arg-type]
|
||||
|
||||
# working example
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("foo", (ValueError(),))
|
||||
|
||||
with RaisesGroup(SyntaxError):
|
||||
with RaisesGroup(ValueError, check=lambda x: True):
|
||||
raise ExceptionGroup("foo", (ValueError(),))
|
||||
|
||||
# wrong subexception
|
||||
with pytest.raises(
|
||||
AssertionError,
|
||||
match="Wrong type in group: got <class 'SyntaxError'>, expected <class 'ValueError'>",
|
||||
):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("foo", (SyntaxError(),))
|
||||
|
||||
# multiple exceptions
|
||||
with RaisesGroup(ValueError, SyntaxError):
|
||||
raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
|
||||
|
||||
# order doesn't matter
|
||||
with RaisesGroup(SyntaxError, ValueError):
|
||||
raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
|
||||
|
||||
# nested exceptions
|
||||
with RaisesGroup(RaisesGroup(ValueError)):
|
||||
raise ExceptionGroup("foo", (ExceptionGroup("bar", (ValueError(),)),))
|
||||
|
||||
with RaisesGroup(
|
||||
SyntaxError,
|
||||
RaisesGroup(ValueError),
|
||||
RaisesGroup(RuntimeError),
|
||||
):
|
||||
raise ExceptionGroup(
|
||||
"foo",
|
||||
(
|
||||
SyntaxError(),
|
||||
ExceptionGroup("bar", (ValueError(),)),
|
||||
ExceptionGroup("", (RuntimeError(),)),
|
||||
),
|
||||
)
|
||||
|
||||
# will error if there's excess exceptions
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with pytest.raises(
|
||||
AssertionError, match="Wrong number of exceptions: got 2, expected 1"
|
||||
):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("", (ValueError(), ValueError()))
|
||||
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("", (RuntimeError(), ValueError()))
|
||||
|
||||
# will error if there's missing exceptions
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError, ValueError):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError, SyntaxError):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
|
||||
# loose semantics, as with expect*
|
||||
with RaisesGroup(ValueError, strict=False):
|
||||
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
|
||||
|
||||
# mixed loose is possible if you want it to be at least N deep
|
||||
with RaisesGroup(RaisesGroup(ValueError, strict=True)):
|
||||
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
|
||||
with RaisesGroup(RaisesGroup(ValueError, strict=False)):
|
||||
raise ExceptionGroup(
|
||||
"", (ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)),)
|
||||
)
|
||||
|
||||
# but not the other way around
|
||||
# double nested exceptions is not (currently) supported (contrary to expect*)
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="^You cannot specify a nested structure inside a RaisesGroup with strict=False$",
|
||||
AssertionError,
|
||||
match="Wrong type in group: got <class '(exceptiongroup.)?ExceptionGroup'>, expected <class 'ValueError'>",
|
||||
):
|
||||
RaisesGroup(RaisesGroup(ValueError), strict=False)
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
|
||||
|
||||
# currently not fully identical in behaviour to expect*, which would also catch an unwrapped exception
|
||||
with pytest.raises(ValueError):
|
||||
with RaisesGroup(ValueError, strict=False):
|
||||
# you'd need to write
|
||||
with RaisesGroup(ExceptionGroup) as excinfo:
|
||||
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
|
||||
RaisesGroup(ValueError).assert_matches(excinfo.value.exceptions[0])
|
||||
|
||||
# unwrapped exceptions are not accepted (contrary to expect*)
|
||||
with pytest.raises(
|
||||
AssertionError, match="Expected an ExceptionGroup, not <class 'ValueError'."
|
||||
):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ValueError
|
||||
|
||||
def test_match(self) -> None:
|
||||
# supports match string
|
||||
with RaisesGroup(ValueError, match="bar"):
|
||||
raise ExceptionGroup("bar", (ValueError(),))
|
||||
with pytest.raises(
|
||||
AssertionError,
|
||||
match=re.escape("Check failed on ExceptionGroup('foo', (ValueError(),))."),
|
||||
):
|
||||
with RaisesGroup(ValueError, check=lambda x: False):
|
||||
raise ExceptionGroup("foo", (ValueError(),))
|
||||
|
||||
try:
|
||||
with RaisesGroup(ValueError, match="foo"):
|
||||
raise ExceptionGroup("bar", (ValueError(),))
|
||||
except AssertionError as e:
|
||||
assert str(e).startswith("Regex pattern did not match.")
|
||||
else:
|
||||
raise AssertionError("Expected pytest.raises.Exception")
|
||||
|
||||
def test_RaisesGroup_matches(self) -> None:
|
||||
def test_RaisesGroup_matches() -> None:
|
||||
eeg = RaisesGroup(ValueError)
|
||||
# exc_val is None
|
||||
assert not eeg.matches(None)
|
||||
# exc_val is not an exceptiongroup
|
||||
assert not eeg.matches(ValueError())
|
||||
# wrong length
|
||||
assert not eeg.matches(ExceptionGroup("", (ValueError(), ValueError())))
|
||||
# wrong type
|
||||
assert not eeg.matches(ExceptionGroup("", (TypeError(),)))
|
||||
# check fails
|
||||
assert not RaisesGroup(ValueError, check=lambda _: False).matches(
|
||||
ExceptionGroup("", (ValueError(),))
|
||||
)
|
||||
# success
|
||||
assert eeg.matches(ExceptionGroup("", (ValueError(),)))
|
||||
|
||||
def test_message(self) -> None:
|
||||
try:
|
||||
|
||||
def test_RaisesGroup_assert_matches() -> None:
|
||||
"""Check direct use of RaisesGroup.assert_matches, without a context manager"""
|
||||
eeg = RaisesGroup(ValueError)
|
||||
with pytest.raises(AssertionError):
|
||||
eeg.assert_matches(None)
|
||||
with pytest.raises(AssertionError):
|
||||
eeg.assert_matches(ValueError())
|
||||
eeg.assert_matches(ExceptionGroup("", (ValueError(),)))
|
||||
|
||||
|
||||
def test_message() -> None:
|
||||
with pytest.raises(
|
||||
Failed,
|
||||
match=re.escape(
|
||||
f"DID NOT RAISE ANY EXCEPTION, expected ExceptionGroup({repr(ValueError)})"
|
||||
),
|
||||
):
|
||||
with RaisesGroup(ValueError):
|
||||
...
|
||||
except pytest.fail.Exception as e:
|
||||
assert e.msg == f"DID NOT RAISE ExceptionGroup({repr(ValueError)},)"
|
||||
else:
|
||||
assert False, "Expected pytest.raises.Exception"
|
||||
try:
|
||||
with RaisesGroup(RaisesGroup(ValueError)):
|
||||
...
|
||||
except pytest.fail.Exception as e:
|
||||
assert (
|
||||
e.msg
|
||||
== f"DID NOT RAISE ExceptionGroup(ExceptionGroup({repr(ValueError)},),)"
|
||||
)
|
||||
else:
|
||||
assert False, "Expected pytest.raises.Exception"
|
||||
|
||||
def test_matcher(self) -> None:
|
||||
with pytest.raises(
|
||||
ValueError, match="^You must specify at least one parameter to match on.$"
|
||||
Failed,
|
||||
match=re.escape(
|
||||
f"DID NOT RAISE ANY EXCEPTION, expected BaseExceptionGroup({repr(KeyboardInterrupt)})"
|
||||
),
|
||||
):
|
||||
Matcher()
|
||||
with RaisesGroup(KeyboardInterrupt):
|
||||
...
|
||||
|
||||
with RaisesGroup(Matcher(ValueError)):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
try:
|
||||
with RaisesGroup(Matcher(TypeError)):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
except ExceptionGroup:
|
||||
pass
|
||||
else:
|
||||
assert False, "Expected pytest.raises.Exception"
|
||||
|
||||
def test_matcher_match(self) -> None:
|
||||
with RaisesGroup(Matcher(ValueError, "foo")):
|
||||
raise ExceptionGroup("", (ValueError("foo"),))
|
||||
try:
|
||||
with RaisesGroup(Matcher(ValueError, "foo")):
|
||||
raise ExceptionGroup("", (ValueError("bar"),))
|
||||
except ExceptionGroup:
|
||||
pass
|
||||
else:
|
||||
assert False, "Expected pytest.raises.Exception"
|
||||
|
||||
# Can be used without specifying the type
|
||||
with RaisesGroup(Matcher(match="foo")):
|
||||
raise ExceptionGroup("", (ValueError("foo"),))
|
||||
try:
|
||||
with RaisesGroup(Matcher(match="foo")):
|
||||
raise ExceptionGroup("", (ValueError("bar"),))
|
||||
except ExceptionGroup:
|
||||
pass
|
||||
else:
|
||||
assert False, "Expected pytest.raises.Exception"
|
||||
|
||||
def test_Matcher_check(self) -> None:
|
||||
def check_oserror_and_errno_is_5(e: BaseException) -> bool:
|
||||
return isinstance(e, OSError) and e.errno == 5
|
||||
|
||||
with RaisesGroup(Matcher(check=check_oserror_and_errno_is_5)):
|
||||
raise ExceptionGroup("", (OSError(5, ""),))
|
||||
|
||||
# specifying exception_type narrows the parameter type to the callable
|
||||
def check_errno_is_5(e: OSError) -> bool:
|
||||
return e.errno == 5
|
||||
|
||||
with RaisesGroup(Matcher(OSError, check=check_errno_is_5)):
|
||||
raise ExceptionGroup("", (OSError(5, ""),))
|
||||
|
||||
try:
|
||||
with RaisesGroup(Matcher(OSError, check=check_errno_is_5)):
|
||||
raise ExceptionGroup("", (OSError(6, ""),))
|
||||
except ExceptionGroup:
|
||||
pass
|
||||
else:
|
||||
assert False, "Expected pytest.raises.Exception"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# getting the typing working satisfactory is very tricky
|
||||
# but with RaisesGroup being seen as a subclass of BaseExceptionGroup
|
||||
# most end-user cases of checking excinfo.value.foobar should work fine now.
|
||||
def test_types_0(self) -> None:
|
||||
_: BaseExceptionGroup[ValueError] = RaisesGroup(ValueError)
|
||||
_ = RaisesGroup(RaisesGroup(ValueError)) # type: ignore[arg-type]
|
||||
a: BaseExceptionGroup[BaseExceptionGroup[ValueError]]
|
||||
a = RaisesGroup(RaisesGroup(ValueError))
|
||||
a = BaseExceptionGroup("", (BaseExceptionGroup("", (ValueError(),)),))
|
||||
assert a
|
||||
|
||||
def test_types_1(self) -> None:
|
||||
def test_types_1() -> None:
|
||||
with RaisesGroup(ValueError) as e:
|
||||
raise ExceptionGroup("foo", (ValueError(),))
|
||||
assert_type(e.value, BaseExceptionGroup[ValueError])
|
||||
# assert_type(e.value, RaisesGroup[ValueError])
|
||||
|
||||
def test_types_2(self) -> None:
|
||||
def test_types_2() -> None:
|
||||
exc: ExceptionGroup[ValueError] | ValueError = ExceptionGroup(
|
||||
"", (ValueError(),)
|
||||
)
|
||||
if RaisesGroup(ValueError).matches(exc):
|
||||
if RaisesGroup(ValueError).assert_matches(exc):
|
||||
assert_type(exc, BaseExceptionGroup[ValueError])
|
||||
|
||||
def test_types_3(self) -> None:
|
||||
def test_types_3() -> None:
|
||||
e: BaseExceptionGroup[KeyboardInterrupt] = BaseExceptionGroup(
|
||||
"", (KeyboardInterrupt(),)
|
||||
)
|
||||
if RaisesGroup(ValueError).matches(e):
|
||||
assert_type(e, BaseExceptionGroup[ValueError])
|
||||
|
||||
def test_types_4(self) -> None:
|
||||
with RaisesGroup(Matcher(ValueError)) as e:
|
||||
...
|
||||
_: BaseExceptionGroup[ValueError] = e.value
|
||||
assert_type(e.value, BaseExceptionGroup[ValueError])
|
||||
|
||||
def test_types_5(self) -> None:
|
||||
with RaisesGroup(RaisesGroup(ValueError)) as excinfo:
|
||||
raise ExceptionGroup("foo", (ValueError(),))
|
||||
_: BaseExceptionGroup[BaseExceptionGroup[ValueError]] = excinfo.value
|
||||
assert_type(
|
||||
excinfo.value,
|
||||
BaseExceptionGroup[RaisesGroup[ValueError]],
|
||||
def test_types_4() -> None:
|
||||
e: BaseExceptionGroup[KeyboardInterrupt] = BaseExceptionGroup(
|
||||
"", (KeyboardInterrupt(),)
|
||||
)
|
||||
print(excinfo.value.exceptions[0].exceptions[0])
|
||||
|
||||
def test_types_6(self) -> None:
|
||||
exc: ExceptionGroup[ExceptionGroup[ValueError]] = ... # type: ignore[assignment]
|
||||
if RaisesGroup(RaisesGroup(ValueError)).matches(exc): # type: ignore[arg-type]
|
||||
# ugly
|
||||
assert_type(exc, BaseExceptionGroup[RaisesGroup[ValueError]])
|
||||
# not currently possible: https://github.com/python/typing/issues/930
|
||||
RaisesGroup(ValueError).assert_matches(e)
|
||||
assert_type(e, BaseExceptionGroup[ValueError]) # type: ignore[assert-type]
|
||||
|
|
Loading…
Reference in New Issue