Typed ExceptionGroupTypes and changed to BaseExceptionGroup, fixed exceptionchain (excinfo->excinfo_, set reprcrash. Extended tests, though they're wip.

This commit is contained in:
jakkdl 2022-08-12 16:28:46 +02:00
parent 078b112138
commit 163201e56b
4 changed files with 107 additions and 46 deletions

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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