mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
Issue #26823: Abbreviate recursive tracebacks
Large sections of repeated lines in tracebacks are now abbreviated as "[Previous line repeated {count} more times]" by both the traceback module and the builtin traceback rendering. Patch by Emanuel Barry.
This commit is contained in:
parent
d61a2e75b5
commit
d00342347e
6 changed files with 222 additions and 4 deletions
|
@ -303,6 +303,137 @@ class TracebackFormatTests(unittest.TestCase):
|
|||
' traceback.print_stack()',
|
||||
])
|
||||
|
||||
# issue 26823 - Shrink recursive tracebacks
|
||||
def _check_recursive_traceback_display(self, render_exc):
|
||||
# Always show full diffs when this test fails
|
||||
# Note that rearranging things may require adjusting
|
||||
# the relative line numbers in the expected tracebacks
|
||||
self.maxDiff = None
|
||||
|
||||
# Check hitting the recursion limit
|
||||
def f():
|
||||
f()
|
||||
|
||||
with captured_output("stderr") as stderr_f:
|
||||
try:
|
||||
f()
|
||||
except RecursionError as exc:
|
||||
render_exc()
|
||||
else:
|
||||
self.fail("no recursion occurred")
|
||||
|
||||
lineno_f = f.__code__.co_firstlineno
|
||||
result_f = (
|
||||
'Traceback (most recent call last):\n'
|
||||
f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
|
||||
' f()\n'
|
||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||
' f()\n'
|
||||
# XXX: The following line changes depending on whether the tests
|
||||
# are run through the interactive interpreter or with -m
|
||||
# It also varies depending on the platform (stack size)
|
||||
# Fortunately, we don't care about exactness here, so we use regex
|
||||
r' \[Previous line repeated (\d+) more times\]' '\n'
|
||||
'RecursionError: maximum recursion depth exceeded\n'
|
||||
)
|
||||
|
||||
expected = result_f.splitlines()
|
||||
actual = stderr_f.getvalue().splitlines()
|
||||
|
||||
# Check the output text matches expectations
|
||||
# 2nd last line contains the repetition count
|
||||
self.assertEqual(actual[:-2], expected[:-2])
|
||||
self.assertRegex(actual[-2], expected[-2])
|
||||
self.assertEqual(actual[-1], expected[-1])
|
||||
|
||||
# Check the recursion count is roughly as expected
|
||||
rec_limit = sys.getrecursionlimit()
|
||||
self.assertIn(int(re.search(r"\d+", actual[-2]).group()), range(rec_limit-50, rec_limit))
|
||||
|
||||
# Check a known (limited) number of recursive invocations
|
||||
def g(count=10):
|
||||
if count:
|
||||
return g(count-1)
|
||||
raise ValueError
|
||||
|
||||
with captured_output("stderr") as stderr_g:
|
||||
try:
|
||||
g()
|
||||
except ValueError as exc:
|
||||
render_exc()
|
||||
else:
|
||||
self.fail("no value error was raised")
|
||||
|
||||
lineno_g = g.__code__.co_firstlineno
|
||||
result_g = (
|
||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||
' return g(count-1)\n'
|
||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||
' return g(count-1)\n'
|
||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||
' return g(count-1)\n'
|
||||
' [Previous line repeated 6 more times]\n'
|
||||
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
||||
' raise ValueError\n'
|
||||
'ValueError\n'
|
||||
)
|
||||
tb_line = (
|
||||
'Traceback (most recent call last):\n'
|
||||
f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
|
||||
' g()\n'
|
||||
)
|
||||
expected = (tb_line + result_g).splitlines()
|
||||
actual = stderr_g.getvalue().splitlines()
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
# Check 2 different repetitive sections
|
||||
def h(count=10):
|
||||
if count:
|
||||
return h(count-1)
|
||||
g()
|
||||
|
||||
with captured_output("stderr") as stderr_h:
|
||||
try:
|
||||
h()
|
||||
except ValueError as exc:
|
||||
render_exc()
|
||||
else:
|
||||
self.fail("no value error was raised")
|
||||
|
||||
lineno_h = h.__code__.co_firstlineno
|
||||
result_h = (
|
||||
'Traceback (most recent call last):\n'
|
||||
f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
|
||||
' h()\n'
|
||||
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
||||
' return h(count-1)\n'
|
||||
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
||||
' return h(count-1)\n'
|
||||
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
||||
' return h(count-1)\n'
|
||||
' [Previous line repeated 6 more times]\n'
|
||||
f' File "{__file__}", line {lineno_h+3}, in h\n'
|
||||
' g()\n'
|
||||
)
|
||||
expected = (result_h + result_g).splitlines()
|
||||
actual = stderr_h.getvalue().splitlines()
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_recursive_traceback_python(self):
|
||||
self._check_recursive_traceback_display(traceback.print_exc)
|
||||
|
||||
@cpython_only
|
||||
def test_recursive_traceback_cpython_internal(self):
|
||||
from _testcapi import exception_print
|
||||
def render_exc():
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
exception_print(exc_value)
|
||||
self._check_recursive_traceback_display(render_exc)
|
||||
|
||||
def test_format_stack(self):
|
||||
def fmt():
|
||||
return traceback.format_stack()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue