From c1800e03e8462c44a04e32da9f68fe029ad03c0a Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Fri, 18 Oct 2024 09:50:03 +0300 Subject: [PATCH 1/9] implementation --- Lib/traceback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index 0fe7187a0c6193..f73149271b9bc9 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1428,7 +1428,7 @@ def format(self, *, chain=True, _ctx=None, **kwargs): f'+---------------- {title} ----------------\n') _ctx.exception_group_depth += 1 if not truncated: - yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx) + yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx, colorize=colorize) else: remaining = num_excs - self.max_group_width plural = 's' if remaining > 1 else '' From d457bce6e3fa46a6f41f4e5a7aee012324685f50 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Fri, 18 Oct 2024 10:01:09 +0300 Subject: [PATCH 2/9] Add test for colorizing --- Lib/test/test_traceback.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 77ef0c5b3c480d..9847b5f6813a45 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4637,6 +4637,27 @@ def foo(): f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] self.assertEqual(actual, expected) + def test_colorized_traceback_from_exception_group(self): + try: + exceptions = [] + try: + 1 / 0 + except ZeroDivisionError as inner_exc: + exceptions.append(inner_exc) + raise ExceptionGroup("test", exceptions) + except Exception as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + + actual = "".join(exc.format(colorize=True)) + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + reset = _colorize.ANSIColors.RESET + + self.assertIn(f"{red}1 {reset+boldr}/{reset+red} 0{reset}", actual) + self.assertIn(f"{red}~~{reset+boldr}^{reset+red}~~{reset}", actual) + if __name__ == "__main__": unittest.main() From 93090485314a04f8f58637ad80b6c5dd7e0cd832 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Fri, 18 Oct 2024 10:14:23 +0300 Subject: [PATCH 3/9] Add news entry --- .../2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst b/Misc/NEWS.d/next/Core and Builtins/2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst new file mode 100644 index 00000000000000..220e94467af849 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst @@ -0,0 +1 @@ +Use color to highlight error locations in traceback from exception group From c862e53c01fc9a9cfc0dbda9b45e4084b1d6def9 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Fri, 18 Oct 2024 17:23:08 +0300 Subject: [PATCH 4/9] first attempt to test correction --- Lib/test/test_traceback.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 9847b5f6813a45..105e80f1c23bf9 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4638,25 +4638,53 @@ def foo(): self.assertEqual(actual, expected) def test_colorized_traceback_from_exception_group(self): - try: + self.maxDiff = None + def foo(): exceptions = [] try: 1 / 0 except ZeroDivisionError as inner_exc: exceptions.append(inner_exc) raise ExceptionGroup("test", exceptions) + + try: + foo() except Exception as e: exc = traceback.TracebackException.from_exception( e, capture_locals=True ) - actual = "".join(exc.format(colorize=True)) red = _colorize.ANSIColors.RED boldr = _colorize.ANSIColors.BOLD_RED + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA reset = _colorize.ANSIColors.RESET + lno_foo = foo.__code__.co_firstlineno - self.assertIn(f"{red}1 {reset+boldr}/{reset+red} 0{reset}", actual) - self.assertIn(f"{red}~~{reset+boldr}^{reset+red}~~{reset}", actual) + print(f"{lno_foo=}") + + actual = "".join(exc.format(colorize=True)) + expected = f' + Exception Group Traceback (most recent call last): ' \ + f' | File {magenta}"{__file__}"{reset}, line {lno_foo+10}, in {magenta}test_colorized_traceback_from_exception_group{reset} ' \ + f' | {red}foo{reset}{boldr}(){reset} ' \ + f' | {red}~~~{reset}{boldr}^^{reset} ' \ + f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')]) " \ + f' | foo = .foo at {hex(id(foo))}> ' \ + f' | self = <__main__.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>' \ + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset} ' \ + f' | raise ExceptionGroup("test", exceptions) ' \ + f" | exceptions = [ZeroDivisionError('division by zero')] " \ + f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset} ' \ + f' +-+---------------- 1 ---------------- ' \ + f' | Traceback (most recent call last): ' \ + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset} ' \ + f' | {red}1{reset} {boldr}/{reset} {red}0{reset} ' \ + f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset} ' \ + f" | exceptions = [ZeroDivisionError('division by zero')] " \ + f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset} ' \ + f' +------------------------------------' + + self.assertEqual(actual, expected) if __name__ == "__main__": From adf7ff98a3f6f617d54b7bf01211833018ad97b0 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Fri, 18 Oct 2024 18:03:05 +0300 Subject: [PATCH 5/9] Fix test --- Lib/test/test_traceback.py | 43 ++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 105e80f1c23bf9..7617f591dd30e2 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4660,30 +4660,27 @@ def foo(): boldm = _colorize.ANSIColors.BOLD_MAGENTA reset = _colorize.ANSIColors.RESET lno_foo = foo.__code__.co_firstlineno - - print(f"{lno_foo=}") - actual = "".join(exc.format(colorize=True)) - expected = f' + Exception Group Traceback (most recent call last): ' \ - f' | File {magenta}"{__file__}"{reset}, line {lno_foo+10}, in {magenta}test_colorized_traceback_from_exception_group{reset} ' \ - f' | {red}foo{reset}{boldr}(){reset} ' \ - f' | {red}~~~{reset}{boldr}^^{reset} ' \ - f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')]) " \ - f' | foo = .foo at {hex(id(foo))}> ' \ - f' | self = <__main__.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>' \ - f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset} ' \ - f' | raise ExceptionGroup("test", exceptions) ' \ - f" | exceptions = [ZeroDivisionError('division by zero')] " \ - f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset} ' \ - f' +-+---------------- 1 ---------------- ' \ - f' | Traceback (most recent call last): ' \ - f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset} ' \ - f' | {red}1{reset} {boldr}/{reset} {red}0{reset} ' \ - f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset} ' \ - f" | exceptions = [ZeroDivisionError('division by zero')] " \ - f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset} ' \ - f' +------------------------------------' - + actual = "".join(exc.format(colorize=True)).splitlines() + expected = [f" + Exception Group Traceback (most recent call last):", + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}', + f' | {red}foo{reset}{boldr}(){reset}', + f' | {red}~~~{reset}{boldr}^^{reset}', + f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])", + f' | foo = .foo at {hex(id(foo))}>', + f' | self = <__main__.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>', + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}', + f' | raise ExceptionGroup("test", exceptions)', + f" | exceptions = [ZeroDivisionError('division by zero')]", + f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset}', + f' +-+---------------- 1 ----------------', + f' | Traceback (most recent call last):', + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset}', + f' | {red}1 {reset}{boldr}/{reset}{red} 0{reset}', + f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset}', + f" | exceptions = [ZeroDivisionError('division by zero')]", + f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}', + f' +------------------------------------'] self.assertEqual(actual, expected) From 86ad2c1aaa1de5b889fab83bd95e205fa403747a Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Fri, 18 Oct 2024 18:07:19 +0300 Subject: [PATCH 6/9] Satisfy linter --- Lib/test/test_traceback.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 7617f591dd30e2..7c8c498453ddf9 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4638,7 +4638,6 @@ def foo(): self.assertEqual(actual, expected) def test_colorized_traceback_from_exception_group(self): - self.maxDiff = None def foo(): exceptions = [] try: @@ -4660,7 +4659,6 @@ def foo(): boldm = _colorize.ANSIColors.BOLD_MAGENTA reset = _colorize.ANSIColors.RESET lno_foo = foo.__code__.co_firstlineno - actual = "".join(exc.format(colorize=True)).splitlines() expected = [f" + Exception Group Traceback (most recent call last):", f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}', From 5689ed514130b52a75654dee36956c414d156161 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Fri, 18 Oct 2024 22:59:08 +0300 Subject: [PATCH 7/9] format __name__ --- Lib/test/test_traceback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 7c8c498453ddf9..b05c479f3b08b6 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4666,7 +4666,7 @@ def foo(): f' | {red}~~~{reset}{boldr}^^{reset}', f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])", f' | foo = .foo at {hex(id(foo))}>', - f' | self = <__main__.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>', + f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>', f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}', f' | raise ExceptionGroup("test", exceptions)', f" | exceptions = [ZeroDivisionError('division by zero')]", From b3ae2348678730b5f60974ba90819d83e04b8dd7 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Sat, 19 Oct 2024 12:26:36 +0300 Subject: [PATCH 8/9] =?UTF-8?q?=D0=94=D0=BE=D0=B1match=20address=20with=20?= =?UTF-8?q?regexp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/test/test_traceback.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index b05c479f3b08b6..307eb14dc0733c 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4659,13 +4659,12 @@ def foo(): boldm = _colorize.ANSIColors.BOLD_MAGENTA reset = _colorize.ANSIColors.RESET lno_foo = foo.__code__.co_firstlineno - actual = "".join(exc.format(colorize=True)).splitlines() + actual = "".join(exc.format(colorize=True)) expected = [f" + Exception Group Traceback (most recent call last):", f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}', f' | {red}foo{reset}{boldr}(){reset}', f' | {red}~~~{reset}{boldr}^^{reset}', f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])", - f' | foo = .foo at {hex(id(foo))}>', f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>', f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}', f' | raise ExceptionGroup("test", exceptions)', @@ -4679,7 +4678,10 @@ def foo(): f" | exceptions = [ZeroDivisionError('division by zero')]", f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}', f' +------------------------------------'] - self.assertEqual(actual, expected) + string_with_address = r' | foo = .foo at 0x[0-9a-fA-F]+>' + for line in expected: + self.assertIn(line, actual) + self.assertRegex(actual, string_with_address) if __name__ == "__main__": From 16ada38ed547710c3c2a45c1fd674571b2c4c145 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk Date: Mon, 21 Oct 2024 18:33:03 +0300 Subject: [PATCH 9/9] Address the review comment --- Lib/test/test_traceback.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 307eb14dc0733c..ec69412f5511eb 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4659,12 +4659,13 @@ def foo(): boldm = _colorize.ANSIColors.BOLD_MAGENTA reset = _colorize.ANSIColors.RESET lno_foo = foo.__code__.co_firstlineno - actual = "".join(exc.format(colorize=True)) + actual = "".join(exc.format(colorize=True)).splitlines() expected = [f" + Exception Group Traceback (most recent call last):", f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}', f' | {red}foo{reset}{boldr}(){reset}', f' | {red}~~~{reset}{boldr}^^{reset}', f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])", + f" | foo = {foo}", f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>', f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}', f' | raise ExceptionGroup("test", exceptions)', @@ -4678,11 +4679,7 @@ def foo(): f" | exceptions = [ZeroDivisionError('division by zero')]", f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}', f' +------------------------------------'] - string_with_address = r' | foo = .foo at 0x[0-9a-fA-F]+>' - for line in expected: - self.assertIn(line, actual) - self.assertRegex(actual, string_with_address) - + self.assertEqual(actual, expected) if __name__ == "__main__": unittest.main()