@@ -303,6 +303,137 @@ def prn():
303303 ' traceback.print_stack()' ,
304304 ])
305305
306+ # issue 26823 - Shrink recursive tracebacks
307+ def _check_recursive_traceback_display (self , render_exc ):
308+ # Always show full diffs when this test fails
309+ # Note that rearranging things may require adjusting
310+ # the relative line numbers in the expected tracebacks
311+ self .maxDiff = None
312+
313+ # Check hitting the recursion limit
314+ def f ():
315+ f ()
316+
317+ with captured_output ("stderr" ) as stderr_f :
318+ try :
319+ f ()
320+ except RecursionError as exc :
321+ render_exc ()
322+ else :
323+ self .fail ("no recursion occurred" )
324+
325+ lineno_f = f .__code__ .co_firstlineno
326+ result_f = (
327+ 'Traceback (most recent call last):\n '
328+ f' File "{ __file__ } ", line { lineno_f + 5 } , in _check_recursive_traceback_display\n '
329+ ' f()\n '
330+ f' File "{ __file__ } ", line { lineno_f + 1 } , in f\n '
331+ ' f()\n '
332+ f' File "{ __file__ } ", line { lineno_f + 1 } , in f\n '
333+ ' f()\n '
334+ f' File "{ __file__ } ", line { lineno_f + 1 } , in f\n '
335+ ' f()\n '
336+ # XXX: The following line changes depending on whether the tests
337+ # are run through the interactive interpreter or with -m
338+ # It also varies depending on the platform (stack size)
339+ # Fortunately, we don't care about exactness here, so we use regex
340+ r' \[Previous line repeated (\d+) more times\]' '\n '
341+ 'RecursionError: maximum recursion depth exceeded\n '
342+ )
343+
344+ expected = result_f .splitlines ()
345+ actual = stderr_f .getvalue ().splitlines ()
346+
347+ # Check the output text matches expectations
348+ # 2nd last line contains the repetition count
349+ self .assertEqual (actual [:- 2 ], expected [:- 2 ])
350+ self .assertRegex (actual [- 2 ], expected [- 2 ])
351+ self .assertEqual (actual [- 1 ], expected [- 1 ])
352+
353+ # Check the recursion count is roughly as expected
354+ rec_limit = sys .getrecursionlimit ()
355+ self .assertIn (int (re .search (r"\d+" , actual [- 2 ]).group ()), range (rec_limit - 50 , rec_limit ))
356+
357+ # Check a known (limited) number of recursive invocations
358+ def g (count = 10 ):
359+ if count :
360+ return g (count - 1 )
361+ raise ValueError
362+
363+ with captured_output ("stderr" ) as stderr_g :
364+ try :
365+ g ()
366+ except ValueError as exc :
367+ render_exc ()
368+ else :
369+ self .fail ("no value error was raised" )
370+
371+ lineno_g = g .__code__ .co_firstlineno
372+ result_g = (
373+ f' File "{ __file__ } ", line { lineno_g + 2 } , in g\n '
374+ ' return g(count-1)\n '
375+ f' File "{ __file__ } ", line { lineno_g + 2 } , in g\n '
376+ ' return g(count-1)\n '
377+ f' File "{ __file__ } ", line { lineno_g + 2 } , in g\n '
378+ ' return g(count-1)\n '
379+ ' [Previous line repeated 6 more times]\n '
380+ f' File "{ __file__ } ", line { lineno_g + 3 } , in g\n '
381+ ' raise ValueError\n '
382+ 'ValueError\n '
383+ )
384+ tb_line = (
385+ 'Traceback (most recent call last):\n '
386+ f' File "{ __file__ } ", line { lineno_g + 7 } , in _check_recursive_traceback_display\n '
387+ ' g()\n '
388+ )
389+ expected = (tb_line + result_g ).splitlines ()
390+ actual = stderr_g .getvalue ().splitlines ()
391+ self .assertEqual (actual , expected )
392+
393+ # Check 2 different repetitive sections
394+ def h (count = 10 ):
395+ if count :
396+ return h (count - 1 )
397+ g ()
398+
399+ with captured_output ("stderr" ) as stderr_h :
400+ try :
401+ h ()
402+ except ValueError as exc :
403+ render_exc ()
404+ else :
405+ self .fail ("no value error was raised" )
406+
407+ lineno_h = h .__code__ .co_firstlineno
408+ result_h = (
409+ 'Traceback (most recent call last):\n '
410+ f' File "{ __file__ } ", line { lineno_h + 7 } , in _check_recursive_traceback_display\n '
411+ ' h()\n '
412+ f' File "{ __file__ } ", line { lineno_h + 2 } , in h\n '
413+ ' return h(count-1)\n '
414+ f' File "{ __file__ } ", line { lineno_h + 2 } , in h\n '
415+ ' return h(count-1)\n '
416+ f' File "{ __file__ } ", line { lineno_h + 2 } , in h\n '
417+ ' return h(count-1)\n '
418+ ' [Previous line repeated 6 more times]\n '
419+ f' File "{ __file__ } ", line { lineno_h + 3 } , in h\n '
420+ ' g()\n '
421+ )
422+ expected = (result_h + result_g ).splitlines ()
423+ actual = stderr_h .getvalue ().splitlines ()
424+ self .assertEqual (actual , expected )
425+
426+ def test_recursive_traceback_python (self ):
427+ self ._check_recursive_traceback_display (traceback .print_exc )
428+
429+ @cpython_only
430+ def test_recursive_traceback_cpython_internal (self ):
431+ from _testcapi import exception_print
432+ def render_exc ():
433+ exc_type , exc_value , exc_tb = sys .exc_info ()
434+ exception_print (exc_value )
435+ self ._check_recursive_traceback_display (render_exc )
436+
306437 def test_format_stack (self ):
307438 def fmt ():
308439 return traceback .format_stack ()
0 commit comments