Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -2062,15 +2062,101 @@ def h(count=10):
actual = stderr_g.getvalue().splitlines()
self.assertEqual(actual, expected)

def _check_previous_line_repeated(self, render_exc):
# See https://github.com/python/cpython/issues/128327
def fib(number: int) -> int:
# wrong implementation
assert number > 0
if number == 1:
return 1
return fib(number - 1) + fib(number - 2)

lineno_fib = fib.__code__.co_firstlineno

with captured_output("stderr") as stderr_fib5:
try:
fib(5)
except AssertionError:
render_exc()
else:
self.fail("no error raised")
result_fib5 = [
'Traceback (most recent call last):',
f' File "{__file__}", line {lineno_fib + 11}, in _check_previous_line_repeated',
' fib(5)',
' ~~~^^^',
f' File "{__file__}", line {lineno_fib + 5}, in fib',
' return fib(number - 1) + fib(number - 2)',
' ~~~^^^^^^^^^^^^',
f' File "{__file__}", line {lineno_fib + 5}, in fib',
' return fib(number - 1) + fib(number - 2)',
' ~~~^^^^^^^^^^^^',
f' File "{__file__}", line {lineno_fib + 5}, in fib',
' return fib(number - 1) + fib(number - 2)',
' ~~~^^^^^^^^^^^^',
]
if self.DEBUG_RANGES:
result_fib5.append(f' File "{__file__}", line {lineno_fib + 5}, in fib')
result_fib5.append(' return fib(number - 1) + fib(number - 2)')
result_fib5.append(' ~~~^^^^^^^^^^^^')
else:
result_fib5.append(' [Previous line repeated 1 more time]')
result_fib5.append(f' File "{__file__}", line {lineno_fib + 2}, in fib')
result_fib5.append(' assert number > 0')
result_fib5.append(' ^^^^^^^^^^')
result_fib5.append('AssertionError')
expected = self._maybe_filter_debug_ranges(result_fib5)
actual = stderr_fib5.getvalue().splitlines()
self.assertEqual(actual, expected)

with captured_output("stderr") as stderr_fib6:
try:
fib(6)
except AssertionError:
render_exc()
else:
self.fail("no error raised")
result_fib6 = [
'Traceback (most recent call last):',
f' File "{__file__}", line {lineno_fib + 47}, in _check_previous_line_repeated',
' fib(6)',
' ~~~^^^',
f' File "{__file__}", line {lineno_fib + 5}, in fib',
' return fib(number - 1) + fib(number - 2)',
' ~~~^^^^^^^^^^^^',
f' File "{__file__}", line {lineno_fib + 5}, in fib',
' return fib(number - 1) + fib(number - 2)',
' ~~~^^^^^^^^^^^^',
f' File "{__file__}", line {lineno_fib + 5}, in fib',
' return fib(number - 1) + fib(number - 2)',
' ~~~^^^^^^^^^^^^',
]
if self.DEBUG_RANGES:
result_fib6.append(' [Previous line repeated 1 more time]')
result_fib6.append(f' File "{__file__}", line {lineno_fib + 5}, in fib')
result_fib6.append(' return fib(number - 1) + fib(number - 2)')
result_fib6.append(' ~~~^^^^^^^^^^^^')
else:
result_fib6.append(' [Previous line repeated 2 more times]')
result_fib6.append(f' File "{__file__}", line {lineno_fib + 2}, in fib')
result_fib6.append(' assert number > 0')
result_fib6.append(' ^^^^^^^^^^')
result_fib6.append('AssertionError')
expected = self._maybe_filter_debug_ranges(result_fib6)
actual = stderr_fib6.getvalue().splitlines()
self.assertEqual(actual, expected)

@requires_debug_ranges()
def test_recursive_traceback(self):
if self.DEBUG_RANGES:
self._check_recursive_traceback_display(traceback.print_exc)
self._check_previous_line_repeated(traceback.print_exc)
else:
from _testcapi import exception_print
def render_exc():
exception_print(sys.exception())
self._check_recursive_traceback_display(render_exc)
self._check_previous_line_repeated(render_exc)

def test_format_stack(self):
def fmt():
Expand Down
12 changes: 3 additions & 9 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -741,26 +741,20 @@ def format(self, **kwargs):
"""
colorize = kwargs.get("colorize", False)
result = []
last_file = None
last_line = None
last_name = None
last_formatted_frame = None
count = 0
for frame_summary in self:
formatted_frame = self.format_frame_summary(frame_summary, colorize=colorize)
if formatted_frame is None:
continue
if (last_file is None or last_file != frame_summary.filename or
last_line is None or last_line != frame_summary.lineno or
last_name is None or last_name != frame_summary.name):
if last_formatted_frame is None or last_formatted_frame != formatted_frame:
if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(
f' [Previous line repeated {count} more '
f'time{"s" if count > 1 else ""}]\n'
)
last_file = frame_summary.filename
last_line = frame_summary.lineno
last_name = frame_summary.name
last_formatted_frame = formatted_frame
count = 0
count += 1
if count > _RECURSIVE_CUTOFF:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve the condition of showing [Previous line repeated %d more time(s)]. Now repeated frames must have the same error locations (indicated by caret (^) and/or tilde (~) characters).
Loading