From 8023ff3f1fc6981071fde13fb7b47982adfa7d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:13:37 +0200 Subject: [PATCH 1/6] Handle an empty AST body when reporting tracebacks. --- Lib/traceback.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/traceback.py b/Lib/traceback.py index 6ee1a50ca6804a..eba6f03a3c6de3 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -698,6 +698,8 @@ def _should_show_carets(self, start_offset, end_offset, all_lines, anchors): with suppress(SyntaxError, ImportError): import ast tree = ast.parse('\n'.join(all_lines)) + if not tree.body: + return False statement = tree.body[0] value = None def _spawns_full_line(value): From 94be00e668aa8e82c72a0b789751928c17769cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:35:28 +0200 Subject: [PATCH 2/6] add tests --- Lib/test/test_traceback.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 1895c88d23b70d..daad9a09cf1202 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3275,6 +3275,17 @@ def format_frame_summary(self, frame_summary, colorize=False): f' File "{__file__}", line {lno}, in f\n 1/0\n' ) + def test_traceback_empty_ast(self): + # see gh-122145 + fs = traceback.FrameSummary("?", 1, "s", lookup_line=False, + locals=None, end_lineno=1, colno=0, + end_colno=10, line="#123456789") + self.assertListEqual( + traceback.StackSummary().format_frame_summary(fs).splitlines(), + [' File "?", line 1, in s', ' #123456789'] + ) + + class Unrepresentable: def __repr__(self) -> str: raise Exception("Unrepresentable") From 0925ed2b25689de6f5a8d5753df05b70d0505406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:38:25 +0200 Subject: [PATCH 3/6] blurb --- .../next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst diff --git a/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst b/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst new file mode 100644 index 00000000000000..740ebda26bb5fc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst @@ -0,0 +1,2 @@ +Fix an issue when reporting tracebacks containing only Python comments. +Patch by Bénédikt Tran. From b98e162fc9625b7ce4bf29128300c764dbd6b3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:11:42 +0200 Subject: [PATCH 4/6] Update NEWS entry. --- .../Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst b/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst index 740ebda26bb5fc..a4282f12d9742a 100644 --- a/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst +++ b/Misc/NEWS.d/next/Library/2024-07-23-12-38-14.gh-issue-122145.sTO8nX.rst @@ -1,2 +1,3 @@ -Fix an issue when reporting tracebacks containing only Python comments. -Patch by Bénédikt Tran. +Fix an issue when reporting tracebacks corresponding to Python code +emitting an empty AST body. +Patch by Nikita Sobolev and Bénédikt Tran. From 9bc84473472f2dce64a7f205c9ffe008260460ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:30:44 +0200 Subject: [PATCH 5/6] update tests --- Lib/test/test_traceback.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index daad9a09cf1202..cc980e96fae7ed 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3275,16 +3275,15 @@ def format_frame_summary(self, frame_summary, colorize=False): f' File "{__file__}", line {lno}, in f\n 1/0\n' ) - def test_traceback_empty_ast(self): - # see gh-122145 - fs = traceback.FrameSummary("?", 1, "s", lookup_line=False, - locals=None, end_lineno=1, colno=0, - end_colno=10, line="#123456789") - self.assertListEqual( - traceback.StackSummary().format_frame_summary(fs).splitlines(), - [' File "?", line 1, in s', ' #123456789'] - ) - + def test_summary_should_show_carets(self): + # See: https://github.com/python/cpython/issues/122353 + should_show = traceback.StackSummary()._should_show_carets + # a line that may or may not have carrets shown + self.assertTrue(should_show(0, 1, ['a = 123456789'], None)) + self.assertFalse(should_show(0, 999, ['return'], None)) + # commented lines have an empty AST body, hence never shown + self.assertFalse(should_show(0, 1, ['# abc = 123456789'], None)) + self.assertFalse(should_show(0, 999, ['# return'], None)) class Unrepresentable: def __repr__(self) -> str: From c944dae5a26c4bade7df4e2e09fba2067b71d29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:33:22 +0200 Subject: [PATCH 6/6] update test --- Lib/test/test_traceback.py | 39 +++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index cc980e96fae7ed..e6311ce6b35b31 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3277,13 +3277,38 @@ def format_frame_summary(self, frame_summary, colorize=False): def test_summary_should_show_carets(self): # See: https://github.com/python/cpython/issues/122353 - should_show = traceback.StackSummary()._should_show_carets - # a line that may or may not have carrets shown - self.assertTrue(should_show(0, 1, ['a = 123456789'], None)) - self.assertFalse(should_show(0, 999, ['return'], None)) - # commented lines have an empty AST body, hence never shown - self.assertFalse(should_show(0, 1, ['# abc = 123456789'], None)) - self.assertFalse(should_show(0, 999, ['# return'], None)) + + # statement to execute and to get a ZeroDivisionError for a traceback + statement = "abcdef = 1 / 0 and 2.0" + colno = statement.index('1 / 0') + end_colno = colno + len('1 / 0') + + # Actual line to use when rendering the traceback + # and whose AST will be extracted (it will be empty). + cached_line = '# this line will be used during rendering' + self.addCleanup(unlink, TESTFN) + with open(TESTFN, "w") as file: + file.write(cached_line) + linecache.updatecache(TESTFN, {}) + + try: + exec(compile(statement, TESTFN, "exec")) + except ZeroDivisionError as exc: + # This is the simplest way to create a StackSummary + # whose FrameSummary items have their column offsets. + s = traceback.TracebackException.from_exception(exc).stack + self.assertIsInstance(s, traceback.StackSummary) + with unittest.mock.patch.object(s, '_should_show_carets', + wraps=s._should_show_carets) as ff: + self.assertEqual(len(s), 2) + self.assertListEqual( + s.format_frame_summary(s[1]).splitlines(), + [ + f' File "{TESTFN}", line 1, in ', + f' {cached_line}' + ] + ) + ff.assert_called_with(colno, end_colno, [cached_line], None) class Unrepresentable: def __repr__(self) -> str: