test_exceptiongroup_tb.py
112 lines
| 3.5 KiB
| text/x-python
|
PythonLexer
jakkdl
|
r28333 | import unittest | ||
import re | ||||
from IPython.utils.capture import capture_output | ||||
import sys | ||||
import pytest | ||||
from tempfile import TemporaryDirectory | ||||
from IPython.testing import tools as tt | ||||
jakkdl
|
r28335 | |||
jakkdl
|
r28333 | def _exceptiongroup_common( | ||
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 | ||||
outer("{outer_chain}", "{inner_chain}") | ||||
""" | ||||
with capture_output() as cap: | ||||
ip.run_cell(filestr) | ||||
match_lines = [] | ||||
if inner_chain == "another": | ||||
match_lines += [ | ||||
"During handling of the above exception, another exception occurred:", | ||||
] | ||||
jakkdl
|
r28335 | elif inner_chain == "from": | ||
jakkdl
|
r28333 | match_lines += [ | ||
"The above exception was the direct cause of the following exception:", | ||||
] | ||||
match_lines += [ | ||||
" + Exception Group Traceback (most recent call last):", | ||||
f" | {pre_catch}BaseExceptionGroup: Oops (2 sub-exceptions)", | ||||
" | ValueError: From f()", | ||||
" | BaseException: From g()", | ||||
] | ||||
if outer_chain == "another": | ||||
match_lines += [ | ||||
"During handling of the above exception, another exception occurred:", | ||||
"IndexError", | ||||
] | ||||
elif outer_chain == "from": | ||||
match_lines += [ | ||||
"The above exception was the direct cause of the following exception:", | ||||
"IndexError", | ||||
] | ||||
error_lines = cap.stderr.split("\n") | ||||
err_index = match_index = 0 | ||||
for expected in match_lines: | ||||
jakkdl
|
r28335 | for i, actual in enumerate(error_lines): | ||
jakkdl
|
r28333 | if actual == expected: | ||
jakkdl
|
r28335 | error_lines = error_lines[i + 1 :] | ||
jakkdl
|
r28333 | break | ||
else: | ||||
jakkdl
|
r28335 | assert False, f"{expected} not found in cap.stderr" | ||
jakkdl
|
r28333 | |||
@pytest.mark.skipif( | ||||
jakkdl
|
r28335 | sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented" | ||
) | ||||
jakkdl
|
r28333 | @pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) | ||
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) | ||||
def test_native_exceptiongroup(outer_chain, inner_chain) -> None: | ||||
_exceptiongroup_common(outer_chain, inner_chain, native=True) | ||||
jakkdl
|
r28335 | |||
jakkdl
|
r28333 | @pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) | ||
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) | ||||
def test_native_exceptiongroup(outer_chain, inner_chain) -> None: | ||||
pytest.importorskip("exceptiongroup") | ||||
_exceptiongroup_common(outer_chain, inner_chain, native=False) | ||||