[7.1.x] fallback to native traceback when handling ExceptionGroup (take 2) [SQUASH] (#10305)

Co-authored-by: John Litborn <11260241+jakkdl@users.noreply.github.com>
This commit is contained in:
github-actions[bot] 2022-09-20 10:12:44 -07:00 committed by GitHub
parent 3cc4c23d3b
commit 14b276309a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 1 deletions

View File

@ -114,6 +114,7 @@ jobs:
python: "3.11-dev"
os: ubuntu-latest
tox_env: "py311"
use_coverage: true
- name: "ubuntu-pypy3"
python: "pypy-3.7"
os: ubuntu-latest

View File

@ -68,6 +68,9 @@ repos:
- packaging
- tomli
- types-pkg_resources
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
# on <3.11
- exceptiongroup>=1.0.0rc8
- repo: local
hooks:
- id: rst

View File

@ -165,6 +165,7 @@ Jeff Rackauckas
Jeff Widman
Jenni Rinker
John Eddie Ayson
John Litborn
John Towler
Jon Sonesen
Jonas Obrist

View File

@ -0,0 +1 @@
Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``.

View File

@ -48,6 +48,7 @@ install_requires =
py>=1.8.2
tomli>=1.0.0
colorama;sys_platform=="win32"
exceptiongroup>=1.0.0rc8;python_version<"3.11"
importlib-metadata>=0.12;python_version<"3.8"
python_requires = >=3.7
package_dir =

View File

@ -56,6 +56,9 @@ if TYPE_CHECKING:
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
if sys.version_info[:2] < (3, 11):
from exceptiongroup import BaseExceptionGroup
class Code:
"""Wrapper around Python code objects."""
@ -923,7 +926,21 @@ class FormattedExcinfo:
while e is not None and id(e) not in seen:
seen.add(id(e))
if excinfo_:
reprtraceback = self.repr_traceback(excinfo_)
# Fall back to native traceback as a temporary workaround until
# full support for exception groups added to ExceptionInfo.
# See https://github.com/pytest-dev/pytest/issues/9159
if isinstance(e, BaseExceptionGroup):
reprtraceback: Union[
ReprTracebackNative, ReprTraceback
] = ReprTracebackNative(
traceback.format_exception(
type(excinfo_.value),
excinfo_.value,
excinfo_.traceback[0]._rawentry,
)
)
else:
reprtraceback = self.repr_traceback(excinfo_)
reprcrash: Optional[ReprFileLocation] = (
excinfo_._getreprcrash() if self.style != "value" else None
)

View File

@ -1468,3 +1468,90 @@ def test_no_recursion_index_on_recursion_error():
with pytest.raises(RuntimeError) as excinfo:
RecursionDepthError().trigger
assert "maximum recursion" in str(excinfo.getrepr())
def _exceptiongroup_common(
pytester: Pytester,
outer_chain: str,
inner_chain: str,
native: bool,
) -> None:
pre_raise = "exceptiongroup." if not native else ""
pre_catch = pre_raise if sys.version_info < (3, 11) else ""
filestr = f"""
{"import exceptiongroup" if not native else ""}
import pytest
def f(): raise ValueError("From f()")
def g(): raise BaseException("From g()")
def inner(inner_chain):
excs = []
for callback in [f, g]:
try:
callback()
except BaseException as err:
excs.append(err)
if excs:
if inner_chain == "none":
raise {pre_raise}BaseExceptionGroup("Oops", excs)
try:
raise SyntaxError()
except SyntaxError as e:
if inner_chain == "from":
raise {pre_raise}BaseExceptionGroup("Oops", excs) from e
else:
raise {pre_raise}BaseExceptionGroup("Oops", excs)
def outer(outer_chain, inner_chain):
try:
inner(inner_chain)
except {pre_catch}BaseExceptionGroup as e:
if outer_chain == "none":
raise
if outer_chain == "from":
raise IndexError() from e
else:
raise IndexError()
def test():
outer("{outer_chain}", "{inner_chain}")
"""
pytester.makepyfile(test_excgroup=filestr)
result = pytester.runpytest()
match_lines = []
if inner_chain in ("another", "from"):
match_lines.append(r"SyntaxError: <no detail available>")
match_lines += [
r" + Exception Group Traceback (most recent call last):",
rf" \| {pre_catch}BaseExceptionGroup: Oops \(2 sub-exceptions\)",
r" \| ValueError: From f\(\)",
r" \| BaseException: From g\(\)",
r"=* short test summary info =*",
]
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 - {pre_catch}BaseExceptionGroup: Oops \(2.*"
)
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.parametrize("outer_chain", ["none", "from", "another"])
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
# with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it
pytest.importorskip("exceptiongroup")
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)

View File

@ -17,6 +17,10 @@ envlist =
docs
docs-checklinks
# checks that 3.11 native ExceptionGroup works with exceptiongroup
# not included in CI.
py311-exceptiongroup
[testenv]
@ -46,6 +50,7 @@ setenv =
extras = testing
deps =
doctesting: PyYAML
exceptiongroup: exceptiongroup>=1.0.0rc8
numpy: numpy>=1.19.4
pexpect: pexpect>=4.8.0
pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git