summaryrefslogtreecommitdiff
path: root/Lib/test/test_traceback.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_traceback.py')
-rw-r--r--Lib/test/test_traceback.py147
1 files changed, 139 insertions, 8 deletions
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 49cedda550..7276bc7ee7 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -125,12 +125,12 @@ class TracebackCases(unittest.TestCase):
def do_test(firstlines, message, charset, lineno):
# Raise the message in a subprocess, and catch the output
try:
- output = open(TESTFN, "w", encoding=charset)
- output.write("""{0}if 1:
- import traceback;
- raise RuntimeError('{1}')
- """.format(firstlines, message))
- output.close()
+ with open(TESTFN, "w", encoding=charset) as output:
+ output.write("""{0}if 1:
+ import traceback;
+ raise RuntimeError('{1}')
+ """.format(firstlines, message))
+
process = subprocess.Popen([sys.executable, TESTFN],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
@@ -171,8 +171,8 @@ class TracebackCases(unittest.TestCase):
text, charset, 5)
do_test(" \t\f\n# coding: {0}\n".format(charset),
text, charset, 5)
- # Issue #18960: coding spec should has no effect
- do_test("0\n# coding: GBK\n", "h\xe9 ho", 'utf-8', 5)
+ # Issue #18960: coding spec should have no effect
+ do_test("x=0\n# coding: GBK\n", "h\xe9 ho", 'utf-8', 5)
@support.requires_type_collecting
def test_print_traceback_at_exit(self):
@@ -299,6 +299,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-60, 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()