Typed ExceptionGroupTypes and changed to BaseExceptionGroup, fixed exceptionchain (excinfo->excinfo_, set reprcrash. Extended tests, though they're wip.
This commit is contained in:
parent
078b112138
commit
163201e56b
1
AUTHORS
1
AUTHORS
|
@ -168,6 +168,7 @@ Jeff Rackauckas
|
||||||
Jeff Widman
|
Jeff Widman
|
||||||
Jenni Rinker
|
Jenni Rinker
|
||||||
John Eddie Ayson
|
John Eddie Ayson
|
||||||
|
John Litborn
|
||||||
John Towler
|
John Towler
|
||||||
Jon Parise
|
Jon Parise
|
||||||
Jon Sonesen
|
Jon Sonesen
|
||||||
|
|
|
@ -56,17 +56,18 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||||
|
|
||||||
ExceptionGroupTypes: tuple = () # type: ignore
|
ExceptionGroupTypes: Tuple[Type[BaseException], ...] = ()
|
||||||
try:
|
|
||||||
ExceptionGroupTypes += (ExceptionGroup,) # type: ignore
|
if sys.version_info >= (3, 11):
|
||||||
except NameError:
|
ExceptionGroupTypes = (BaseExceptionGroup,) # type: ignore # noqa: F821
|
||||||
pass # Is missing for `python<3.10`
|
|
||||||
try:
|
try:
|
||||||
import exceptiongroup
|
import exceptiongroup
|
||||||
|
|
||||||
ExceptionGroupTypes += (exceptiongroup.ExceptionGroup,)
|
ExceptionGroupTypes += (exceptiongroup.BaseExceptionGroup,)
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
pass # No backport is installed
|
# no backport installed - if <3.11 that means programs can't raise exceptiongroups
|
||||||
|
# so we don't need to handle it
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Code:
|
class Code:
|
||||||
|
@ -935,20 +936,22 @@ class FormattedExcinfo:
|
||||||
seen: Set[int] = set()
|
seen: Set[int] = set()
|
||||||
while e is not None and id(e) not in seen:
|
while e is not None and id(e) not in seen:
|
||||||
seen.add(id(e))
|
seen.add(id(e))
|
||||||
if isinstance(e, ExceptionGroupTypes):
|
if excinfo_:
|
||||||
|
if isinstance(e, tuple(ExceptionGroupTypes)):
|
||||||
reprtraceback: Union[
|
reprtraceback: Union[
|
||||||
ReprTracebackNative, ReprTraceback
|
ReprTracebackNative, ReprTraceback
|
||||||
] = ReprTracebackNative(
|
] = ReprTracebackNative(
|
||||||
traceback.format_exception(
|
traceback.format_exception(
|
||||||
type(excinfo.value),
|
type(excinfo_.value),
|
||||||
excinfo.value,
|
excinfo_.value,
|
||||||
excinfo.traceback[0]._rawentry,
|
excinfo_.traceback[0]._rawentry,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reprcrash: Optional[ReprFileLocation] = None
|
else:
|
||||||
elif excinfo_:
|
|
||||||
reprtraceback = self.repr_traceback(excinfo_)
|
reprtraceback = self.repr_traceback(excinfo_)
|
||||||
reprcrash = excinfo_._getreprcrash() if self.style != "value" else None
|
reprcrash: Optional[ReprFileLocation] = (
|
||||||
|
excinfo_._getreprcrash() if self.style != "value" else None
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Fallback to native repr if the exception doesn't have a traceback:
|
# Fallback to native repr if the exception doesn't have a traceback:
|
||||||
# ExceptionInfo objects require a full traceback to work.
|
# ExceptionInfo objects require a full traceback to work.
|
||||||
|
|
|
@ -11,6 +11,11 @@ from typing import Tuple
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
try:
|
||||||
|
import exceptiongroup # noqa (referred to in strings)
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest._code.code import ExceptionChainRepr
|
from _pytest._code.code import ExceptionChainRepr
|
||||||
|
@ -23,7 +28,6 @@ from _pytest.pathlib import import_path
|
||||||
from _pytest.pytester import LineMatcher
|
from _pytest.pytester import LineMatcher
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _pytest._code.code import _TracebackStyle
|
from _pytest._code.code import _TracebackStyle
|
||||||
|
|
||||||
|
@ -1472,32 +1476,84 @@ def test_no_recursion_index_on_recursion_error():
|
||||||
assert "maximum recursion" in str(excinfo.getrepr())
|
assert "maximum recursion" in str(excinfo.getrepr())
|
||||||
|
|
||||||
|
|
||||||
def test_exceptiongroup(pytester: Pytester) -> None:
|
def _exceptiongroup_common(
|
||||||
pytester.makepyfile(
|
pytester: Pytester,
|
||||||
"""
|
outer_chain: str,
|
||||||
def f(): raise ValueError("From f()")
|
inner_chain: str,
|
||||||
def g(): raise RuntimeError("From g()")
|
native: bool,
|
||||||
|
) -> None:
|
||||||
|
pre = "exceptiongroup." if not native else ""
|
||||||
|
pre2 = pre if sys.version_info < (3, 11) else ""
|
||||||
|
filestr = f"""
|
||||||
|
{"import exceptiongroup" if not native else ""}
|
||||||
|
import pytest
|
||||||
|
|
||||||
def main():
|
def f(): raise ValueError("From f()")
|
||||||
|
def g(): raise BaseException("From g()")
|
||||||
|
|
||||||
|
def inner(inner_chain):
|
||||||
excs = []
|
excs = []
|
||||||
for callback in [f, g]:
|
for callback in [f, g]:
|
||||||
try:
|
try:
|
||||||
callback()
|
callback()
|
||||||
except Exception as err:
|
except BaseException as err:
|
||||||
excs.append(err)
|
excs.append(err)
|
||||||
if excs:
|
if excs:
|
||||||
raise ExceptionGroup("Oops", excs)
|
if inner_chain == "none":
|
||||||
|
raise {pre}BaseExceptionGroup("Oops", excs)
|
||||||
|
try:
|
||||||
|
raise SyntaxError()
|
||||||
|
except SyntaxError as e:
|
||||||
|
if inner_chain == "from":
|
||||||
|
raise {pre}BaseExceptionGroup("Oops", excs) from e
|
||||||
|
else:
|
||||||
|
raise {pre}BaseExceptionGroup("Oops", excs)
|
||||||
|
|
||||||
|
def outer(outer_chain, inner_chain):
|
||||||
|
try:
|
||||||
|
inner(inner_chain)
|
||||||
|
except {pre2}BaseExceptionGroup as e:
|
||||||
|
if outer_chain == "none":
|
||||||
|
raise
|
||||||
|
if outer_chain == "from":
|
||||||
|
raise IndexError() from e
|
||||||
|
else:
|
||||||
|
raise IndexError()
|
||||||
|
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
main()
|
outer("{outer_chain}", "{inner_chain}")
|
||||||
"""
|
"""
|
||||||
)
|
pytester.makepyfile(test_excgroup=filestr)
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest()
|
||||||
assert result.ret != 0
|
match_lines = [
|
||||||
|
rf" \| {pre2}BaseExceptionGroup: Oops \(2 sub-exceptions\)",
|
||||||
match = [
|
r" \| ValueError: From f\(\)",
|
||||||
r" | ExceptionGroup: Oops (2 sub-exceptions)",
|
r" \| BaseException: From g\(\)",
|
||||||
r" | ValueError: From f()",
|
r"=* short test summary info =*",
|
||||||
r" | RuntimeError: From g()",
|
|
||||||
]
|
]
|
||||||
result.stdout.re_match_lines(match)
|
if outer_chain in ("another", "from"):
|
||||||
|
match_lines.append(r"FAILED test_excgroup.py::test - IndexError")
|
||||||
|
else:
|
||||||
|
match_lines.append(
|
||||||
|
rf"FAILED test_excgroup.py::test - {pre2}BaseExceptionGroup: Oops \(2 su.*"
|
||||||
|
)
|
||||||
|
result.stdout.re_match_lines(match_lines)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented"
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
|
||||||
|
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
|
||||||
|
def test_native_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
|
||||||
|
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
"exceptiongroup" not in sys.modules, reason="exceptiongroup not installed"
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
|
||||||
|
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
|
||||||
|
def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
|
||||||
|
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)
|
||||||
|
|
5
tox.ini
5
tox.ini
|
@ -9,8 +9,9 @@ envlist =
|
||||||
py39
|
py39
|
||||||
py310
|
py310
|
||||||
py311
|
py311
|
||||||
|
py311-exceptiongroup
|
||||||
pypy3
|
pypy3
|
||||||
py37-{pexpect,xdist,unittestextras,numpy,pluggymain}
|
py37-{pexpect,xdist,unittestextras,numpy,pluggymain,exceptiongroup}
|
||||||
doctesting
|
doctesting
|
||||||
plugins
|
plugins
|
||||||
py37-freeze
|
py37-freeze
|
||||||
|
@ -46,7 +47,7 @@ setenv =
|
||||||
extras = testing
|
extras = testing
|
||||||
deps =
|
deps =
|
||||||
doctesting: PyYAML
|
doctesting: PyYAML
|
||||||
exceptiongroup: exceptiongroup>=1.0.0
|
exceptiongroup: exceptiongroup>=1.0.0rc8
|
||||||
numpy: numpy>=1.19.4
|
numpy: numpy>=1.19.4
|
||||||
pexpect: pexpect>=4.8.0
|
pexpect: pexpect>=4.8.0
|
||||||
pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git
|
pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git
|
||||||
|
|
Loading…
Reference in New Issue