summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_traceback.py
diff options
context:
space:
mode:
authorNick Coghlan <ncoghlan@gmail.com>2016-08-15 03:11:34 (GMT)
committerNick Coghlan <ncoghlan@gmail.com>2016-08-15 03:11:34 (GMT)
commitd00342347e467981b52368235b99a22dc264dab1 (patch)
treee7ea356a2c4e93f592fd7f3e52e12dfeccfaf13e /Lib/test/test_traceback.py
parentd61a2e75b5218f0f89b4f623713004edb9512180 (diff)
downloadcpython-d00342347e467981b52368235b99a22dc264dab1.zip
cpython-d00342347e467981b52368235b99a22dc264dab1.tar.gz
cpython-d00342347e467981b52368235b99a22dc264dab1.tar.bz2
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.
Diffstat (limited to 'Lib/test/test_traceback.py')
-rw-r--r--Lib/test/test_traceback.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 787409c..665abb4 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -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()