From 352db284289daa98eabc4a884c51141ee02cd6be Mon Sep 17 00:00:00 2001 From: Cheukting Date: Thu, 23 Jun 2022 11:33:10 +0800 Subject: [PATCH 1/9] fall back to native when handeling to exception groups --- src/_pytest/_code/code.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 304a5cbd751..5dd309c2bc3 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -923,7 +923,12 @@ def repr_excinfo( seen: Set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) - if excinfo_: + if isinstance(e, ExceptionGroup): + reprtraceback = ReprTracebackNative( + traceback.format_exception(type(e), e, excinfo.traceback[0]._rawentry) + ) + reprcrash = None + elif excinfo_: reprtraceback = self.repr_traceback(excinfo_) reprcrash: Optional[ReprFileLocation] = ( excinfo_._getreprcrash() if self.style != "value" else None From 88424c273c7d507ad8e7606fa3c593561261d389 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Thu, 23 Jun 2022 11:40:41 +0800 Subject: [PATCH 2/9] adding changelog --- changelog/9159.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/9159.bugfix.rst diff --git a/changelog/9159.bugfix.rst b/changelog/9159.bugfix.rst new file mode 100644 index 00000000000..bd4632e1b42 --- /dev/null +++ b/changelog/9159.bugfix.rst @@ -0,0 +1 @@ +Showing inner exceptions in ``ExceptionGroups`` when using display options other than ``--tb=native``. From c135a236d341488340332ca4b087e59940bfbfda Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 03:43:27 +0000 Subject: [PATCH 3/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/_code/code.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 5dd309c2bc3..a3cdc7e3bd7 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -925,7 +925,9 @@ def repr_excinfo( seen.add(id(e)) if isinstance(e, ExceptionGroup): reprtraceback = ReprTracebackNative( - traceback.format_exception(type(e), e, excinfo.traceback[0]._rawentry) + traceback.format_exception( + type(e), e, excinfo.traceback[0]._rawentry + ) ) reprcrash = None elif excinfo_: From 39339b991d5e20317fcbf0b1b4a1265b86cc75a0 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Thu, 23 Jun 2022 15:03:48 +0800 Subject: [PATCH 4/9] fix if ExceptionGroup not exist --- src/_pytest/_code/code.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index a3cdc7e3bd7..3fa0b637b0f 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -56,6 +56,18 @@ _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] +ExceptionGroupTypes: tuple = () +try: + ExceptionGroupTypes += (ExceptionGroup,) +except NameError: + pass # Is missing for `python<3.10` +try: + import exceptiongroup + + ExceptionGroupTypes += (exceptiongroup.ExceptionGroup,) +except ModuleNotFoundError: + pass # No backport is installed + class Code: """Wrapper around Python code objects.""" @@ -923,7 +935,7 @@ def repr_excinfo( seen: Set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) - if isinstance(e, ExceptionGroup): + if isinstance(e, ExceptionGroupTypes): reprtraceback = ReprTracebackNative( traceback.format_exception( type(e), e, excinfo.traceback[0]._rawentry From be502f932906e19a418285b2e57b2486435938e4 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Thu, 23 Jun 2022 15:48:21 +0800 Subject: [PATCH 5/9] Fix MyPy --- src/_pytest/_code/code.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 3fa0b637b0f..9f04c0ffe6e 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -56,9 +56,9 @@ _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] -ExceptionGroupTypes: tuple = () +ExceptionGroupTypes: tuple = () # type: ignore try: - ExceptionGroupTypes += (ExceptionGroup,) + ExceptionGroupTypes += (ExceptionGroup,) # type: ignore except NameError: pass # Is missing for `python<3.10` try: @@ -68,7 +68,6 @@ except ModuleNotFoundError: pass # No backport is installed - class Code: """Wrapper around Python code objects.""" @@ -936,15 +935,15 @@ def repr_excinfo( while e is not None and id(e) not in seen: seen.add(id(e)) if isinstance(e, ExceptionGroupTypes): - reprtraceback = ReprTracebackNative( + reprtraceback: Union[ReprTracebackNative, ReprTraceback] = ReprTracebackNative( traceback.format_exception( - type(e), e, excinfo.traceback[0]._rawentry + type(excinfo.value), excinfo.value, excinfo.traceback[0]._rawentry ) ) - reprcrash = None + reprcrash: Optional[ReprFileLocation] = None elif excinfo_: reprtraceback = self.repr_traceback(excinfo_) - reprcrash: Optional[ReprFileLocation] = ( + reprcrash = ( excinfo_._getreprcrash() if self.style != "value" else None ) else: From ee348a9f4cf49bd37b1427164cbfc520fe996ff0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 07:50:52 +0000 Subject: [PATCH 6/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/_code/code.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 9f04c0ffe6e..b1991f1aa31 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -68,6 +68,7 @@ except ModuleNotFoundError: pass # No backport is installed + class Code: """Wrapper around Python code objects.""" @@ -935,17 +936,19 @@ def repr_excinfo( while e is not None and id(e) not in seen: seen.add(id(e)) if isinstance(e, ExceptionGroupTypes): - reprtraceback: Union[ReprTracebackNative, ReprTraceback] = ReprTracebackNative( + reprtraceback: Union[ + ReprTracebackNative, ReprTraceback + ] = ReprTracebackNative( traceback.format_exception( - type(excinfo.value), excinfo.value, excinfo.traceback[0]._rawentry + type(excinfo.value), + excinfo.value, + excinfo.traceback[0]._rawentry, ) ) reprcrash: Optional[ReprFileLocation] = None elif excinfo_: reprtraceback = self.repr_traceback(excinfo_) - reprcrash = ( - excinfo_._getreprcrash() if self.style != "value" else None - ) + reprcrash = excinfo_._getreprcrash() if self.style != "value" else None else: # Fallback to native repr if the exception doesn't have a traceback: # ExceptionInfo objects require a full traceback to work. From ed054c6379d01c0abf6e97f6a1966f425785ccc5 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Thu, 23 Jun 2022 16:00:07 +0800 Subject: [PATCH 7/9] update changelog --- changelog/9159.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/9159.bugfix.rst b/changelog/9159.bugfix.rst index bd4632e1b42..5e7f0a3014d 100644 --- a/changelog/9159.bugfix.rst +++ b/changelog/9159.bugfix.rst @@ -1 +1 @@ -Showing inner exceptions in ``ExceptionGroups`` when using display options other than ``--tb=native``. +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``. From 2a64ad4fd5eca96ea925a51825990d4d721fd105 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Thu, 23 Jun 2022 16:41:06 +0800 Subject: [PATCH 8/9] add test --- testing/code/test_excinfo.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index af72857f3e7..c79030cd51a 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1470,3 +1470,35 @@ def __getattr__(self, attr): with pytest.raises(RuntimeError) as excinfo: RecursionDepthError().trigger assert "maximum recursion" in str(excinfo.getrepr()) + + +@pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11 or higher") +def test_exceptiongroup(pytester: Pytester) -> None: + pytester.makepyfile( + """ + def f(): raise ValueError("From f()") + def g(): raise RuntimeError("From g()") + + def main(): + excs = [] + for callback in [f, g]: + try: + callback() + except Exception as err: + excs.append(err) + if excs: + raise ExceptionGroup("Oops", excs) + + def test(): + main() + """ + ) + result = pytester.runpytest() + assert result.ret != 0 + + match = [ + r" | ExceptionGroup: Oops (2 sub-exceptions)", + r" | ValueError: From f()", + r" | RuntimeError: From g()", + ] + result.stdout.re_match_lines(match) From 97865ce79faae5bcd9886b905f712ec3b3dc795a Mon Sep 17 00:00:00 2001 From: Cheukting Date: Thu, 23 Jun 2022 17:01:04 +0800 Subject: [PATCH 9/9] test in all py versions --- testing/code/test_excinfo.py | 1 - tox.ini | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index c79030cd51a..78f777718e9 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1472,7 +1472,6 @@ def __getattr__(self, attr): assert "maximum recursion" in str(excinfo.getrepr()) -@pytest.mark.skipif(sys.version_info < (3, 11), reason="requires python3.11 or higher") def test_exceptiongroup(pytester: Pytester) -> None: pytester.makepyfile( """ diff --git a/tox.ini b/tox.ini index 93c390ffc57..fd2ff1ba176 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,7 @@ setenv = extras = testing deps = doctesting: PyYAML + exceptiongroup: exceptiongroup>=1.0.0 numpy: numpy>=1.19.4 pexpect: pexpect>=4.8.0 pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git