• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Test cases for traceback module"""
2
3from collections import namedtuple
4from io import StringIO
5import linecache
6import sys
7import types
8import inspect
9import builtins
10import unittest
11import unittest.mock
12import re
13import tempfile
14import random
15import string
16from test import support
17import shutil
18from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
19                          requires_debug_ranges, has_no_debug_ranges,
20                          requires_subprocess)
21from test.support.os_helper import TESTFN, unlink
22from test.support.script_helper import assert_python_ok, assert_python_failure
23from test.support.import_helper import forget
24from test.support import force_not_colorized
25
26import json
27import textwrap
28import traceback
29from functools import partial
30from pathlib import Path
31import _colorize
32
33MODULE_PREFIX = f'{__name__}.' if __name__ == '__main__' else ''
34
35test_code = namedtuple('code', ['co_filename', 'co_name'])
36test_code.co_positions = lambda _: iter([(6, 6, 0, 0)])
37test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
38test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti'])
39
40
41LEVENSHTEIN_DATA_FILE = Path(__file__).parent / 'levenshtein_examples.json'
42
43
44class TracebackCases(unittest.TestCase):
45    # For now, a very minimal set of tests.  I want to be sure that
46    # formatting of SyntaxErrors works based on changes for 2.1.
47    def setUp(self):
48        super().setUp()
49        self.colorize = _colorize.COLORIZE
50        _colorize.COLORIZE = False
51
52    def tearDown(self):
53        super().tearDown()
54        _colorize.COLORIZE = self.colorize
55
56    def get_exception_format(self, func, exc):
57        try:
58            func()
59        except exc as value:
60            return traceback.format_exception_only(exc, value)
61        else:
62            raise ValueError("call did not raise exception")
63
64    def syntax_error_with_caret(self):
65        compile("def fact(x):\n\treturn x!\n", "?", "exec")
66
67    def syntax_error_with_caret_2(self):
68        compile("1 +\n", "?", "exec")
69
70    def syntax_error_with_caret_range(self):
71        compile("f(x, y for y in range(30), z)", "?", "exec")
72
73    def syntax_error_bad_indentation(self):
74        compile("def spam():\n  print(1)\n print(2)", "?", "exec")
75
76    def syntax_error_with_caret_non_ascii(self):
77        compile('Python = "\u1e54\xfd\u0163\u0125\xf2\xf1" +', "?", "exec")
78
79    def syntax_error_bad_indentation2(self):
80        compile(" print(2)", "?", "exec")
81
82    def tokenizer_error_with_caret_range(self):
83        compile("blech  (  ", "?", "exec")
84
85    def test_caret(self):
86        err = self.get_exception_format(self.syntax_error_with_caret,
87                                        SyntaxError)
88        self.assertEqual(len(err), 4)
89        self.assertTrue(err[1].strip() == "return x!")
90        self.assertIn("^", err[2]) # third line has caret
91        self.assertEqual(err[1].find("!"), err[2].find("^")) # in the right place
92        self.assertEqual(err[2].count("^"), 1)
93
94        err = self.get_exception_format(self.syntax_error_with_caret_2,
95                                        SyntaxError)
96        self.assertIn("^", err[2]) # third line has caret
97        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
98        self.assertEqual(err[1].find("+") + 1, err[2].find("^"))  # in the right place
99        self.assertEqual(err[2].count("^"), 1)
100
101        err = self.get_exception_format(self.syntax_error_with_caret_non_ascii,
102                                        SyntaxError)
103        self.assertIn("^", err[2]) # third line has caret
104        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
105        self.assertEqual(err[1].find("+") + 1, err[2].find("^"))  # in the right place
106        self.assertEqual(err[2].count("^"), 1)
107
108        err = self.get_exception_format(self.syntax_error_with_caret_range,
109                                        SyntaxError)
110        self.assertIn("^", err[2]) # third line has caret
111        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
112        self.assertEqual(err[1].find("y"), err[2].find("^"))  # in the right place
113        self.assertEqual(err[2].count("^"), len("y for y in range(30)"))
114
115        err = self.get_exception_format(self.tokenizer_error_with_caret_range,
116                                        SyntaxError)
117        self.assertIn("^", err[2]) # third line has caret
118        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
119        self.assertEqual(err[1].find("("), err[2].find("^"))  # in the right place
120        self.assertEqual(err[2].count("^"), 1)
121
122    def test_nocaret(self):
123        exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
124        err = traceback.format_exception_only(SyntaxError, exc)
125        self.assertEqual(len(err), 3)
126        self.assertEqual(err[1].strip(), "bad syntax")
127
128    @force_not_colorized
129    def test_no_caret_with_no_debug_ranges_flag(self):
130        # Make sure that if `-X no_debug_ranges` is used, there are no carets
131        # in the traceback.
132        try:
133            with open(TESTFN, 'w') as f:
134                f.write("x = 1 / 0\n")
135
136            _, _, stderr = assert_python_failure(
137                '-X', 'no_debug_ranges', TESTFN)
138
139            lines = stderr.splitlines()
140            self.assertEqual(len(lines), 4)
141            self.assertEqual(lines[0], b'Traceback (most recent call last):')
142            self.assertIn(b'line 1, in <module>', lines[1])
143            self.assertEqual(lines[2], b'    x = 1 / 0')
144            self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
145        finally:
146            unlink(TESTFN)
147
148    def test_no_caret_with_no_debug_ranges_flag_python_traceback(self):
149        code = textwrap.dedent("""
150            import traceback
151            try:
152                x = 1 / 0
153            except ZeroDivisionError:
154                traceback.print_exc()
155            """)
156        try:
157            with open(TESTFN, 'w') as f:
158                f.write(code)
159
160            _, _, stderr = assert_python_ok(
161                '-X', 'no_debug_ranges', TESTFN)
162
163            lines = stderr.splitlines()
164            self.assertEqual(len(lines), 4)
165            self.assertEqual(lines[0], b'Traceback (most recent call last):')
166            self.assertIn(b'line 4, in <module>', lines[1])
167            self.assertEqual(lines[2], b'    x = 1 / 0')
168            self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
169        finally:
170            unlink(TESTFN)
171
172    def test_recursion_error_during_traceback(self):
173        code = textwrap.dedent("""
174                import sys
175                from weakref import ref
176
177                sys.setrecursionlimit(15)
178
179                def f():
180                    ref(lambda: 0, [])
181                    f()
182
183                try:
184                    f()
185                except RecursionError:
186                    pass
187        """)
188        try:
189            with open(TESTFN, 'w') as f:
190                f.write(code)
191
192            rc, _, _ = assert_python_ok(TESTFN)
193            self.assertEqual(rc, 0)
194        finally:
195            unlink(TESTFN)
196
197    def test_bad_indentation(self):
198        err = self.get_exception_format(self.syntax_error_bad_indentation,
199                                        IndentationError)
200        self.assertEqual(len(err), 4)
201        self.assertEqual(err[1].strip(), "print(2)")
202        self.assertIn("^", err[2])
203        self.assertEqual(err[1].find(")") + 1, err[2].find("^"))
204
205        # No caret for "unexpected indent"
206        err = self.get_exception_format(self.syntax_error_bad_indentation2,
207                                        IndentationError)
208        self.assertEqual(len(err), 3)
209        self.assertEqual(err[1].strip(), "print(2)")
210
211    def test_base_exception(self):
212        # Test that exceptions derived from BaseException are formatted right
213        e = KeyboardInterrupt()
214        lst = traceback.format_exception_only(e.__class__, e)
215        self.assertEqual(lst, ['KeyboardInterrupt\n'])
216
217    def test_format_exception_only_bad__str__(self):
218        class X(Exception):
219            def __str__(self):
220                1/0
221        err = traceback.format_exception_only(X, X())
222        self.assertEqual(len(err), 1)
223        str_value = '<exception str() failed>'
224        if X.__module__ in ('__main__', 'builtins'):
225            str_name = X.__qualname__
226        else:
227            str_name = '.'.join([X.__module__, X.__qualname__])
228        self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
229
230    def test_format_exception_group_without_show_group(self):
231        eg = ExceptionGroup('A', [ValueError('B')])
232        err = traceback.format_exception_only(eg)
233        self.assertEqual(err, ['ExceptionGroup: A (1 sub-exception)\n'])
234
235    def test_format_exception_group(self):
236        eg = ExceptionGroup('A', [ValueError('B')])
237        err = traceback.format_exception_only(eg, show_group=True)
238        self.assertEqual(err, [
239            'ExceptionGroup: A (1 sub-exception)\n',
240            '   ValueError: B\n',
241        ])
242
243    def test_format_base_exception_group(self):
244        eg = BaseExceptionGroup('A', [BaseException('B')])
245        err = traceback.format_exception_only(eg, show_group=True)
246        self.assertEqual(err, [
247            'BaseExceptionGroup: A (1 sub-exception)\n',
248            '   BaseException: B\n',
249        ])
250
251    def test_format_exception_group_with_note(self):
252        exc = ValueError('B')
253        exc.add_note('Note')
254        eg = ExceptionGroup('A', [exc])
255        err = traceback.format_exception_only(eg, show_group=True)
256        self.assertEqual(err, [
257            'ExceptionGroup: A (1 sub-exception)\n',
258            '   ValueError: B\n',
259            '   Note\n',
260        ])
261
262    def test_format_exception_group_explicit_class(self):
263        eg = ExceptionGroup('A', [ValueError('B')])
264        err = traceback.format_exception_only(ExceptionGroup, eg, show_group=True)
265        self.assertEqual(err, [
266            'ExceptionGroup: A (1 sub-exception)\n',
267            '   ValueError: B\n',
268        ])
269
270    def test_format_exception_group_multiple_exceptions(self):
271        eg = ExceptionGroup('A', [ValueError('B'), TypeError('C')])
272        err = traceback.format_exception_only(eg, show_group=True)
273        self.assertEqual(err, [
274            'ExceptionGroup: A (2 sub-exceptions)\n',
275            '   ValueError: B\n',
276            '   TypeError: C\n',
277        ])
278
279    def test_format_exception_group_multiline_messages(self):
280        eg = ExceptionGroup('A\n1', [ValueError('B\n2')])
281        err = traceback.format_exception_only(eg, show_group=True)
282        self.assertEqual(err, [
283            'ExceptionGroup: A\n1 (1 sub-exception)\n',
284            '   ValueError: B\n',
285            '   2\n',
286        ])
287
288    def test_format_exception_group_multiline2_messages(self):
289        exc = ValueError('B\n\n2\n')
290        exc.add_note('\nC\n\n3')
291        eg = ExceptionGroup('A\n\n1\n', [exc, IndexError('D')])
292        err = traceback.format_exception_only(eg, show_group=True)
293        self.assertEqual(err, [
294            'ExceptionGroup: A\n\n1\n (2 sub-exceptions)\n',
295            '   ValueError: B\n',
296            '   \n',
297            '   2\n',
298            '   \n',
299            '   \n',  # first char of `note`
300            '   C\n',
301            '   \n',
302            '   3\n', # note ends
303            '   IndexError: D\n',
304        ])
305
306    def test_format_exception_group_syntax_error(self):
307        exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
308        eg = ExceptionGroup('A\n1', [exc])
309        err = traceback.format_exception_only(eg, show_group=True)
310        self.assertEqual(err, [
311            'ExceptionGroup: A\n1 (1 sub-exception)\n',
312            '     File "x.py", line 23\n',
313            '       bad syntax\n',
314            '   SyntaxError: error\n',
315        ])
316
317    def test_format_exception_group_nested_with_notes(self):
318        exc = IndexError('D')
319        exc.add_note('Note\nmultiline')
320        eg = ExceptionGroup('A', [
321            ValueError('B'),
322            ExceptionGroup('C', [exc, LookupError('E')]),
323            TypeError('F'),
324        ])
325        err = traceback.format_exception_only(eg, show_group=True)
326        self.assertEqual(err, [
327            'ExceptionGroup: A (3 sub-exceptions)\n',
328            '   ValueError: B\n',
329            '   ExceptionGroup: C (2 sub-exceptions)\n',
330            '      IndexError: D\n',
331            '      Note\n',
332            '      multiline\n',
333            '      LookupError: E\n',
334            '   TypeError: F\n',
335        ])
336
337    def test_format_exception_group_with_tracebacks(self):
338        def f():
339            try:
340                1 / 0
341            except ZeroDivisionError as e:
342                return e
343
344        def g():
345            try:
346                raise TypeError('g')
347            except TypeError as e:
348                return e
349
350        eg = ExceptionGroup('A', [
351            f(),
352            ExceptionGroup('B', [g()]),
353        ])
354        err = traceback.format_exception_only(eg, show_group=True)
355        self.assertEqual(err, [
356            'ExceptionGroup: A (2 sub-exceptions)\n',
357            '   ZeroDivisionError: division by zero\n',
358            '   ExceptionGroup: B (1 sub-exception)\n',
359            '      TypeError: g\n',
360        ])
361
362    def test_format_exception_group_with_cause(self):
363        def f():
364            try:
365                try:
366                    1 / 0
367                except ZeroDivisionError:
368                    raise ValueError(0)
369            except Exception as e:
370                return e
371
372        eg = ExceptionGroup('A', [f()])
373        err = traceback.format_exception_only(eg, show_group=True)
374        self.assertEqual(err, [
375            'ExceptionGroup: A (1 sub-exception)\n',
376            '   ValueError: 0\n',
377        ])
378
379    @requires_subprocess()
380    @force_not_colorized
381    def test_encoded_file(self):
382        # Test that tracebacks are correctly printed for encoded source files:
383        # - correct line number (Issue2384)
384        # - respect file encoding (Issue3975)
385        import sys, subprocess
386
387        # The spawned subprocess has its stdout redirected to a PIPE, and its
388        # encoding may be different from the current interpreter, on Windows
389        # at least.
390        process = subprocess.Popen([sys.executable, "-c",
391                                    "import sys; print(sys.stdout.encoding)"],
392                                   stdout=subprocess.PIPE,
393                                   stderr=subprocess.STDOUT)
394        stdout, stderr = process.communicate()
395        output_encoding = str(stdout, 'ascii').splitlines()[0]
396
397        def do_test(firstlines, message, charset, lineno):
398            # Raise the message in a subprocess, and catch the output
399            try:
400                with open(TESTFN, "w", encoding=charset) as output:
401                    output.write("""{0}if 1:
402                        import traceback;
403                        raise RuntimeError('{1}')
404                        """.format(firstlines, message))
405
406                process = subprocess.Popen([sys.executable, TESTFN],
407                    stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
408                stdout, stderr = process.communicate()
409                stdout = stdout.decode(output_encoding).splitlines()
410            finally:
411                unlink(TESTFN)
412
413            # The source lines are encoded with the 'backslashreplace' handler
414            encoded_message = message.encode(output_encoding,
415                                             'backslashreplace')
416            # and we just decoded them with the output_encoding.
417            message_ascii = encoded_message.decode(output_encoding)
418
419            err_line = "raise RuntimeError('{0}')".format(message_ascii)
420            err_msg = "RuntimeError: {0}".format(message_ascii)
421
422            self.assertIn(("line %s" % lineno), stdout[1],
423                "Invalid line number: {0!r} instead of {1}".format(
424                    stdout[1], lineno))
425            self.assertTrue(stdout[2].endswith(err_line),
426                "Invalid traceback line: {0!r} instead of {1!r}".format(
427                    stdout[2], err_line))
428            actual_err_msg = stdout[3]
429            self.assertTrue(actual_err_msg == err_msg,
430                "Invalid error message: {0!r} instead of {1!r}".format(
431                    actual_err_msg, err_msg))
432
433        do_test("", "foo", "ascii", 3)
434        for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
435            if charset == "ascii":
436                text = "foo"
437            elif charset == "GBK":
438                text = "\u4E02\u5100"
439            else:
440                text = "h\xe9 ho"
441            do_test("# coding: {0}\n".format(charset),
442                    text, charset, 4)
443            do_test("#!shebang\n# coding: {0}\n".format(charset),
444                    text, charset, 5)
445            do_test(" \t\f\n# coding: {0}\n".format(charset),
446                    text, charset, 5)
447        # Issue #18960: coding spec should have no effect
448        do_test("x=0\n# coding: GBK\n", "h\xe9 ho", 'utf-8', 5)
449
450    def test_print_traceback_at_exit(self):
451        # Issue #22599: Ensure that it is possible to use the traceback module
452        # to display an exception at Python exit
453        code = textwrap.dedent("""
454            import sys
455            import traceback
456
457            class PrintExceptionAtExit(object):
458                def __init__(self):
459                    try:
460                        x = 1 / 0
461                    except Exception as e:
462                        self.exc = e
463                        # self.exc.__traceback__ contains frames:
464                        # explicitly clear the reference to self in the current
465                        # frame to break a reference cycle
466                        self = None
467
468                def __del__(self):
469                    traceback.print_exception(self.exc)
470
471            # Keep a reference in the module namespace to call the destructor
472            # when the module is unloaded
473            obj = PrintExceptionAtExit()
474        """)
475        rc, stdout, stderr = assert_python_ok('-c', code)
476        expected = [b'Traceback (most recent call last):',
477                    b'  File "<string>", line 8, in __init__',
478                    b'    x = 1 / 0',
479                    b'        ^^^^^',
480                    b'ZeroDivisionError: division by zero']
481        self.assertEqual(stderr.splitlines(), expected)
482
483    def test_print_exception(self):
484        output = StringIO()
485        traceback.print_exception(
486            Exception, Exception("projector"), None, file=output
487        )
488        self.assertEqual(output.getvalue(), "Exception: projector\n")
489
490    def test_print_exception_exc(self):
491        output = StringIO()
492        traceback.print_exception(Exception("projector"), file=output)
493        self.assertEqual(output.getvalue(), "Exception: projector\n")
494
495    def test_format_exception_exc(self):
496        e = Exception("projector")
497        output = traceback.format_exception(e)
498        self.assertEqual(output, ["Exception: projector\n"])
499        with self.assertRaisesRegex(ValueError, 'Both or neither'):
500            traceback.format_exception(e.__class__, e)
501        with self.assertRaisesRegex(ValueError, 'Both or neither'):
502            traceback.format_exception(e.__class__, tb=e.__traceback__)
503        with self.assertRaisesRegex(TypeError, 'required positional argument'):
504            traceback.format_exception(exc=e)
505
506    def test_format_exception_only_exc(self):
507        output = traceback.format_exception_only(Exception("projector"))
508        self.assertEqual(output, ["Exception: projector\n"])
509
510    def test_exception_is_None(self):
511        NONE_EXC_STRING = 'NoneType: None\n'
512        excfile = StringIO()
513        traceback.print_exception(None, file=excfile)
514        self.assertEqual(excfile.getvalue(), NONE_EXC_STRING)
515
516        excfile = StringIO()
517        traceback.print_exception(None, None, None, file=excfile)
518        self.assertEqual(excfile.getvalue(), NONE_EXC_STRING)
519
520        excfile = StringIO()
521        traceback.print_exc(None, file=excfile)
522        self.assertEqual(excfile.getvalue(), NONE_EXC_STRING)
523
524        self.assertEqual(traceback.format_exc(None), NONE_EXC_STRING)
525        self.assertEqual(traceback.format_exception(None), [NONE_EXC_STRING])
526        self.assertEqual(
527            traceback.format_exception(None, None, None), [NONE_EXC_STRING])
528        self.assertEqual(
529            traceback.format_exception_only(None), [NONE_EXC_STRING])
530        self.assertEqual(
531            traceback.format_exception_only(None, None), [NONE_EXC_STRING])
532
533    def test_signatures(self):
534        self.assertEqual(
535            str(inspect.signature(traceback.print_exception)),
536            ('(exc, /, value=<implicit>, tb=<implicit>, '
537             'limit=None, file=None, chain=True, **kwargs)'))
538
539        self.assertEqual(
540            str(inspect.signature(traceback.format_exception)),
541            ('(exc, /, value=<implicit>, tb=<implicit>, limit=None, '
542             'chain=True, **kwargs)'))
543
544        self.assertEqual(
545            str(inspect.signature(traceback.format_exception_only)),
546            '(exc, /, value=<implicit>, *, show_group=False, **kwargs)')
547
548
549class PurePythonExceptionFormattingMixin:
550    def get_exception(self, callable, slice_start=0, slice_end=-1):
551        try:
552            callable()
553        except BaseException:
554            return traceback.format_exc().splitlines()[slice_start:slice_end]
555        else:
556            self.fail("No exception thrown.")
557
558    callable_line = get_exception.__code__.co_firstlineno + 2
559
560
561class CAPIExceptionFormattingMixin:
562    LEGACY = 0
563
564    def get_exception(self, callable, slice_start=0, slice_end=-1):
565        from _testcapi import exception_print
566        try:
567            callable()
568            self.fail("No exception thrown.")
569        except Exception as e:
570            with captured_output("stderr") as tbstderr:
571                exception_print(e, self.LEGACY)
572            return tbstderr.getvalue().splitlines()[slice_start:slice_end]
573
574    callable_line = get_exception.__code__.co_firstlineno + 3
575
576class CAPIExceptionFormattingLegacyMixin(CAPIExceptionFormattingMixin):
577    LEGACY = 1
578
579@requires_debug_ranges()
580class TracebackErrorLocationCaretTestBase:
581    """
582    Tests for printing code error expressions as part of PEP 657
583    """
584    def test_basic_caret(self):
585        # NOTE: In caret tests, "if True:" is used as a way to force indicator
586        #   display, since the raising expression spans only part of the line.
587        def f():
588            if True: raise ValueError("basic caret tests")
589
590        lineno_f = f.__code__.co_firstlineno
591        expected_f = (
592            'Traceback (most recent call last):\n'
593            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
594            '    callable()\n'
595            '    ~~~~~~~~^^\n'
596            f'  File "{__file__}", line {lineno_f+1}, in f\n'
597            '    if True: raise ValueError("basic caret tests")\n'
598            '             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
599        )
600        result_lines = self.get_exception(f)
601        self.assertEqual(result_lines, expected_f.splitlines())
602
603    def test_line_with_unicode(self):
604        # Make sure that even if a line contains multi-byte unicode characters
605        # the correct carets are printed.
606        def f_with_unicode():
607            if True: raise ValueError("Ĥellö Wörld")
608
609        lineno_f = f_with_unicode.__code__.co_firstlineno
610        expected_f = (
611            'Traceback (most recent call last):\n'
612            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
613            '    callable()\n'
614            '    ~~~~~~~~^^\n'
615            f'  File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
616            '    if True: raise ValueError("Ĥellö Wörld")\n'
617            '             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
618        )
619        result_lines = self.get_exception(f_with_unicode)
620        self.assertEqual(result_lines, expected_f.splitlines())
621
622    def test_caret_in_type_annotation(self):
623        def f_with_type():
624            def foo(a: THIS_DOES_NOT_EXIST ) -> int:
625                return 0
626
627        lineno_f = f_with_type.__code__.co_firstlineno
628        expected_f = (
629            'Traceback (most recent call last):\n'
630            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
631            '    callable()\n'
632            '    ~~~~~~~~^^\n'
633            f'  File "{__file__}", line {lineno_f+1}, in f_with_type\n'
634            '    def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
635            '               ^^^^^^^^^^^^^^^^^^^\n'
636        )
637        result_lines = self.get_exception(f_with_type)
638        self.assertEqual(result_lines, expected_f.splitlines())
639
640    def test_caret_multiline_expression(self):
641        # Make sure no carets are printed for expressions spanning multiple
642        # lines.
643        def f_with_multiline():
644            if True: raise ValueError(
645                "error over multiple lines"
646            )
647
648        lineno_f = f_with_multiline.__code__.co_firstlineno
649        expected_f = (
650            'Traceback (most recent call last):\n'
651            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
652            '    callable()\n'
653            '    ~~~~~~~~^^\n'
654            f'  File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
655            '    if True: raise ValueError(\n'
656            '             ^^^^^^^^^^^^^^^^^\n'
657            '        "error over multiple lines"\n'
658            '        ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
659            '    )\n'
660            '    ^'
661        )
662        result_lines = self.get_exception(f_with_multiline)
663        self.assertEqual(result_lines, expected_f.splitlines())
664
665    def test_caret_multiline_expression_syntax_error(self):
666        # Make sure an expression spanning multiple lines that has
667        # a syntax error is correctly marked with carets.
668        code = textwrap.dedent("""
669        def foo(*args, **kwargs):
670            pass
671
672        a, b, c = 1, 2, 3
673
674        foo(a, z
675                for z in
676                    range(10), b, c)
677        """)
678
679        def f_with_multiline():
680            # Need to defer the compilation until in self.get_exception(..)
681            return compile(code, "?", "exec")
682
683        lineno_f = f_with_multiline.__code__.co_firstlineno
684
685        expected_f = (
686            'Traceback (most recent call last):\n'
687            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
688            '    callable()\n'
689            '    ~~~~~~~~^^\n'
690            f'  File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
691            '    return compile(code, "?", "exec")\n'
692            '  File "?", line 7\n'
693            '    foo(a, z\n'
694            '           ^'
695            )
696
697        result_lines = self.get_exception(f_with_multiline)
698        self.assertEqual(result_lines, expected_f.splitlines())
699
700        # Check custom error messages covering multiple lines
701        code = textwrap.dedent("""
702        dummy_call(
703            "dummy value"
704            foo="bar",
705        )
706        """)
707
708        def f_with_multiline():
709            # Need to defer the compilation until in self.get_exception(..)
710            return compile(code, "?", "exec")
711
712        lineno_f = f_with_multiline.__code__.co_firstlineno
713
714        expected_f = (
715            'Traceback (most recent call last):\n'
716            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
717            '    callable()\n'
718            '    ~~~~~~~~^^\n'
719            f'  File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
720            '    return compile(code, "?", "exec")\n'
721            '  File "?", line 3\n'
722            '    "dummy value"\n'
723            '    ^^^^^^^^^^^^^'
724            )
725
726        result_lines = self.get_exception(f_with_multiline)
727        self.assertEqual(result_lines, expected_f.splitlines())
728
729    def test_caret_multiline_expression_bin_op(self):
730        # Make sure no carets are printed for expressions spanning multiple
731        # lines.
732        def f_with_multiline():
733            return (
734                2 + 1 /
735                0
736            )
737
738        lineno_f = f_with_multiline.__code__.co_firstlineno
739        expected_f = (
740            'Traceback (most recent call last):\n'
741            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
742            '    callable()\n'
743            '    ~~~~~~~~^^\n'
744            f'  File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
745            '    2 + 1 /\n'
746            '        ~~^\n'
747            '    0\n'
748            '    ~'
749        )
750        result_lines = self.get_exception(f_with_multiline)
751        self.assertEqual(result_lines, expected_f.splitlines())
752
753    def test_caret_for_binary_operators(self):
754        def f_with_binary_operator():
755            divisor = 20
756            return 10 + divisor / 0 + 30
757
758        lineno_f = f_with_binary_operator.__code__.co_firstlineno
759        expected_error = (
760            'Traceback (most recent call last):\n'
761            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
762            '    callable()\n'
763            '    ~~~~~~~~^^\n'
764            f'  File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
765            '    return 10 + divisor / 0 + 30\n'
766            '                ~~~~~~~~^~~\n'
767        )
768        result_lines = self.get_exception(f_with_binary_operator)
769        self.assertEqual(result_lines, expected_error.splitlines())
770
771    def test_caret_for_binary_operators_with_unicode(self):
772        def f_with_binary_operator():
773            áóí = 20
774            return 10 + áóí / 0 + 30
775
776        lineno_f = f_with_binary_operator.__code__.co_firstlineno
777        expected_error = (
778            'Traceback (most recent call last):\n'
779            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
780            '    callable()\n'
781            '    ~~~~~~~~^^\n'
782            f'  File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
783            '    return 10 + áóí / 0 + 30\n'
784            '                ~~~~^~~\n'
785        )
786        result_lines = self.get_exception(f_with_binary_operator)
787        self.assertEqual(result_lines, expected_error.splitlines())
788
789    def test_caret_for_binary_operators_two_char(self):
790        def f_with_binary_operator():
791            divisor = 20
792            return 10 + divisor // 0 + 30
793
794        lineno_f = f_with_binary_operator.__code__.co_firstlineno
795        expected_error = (
796            'Traceback (most recent call last):\n'
797            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
798            '    callable()\n'
799            '    ~~~~~~~~^^\n'
800            f'  File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
801            '    return 10 + divisor // 0 + 30\n'
802            '                ~~~~~~~~^^~~\n'
803        )
804        result_lines = self.get_exception(f_with_binary_operator)
805        self.assertEqual(result_lines, expected_error.splitlines())
806
807    def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
808        def f_with_binary_operator():
809            a = 1
810            b = c = ""
811            return ( a   )   +b + c
812
813        lineno_f = f_with_binary_operator.__code__.co_firstlineno
814        expected_error = (
815            'Traceback (most recent call last):\n'
816            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
817            '    callable()\n'
818            '    ~~~~~~~~^^\n'
819            f'  File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
820            '    return ( a   )   +b + c\n'
821            '           ~~~~~~~~~~^~\n'
822        )
823        result_lines = self.get_exception(f_with_binary_operator)
824        self.assertEqual(result_lines, expected_error.splitlines())
825
826    def test_caret_for_binary_operators_multiline(self):
827        def f_with_binary_operator():
828            b = 1
829            c = ""
830            a = b    \
831         +\
832               c  # test
833            return a
834
835        lineno_f = f_with_binary_operator.__code__.co_firstlineno
836        expected_error = (
837            'Traceback (most recent call last):\n'
838            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
839            '    callable()\n'
840            '    ~~~~~~~~^^\n'
841            f'  File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
842            '       a = b    \\\n'
843            '           ~~~~~~\n'
844            '    +\\\n'
845            '    ^~\n'
846            '          c  # test\n'
847            '          ~\n'
848        )
849        result_lines = self.get_exception(f_with_binary_operator)
850        self.assertEqual(result_lines, expected_error.splitlines())
851
852    def test_caret_for_binary_operators_multiline_two_char(self):
853        def f_with_binary_operator():
854            b = 1
855            c = ""
856            a = (
857                (b  # test +
858                    )  \
859                # +
860            << (c  # test
861                \
862            )  # test
863            )
864            return a
865
866        lineno_f = f_with_binary_operator.__code__.co_firstlineno
867        expected_error = (
868            'Traceback (most recent call last):\n'
869            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
870            '    callable()\n'
871            '    ~~~~~~~~^^\n'
872            f'  File "{__file__}", line {lineno_f+4}, in f_with_binary_operator\n'
873            '        (b  # test +\n'
874            '        ~~~~~~~~~~~~\n'
875            '            )  \\\n'
876            '            ~~~~\n'
877            '        # +\n'
878            '        ~~~\n'
879            '    << (c  # test\n'
880            '    ^^~~~~~~~~~~~\n'
881            '        \\\n'
882            '        ~\n'
883            '    )  # test\n'
884            '    ~\n'
885        )
886        result_lines = self.get_exception(f_with_binary_operator)
887        self.assertEqual(result_lines, expected_error.splitlines())
888
889    def test_caret_for_binary_operators_multiline_with_unicode(self):
890        def f_with_binary_operator():
891            b = 1
892            a = ("ááá" +
893                "áá") + b
894            return a
895
896        lineno_f = f_with_binary_operator.__code__.co_firstlineno
897        expected_error = (
898            'Traceback (most recent call last):\n'
899            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
900            '    callable()\n'
901            '    ~~~~~~~~^^\n'
902            f'  File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
903            '    a = ("ááá" +\n'
904            '        ~~~~~~~~\n'
905            '        "áá") + b\n'
906            '        ~~~~~~^~~\n'
907        )
908        result_lines = self.get_exception(f_with_binary_operator)
909        self.assertEqual(result_lines, expected_error.splitlines())
910
911    def test_caret_for_subscript(self):
912        def f_with_subscript():
913            some_dict = {'x': {'y': None}}
914            return some_dict['x']['y']['z']
915
916        lineno_f = f_with_subscript.__code__.co_firstlineno
917        expected_error = (
918            'Traceback (most recent call last):\n'
919            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
920            '    callable()\n'
921            '    ~~~~~~~~^^\n'
922            f'  File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
923            "    return some_dict['x']['y']['z']\n"
924            '           ~~~~~~~~~~~~~~~~~~~^^^^^\n'
925        )
926        result_lines = self.get_exception(f_with_subscript)
927        self.assertEqual(result_lines, expected_error.splitlines())
928
929    def test_caret_for_subscript_unicode(self):
930        def f_with_subscript():
931            some_dict = {'ó': {'á': {'í': {'theta': 1}}}}
932            return some_dict['ó']['á']['í']['beta']
933
934        lineno_f = f_with_subscript.__code__.co_firstlineno
935        expected_error = (
936            'Traceback (most recent call last):\n'
937            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
938            '    callable()\n'
939            '    ~~~~~~~~^^\n'
940            f'  File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
941            "    return some_dict['ó']['á']['í']['beta']\n"
942            '           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n'
943        )
944        result_lines = self.get_exception(f_with_subscript)
945        self.assertEqual(result_lines, expected_error.splitlines())
946
947    def test_caret_for_subscript_with_spaces_and_parenthesis(self):
948        def f_with_binary_operator():
949            a = []
950            b = c = 1
951            return b     [    a  ] + c
952
953        lineno_f = f_with_binary_operator.__code__.co_firstlineno
954        expected_error = (
955            'Traceback (most recent call last):\n'
956            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
957            '    callable()\n'
958            '    ~~~~~~~~^^\n'
959            f'  File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
960            '    return b     [    a  ] + c\n'
961            '           ~~~~~~^^^^^^^^^\n'
962        )
963        result_lines = self.get_exception(f_with_binary_operator)
964        self.assertEqual(result_lines, expected_error.splitlines())
965
966    def test_caret_for_subscript_multiline(self):
967        def f_with_subscript():
968            bbbbb = {}
969            ccc = 1
970            ddd = 2
971            b = bbbbb \
972                [  ccc # test
973
974                 + ddd  \
975
976                ] # test
977            return b
978
979        lineno_f = f_with_subscript.__code__.co_firstlineno
980        expected_error = (
981            'Traceback (most recent call last):\n'
982            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
983            '    callable()\n'
984            '    ~~~~~~~~^^\n'
985            f'  File "{__file__}", line {lineno_f+4}, in f_with_subscript\n'
986            '    b = bbbbb \\\n'
987            '        ~~~~~~~\n'
988            '        [  ccc # test\n'
989            '        ^^^^^^^^^^^^^\n'
990            '    \n'
991            '    \n'
992            '         + ddd  \\\n'
993            '         ^^^^^^^^\n'
994            '    \n'
995            '    \n'
996            '        ] # test\n'
997            '        ^\n'
998        )
999        result_lines = self.get_exception(f_with_subscript)
1000        self.assertEqual(result_lines, expected_error.splitlines())
1001
1002    def test_caret_for_call(self):
1003        def f_with_call():
1004            def f1(a):
1005                def f2(b):
1006                    raise RuntimeError("fail")
1007                return f2
1008            return f1("x")("y")("z")
1009
1010        lineno_f = f_with_call.__code__.co_firstlineno
1011        expected_error = (
1012            'Traceback (most recent call last):\n'
1013            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1014            '    callable()\n'
1015            '    ~~~~~~~~^^\n'
1016            f'  File "{__file__}", line {lineno_f+5}, in f_with_call\n'
1017            '    return f1("x")("y")("z")\n'
1018            '           ~~~~~~~^^^^^\n'
1019            f'  File "{__file__}", line {lineno_f+3}, in f2\n'
1020            '    raise RuntimeError("fail")\n'
1021        )
1022        result_lines = self.get_exception(f_with_call)
1023        self.assertEqual(result_lines, expected_error.splitlines())
1024
1025    def test_caret_for_call_unicode(self):
1026        def f_with_call():
1027            def f1(a):
1028                def f2(b):
1029                    raise RuntimeError("fail")
1030                return f2
1031            return f1("ó")("á")
1032
1033        lineno_f = f_with_call.__code__.co_firstlineno
1034        expected_error = (
1035            'Traceback (most recent call last):\n'
1036            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1037            '    callable()\n'
1038            '    ~~~~~~~~^^\n'
1039            f'  File "{__file__}", line {lineno_f+5}, in f_with_call\n'
1040            '    return f1("ó")("á")\n'
1041            '           ~~~~~~~^^^^^\n'
1042            f'  File "{__file__}", line {lineno_f+3}, in f2\n'
1043            '    raise RuntimeError("fail")\n'
1044        )
1045        result_lines = self.get_exception(f_with_call)
1046        self.assertEqual(result_lines, expected_error.splitlines())
1047
1048    def test_caret_for_call_with_spaces_and_parenthesis(self):
1049        def f_with_binary_operator():
1050            def f(a):
1051                raise RuntimeError("fail")
1052            return f     (    "x"  ) + 2
1053
1054        lineno_f = f_with_binary_operator.__code__.co_firstlineno
1055        expected_error = (
1056            'Traceback (most recent call last):\n'
1057            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1058            '    callable()\n'
1059            '    ~~~~~~~~^^\n'
1060            f'  File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
1061            '    return f     (    "x"  ) + 2\n'
1062            '           ~~~~~~^^^^^^^^^^^\n'
1063            f'  File "{__file__}", line {lineno_f+2}, in f\n'
1064            '    raise RuntimeError("fail")\n'
1065        )
1066        result_lines = self.get_exception(f_with_binary_operator)
1067        self.assertEqual(result_lines, expected_error.splitlines())
1068
1069    def test_caret_for_call_multiline(self):
1070        def f_with_call():
1071            class C:
1072                def y(self, a):
1073                    def f(b):
1074                        raise RuntimeError("fail")
1075                    return f
1076            def g(x):
1077                return C()
1078            a = (g(1).y)(
1079                2
1080            )(3)(4)
1081            return a
1082
1083        lineno_f = f_with_call.__code__.co_firstlineno
1084        expected_error = (
1085            'Traceback (most recent call last):\n'
1086            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1087            '    callable()\n'
1088            '    ~~~~~~~~^^\n'
1089            f'  File "{__file__}", line {lineno_f+8}, in f_with_call\n'
1090            '    a = (g(1).y)(\n'
1091            '        ~~~~~~~~~\n'
1092            '        2\n'
1093            '        ~\n'
1094            '    )(3)(4)\n'
1095            '    ~^^^\n'
1096            f'  File "{__file__}", line {lineno_f+4}, in f\n'
1097            '    raise RuntimeError("fail")\n'
1098        )
1099        result_lines = self.get_exception(f_with_call)
1100        self.assertEqual(result_lines, expected_error.splitlines())
1101
1102    def test_many_lines(self):
1103        def f():
1104            x = 1
1105            if True: x += (
1106                "a" +
1107                "a"
1108            )  # test
1109
1110        lineno_f = f.__code__.co_firstlineno
1111        expected_error = (
1112            'Traceback (most recent call last):\n'
1113            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1114            '    callable()\n'
1115            '    ~~~~~~~~^^\n'
1116            f'  File "{__file__}", line {lineno_f+2}, in f\n'
1117            '    if True: x += (\n'
1118            '             ^^^^^^\n'
1119            '    ...<2 lines>...\n'
1120            '    )  # test\n'
1121            '    ^\n'
1122        )
1123        result_lines = self.get_exception(f)
1124        self.assertEqual(result_lines, expected_error.splitlines())
1125
1126    def test_many_lines_no_caret(self):
1127        def f():
1128            x = 1
1129            x += (
1130                "a" +
1131                "a"
1132            )
1133
1134        lineno_f = f.__code__.co_firstlineno
1135        expected_error = (
1136            'Traceback (most recent call last):\n'
1137            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1138            '    callable()\n'
1139            '    ~~~~~~~~^^\n'
1140            f'  File "{__file__}", line {lineno_f+2}, in f\n'
1141            '    x += (\n'
1142            '    ...<2 lines>...\n'
1143            '    )\n'
1144        )
1145        result_lines = self.get_exception(f)
1146        self.assertEqual(result_lines, expected_error.splitlines())
1147
1148    def test_many_lines_binary_op(self):
1149        def f_with_binary_operator():
1150            b = 1
1151            c = "a"
1152            a = (
1153                b +
1154                b
1155            ) + (
1156                c +
1157                c +
1158                c
1159            )
1160            return a
1161
1162        lineno_f = f_with_binary_operator.__code__.co_firstlineno
1163        expected_error = (
1164            'Traceback (most recent call last):\n'
1165            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1166            '    callable()\n'
1167            '    ~~~~~~~~^^\n'
1168            f'  File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
1169            '    a = (\n'
1170            '        ~\n'
1171            '        b +\n'
1172            '        ~~~\n'
1173            '        b\n'
1174            '        ~\n'
1175            '    ) + (\n'
1176            '    ~~^~~\n'
1177            '        c +\n'
1178            '        ~~~\n'
1179            '    ...<2 lines>...\n'
1180            '    )\n'
1181            '    ~\n'
1182        )
1183        result_lines = self.get_exception(f_with_binary_operator)
1184        self.assertEqual(result_lines, expected_error.splitlines())
1185
1186    def test_traceback_specialization_with_syntax_error(self):
1187        bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")
1188
1189        with open(TESTFN, "w") as file:
1190            # make the file's contents invalid
1191            file.write("1 $ 0 / 1 / 2\n")
1192        self.addCleanup(unlink, TESTFN)
1193
1194        func = partial(exec, bytecode)
1195        result_lines = self.get_exception(func)
1196
1197        lineno_f = bytecode.co_firstlineno
1198        expected_error = (
1199            'Traceback (most recent call last):\n'
1200            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1201            '    callable()\n'
1202            '    ~~~~~~~~^^\n'
1203            f'  File "{TESTFN}", line {lineno_f}, in <module>\n'
1204            "    1 $ 0 / 1 / 2\n"
1205            '    ^^^^^\n'
1206        )
1207        self.assertEqual(result_lines, expected_error.splitlines())
1208
1209    def test_traceback_very_long_line(self):
1210        source = "if True: " + "a" * 256
1211        bytecode = compile(source, TESTFN, "exec")
1212
1213        with open(TESTFN, "w") as file:
1214            file.write(source)
1215        self.addCleanup(unlink, TESTFN)
1216
1217        func = partial(exec, bytecode)
1218        result_lines = self.get_exception(func)
1219
1220        lineno_f = bytecode.co_firstlineno
1221        expected_error = (
1222            'Traceback (most recent call last):\n'
1223            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1224            '    callable()\n'
1225            '    ~~~~~~~~^^\n'
1226            f'  File "{TESTFN}", line {lineno_f}, in <module>\n'
1227            f'    {source}\n'
1228            f'    {" "*len("if True: ") + "^"*256}\n'
1229        )
1230        self.assertEqual(result_lines, expected_error.splitlines())
1231
1232    def test_secondary_caret_not_elided(self):
1233        # Always show a line's indicators if they include the secondary character.
1234        def f_with_subscript():
1235            some_dict = {'x': {'y': None}}
1236            some_dict['x']['y']['z']
1237
1238        lineno_f = f_with_subscript.__code__.co_firstlineno
1239        expected_error = (
1240            'Traceback (most recent call last):\n'
1241            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1242            '    callable()\n'
1243            '    ~~~~~~~~^^\n'
1244            f'  File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
1245            "    some_dict['x']['y']['z']\n"
1246            '    ~~~~~~~~~~~~~~~~~~~^^^^^\n'
1247        )
1248        result_lines = self.get_exception(f_with_subscript)
1249        self.assertEqual(result_lines, expected_error.splitlines())
1250
1251    def test_caret_exception_group(self):
1252        # Notably, this covers whether indicators handle margin strings correctly.
1253        # (Exception groups use margin strings to display vertical indicators.)
1254        # The implementation must account for both "indent" and "margin" offsets.
1255
1256        def exc():
1257            if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
1258
1259        expected_error = (
1260             f'  + Exception Group Traceback (most recent call last):\n'
1261             f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
1262             f'  |     callable()\n'
1263             f'  |     ~~~~~~~~^^\n'
1264             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
1265             f'  |     if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
1266             f'  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
1267             f'  | ExceptionGroup: eg (2 sub-exceptions)\n'
1268             f'  +-+---------------- 1 ----------------\n'
1269             f'    | ValueError: 1\n'
1270             f'    +---------------- 2 ----------------\n'
1271             f'    | TypeError: 2\n')
1272
1273        result_lines = self.get_exception(exc)
1274        self.assertEqual(result_lines, expected_error.splitlines())
1275
1276    def assertSpecialized(self, func, expected_specialization):
1277        result_lines = self.get_exception(func)
1278        specialization_line = result_lines[-1]
1279        self.assertEqual(specialization_line.lstrip(), expected_specialization)
1280
1281    def test_specialization_variations(self):
1282        self.assertSpecialized(lambda: 1/0,
1283                                      "~^~")
1284        self.assertSpecialized(lambda: 1/0/3,
1285                                      "~^~")
1286        self.assertSpecialized(lambda: 1 / 0,
1287                                      "~~^~~")
1288        self.assertSpecialized(lambda: 1 / 0 / 3,
1289                                      "~~^~~")
1290        self.assertSpecialized(lambda: 1/ 0,
1291                                      "~^~~")
1292        self.assertSpecialized(lambda: 1/ 0/3,
1293                                      "~^~~")
1294        self.assertSpecialized(lambda: 1    /  0,
1295                                      "~~~~~^~~~")
1296        self.assertSpecialized(lambda: 1    /  0   / 5,
1297                                      "~~~~~^~~~")
1298        self.assertSpecialized(lambda: 1 /0,
1299                                      "~~^~")
1300        self.assertSpecialized(lambda: 1//0,
1301                                      "~^^~")
1302        self.assertSpecialized(lambda: 1//0//4,
1303                                      "~^^~")
1304        self.assertSpecialized(lambda: 1 // 0,
1305                                      "~~^^~~")
1306        self.assertSpecialized(lambda: 1 // 0 // 4,
1307                                      "~~^^~~")
1308        self.assertSpecialized(lambda: 1 //0,
1309                                      "~~^^~")
1310        self.assertSpecialized(lambda: 1// 0,
1311                                      "~^^~~")
1312
1313    def test_decorator_application_lineno_correct(self):
1314        def dec_error(func):
1315            raise TypeError
1316        def dec_fine(func):
1317            return func
1318        def applydecs():
1319            @dec_error
1320            @dec_fine
1321            def g(): pass
1322        result_lines = self.get_exception(applydecs)
1323        lineno_applydescs = applydecs.__code__.co_firstlineno
1324        lineno_dec_error = dec_error.__code__.co_firstlineno
1325        expected_error = (
1326            'Traceback (most recent call last):\n'
1327            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1328            '    callable()\n'
1329            '    ~~~~~~~~^^\n'
1330            f'  File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
1331            '    @dec_error\n'
1332            '     ^^^^^^^^^\n'
1333            f'  File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
1334            '    raise TypeError\n'
1335        )
1336        self.assertEqual(result_lines, expected_error.splitlines())
1337
1338        def applydecs_class():
1339            @dec_error
1340            @dec_fine
1341            class A: pass
1342        result_lines = self.get_exception(applydecs_class)
1343        lineno_applydescs_class = applydecs_class.__code__.co_firstlineno
1344        expected_error = (
1345            'Traceback (most recent call last):\n'
1346            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1347            '    callable()\n'
1348            '    ~~~~~~~~^^\n'
1349            f'  File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
1350            '    @dec_error\n'
1351            '     ^^^^^^^^^\n'
1352            f'  File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
1353            '    raise TypeError\n'
1354        )
1355        self.assertEqual(result_lines, expected_error.splitlines())
1356
1357    def test_multiline_method_call_a(self):
1358        def f():
1359            (None
1360                .method
1361            )()
1362        actual = self.get_exception(f)
1363        expected = [
1364            "Traceback (most recent call last):",
1365            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1366            "    callable()",
1367            "    ~~~~~~~~^^",
1368            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
1369            "    .method",
1370            "     ^^^^^^",
1371        ]
1372        self.assertEqual(actual, expected)
1373
1374    def test_multiline_method_call_b(self):
1375        def f():
1376            (None.
1377                method
1378            )()
1379        actual = self.get_exception(f)
1380        expected = [
1381            "Traceback (most recent call last):",
1382            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1383            "    callable()",
1384            "    ~~~~~~~~^^",
1385            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
1386            "    method",
1387        ]
1388        self.assertEqual(actual, expected)
1389
1390    def test_multiline_method_call_c(self):
1391        def f():
1392            (None
1393                . method
1394            )()
1395        actual = self.get_exception(f)
1396        expected = [
1397            "Traceback (most recent call last):",
1398            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1399            "    callable()",
1400            "    ~~~~~~~~^^",
1401            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
1402            "    . method",
1403            "      ^^^^^^",
1404        ]
1405        self.assertEqual(actual, expected)
1406
1407    def test_wide_characters_unicode_with_problematic_byte_offset(self):
1408        def f():
1409            width
1410
1411        actual = self.get_exception(f)
1412        expected = [
1413            "Traceback (most recent call last):",
1414            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1415            "    callable()",
1416            "    ~~~~~~~~^^",
1417            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1418            "    width",
1419        ]
1420        self.assertEqual(actual, expected)
1421
1422
1423    def test_byte_offset_with_wide_characters_middle(self):
1424        def f():
1425            width = 1
1426            raise ValueError(width)
1427
1428        actual = self.get_exception(f)
1429        expected = [
1430            "Traceback (most recent call last):",
1431            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1432            "    callable()",
1433            "    ~~~~~~~~^^",
1434            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
1435            "    raise ValueError(width)",
1436        ]
1437        self.assertEqual(actual, expected)
1438
1439    def test_byte_offset_multiline(self):
1440        def f():
1441            www = 1
1442            th = 0
1443
1444            print(1, www(
1445                    th))
1446
1447        actual = self.get_exception(f)
1448        expected = [
1449            "Traceback (most recent call last):",
1450            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1451            "    callable()",
1452            "    ~~~~~~~~^^",
1453            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 4}, in f",
1454            f"    print(1, www(",
1455            f"             ~~~~~~^",
1456            f"            th))",
1457            f"            ^^^^^",
1458        ]
1459        self.assertEqual(actual, expected)
1460
1461    def test_byte_offset_with_wide_characters_term_highlight(self):
1462        def f():
1463            说明说明 = 1
1464            şçöğıĤellö = 0 # not wide but still non-ascii
1465            return 说明说明 / şçöğıĤellö
1466
1467        actual = self.get_exception(f)
1468        expected = [
1469            f"Traceback (most recent call last):",
1470            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1471            f"    callable()",
1472            f"    ~~~~~~~~^^",
1473            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 3}, in f",
1474            f"    return 说明说明 / şçöğıĤellö",
1475            f"           ~~~~~~~~~^~~~~~~~~~~~",
1476        ]
1477        self.assertEqual(actual, expected)
1478
1479    def test_byte_offset_with_emojis_term_highlight(self):
1480        def f():
1481            return "✨��" + func_说明说明("����",
1482                "����") + "��"
1483
1484        actual = self.get_exception(f)
1485        expected = [
1486            f"Traceback (most recent call last):",
1487            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1488            f"    callable()",
1489            f"    ~~~~~~~~^^",
1490            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1491            f'    return "✨��" + func_说明说明("����",',
1492            f"                    ^^^^^^^^^^^^^",
1493        ]
1494        self.assertEqual(actual, expected)
1495
1496    def test_byte_offset_wide_chars_subscript(self):
1497        def f():
1498            my_dct = {
1499                "✨��✨": {
1500                    "说明": {
1501                        "������": None
1502                    }
1503                }
1504            }
1505            return my_dct["✨��✨"]["说明"]["��"]["说明"]["����"]
1506
1507        actual = self.get_exception(f)
1508        expected = [
1509            f"Traceback (most recent call last):",
1510            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1511            f"    callable()",
1512            f"    ~~~~~~~~^^",
1513            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 8}, in f",
1514            f'    return my_dct["✨��✨"]["说明"]["��"]["说明"]["����"]',
1515            f"           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^",
1516        ]
1517        self.assertEqual(actual, expected)
1518
1519    def test_memory_error(self):
1520        def f():
1521            raise MemoryError()
1522
1523        actual = self.get_exception(f)
1524        expected = ['Traceback (most recent call last):',
1525            f'  File "{__file__}", line {self.callable_line}, in get_exception',
1526            '    callable()',
1527            '    ~~~~~~~~^^',
1528            f'  File "{__file__}", line {f.__code__.co_firstlineno + 1}, in f',
1529            '    raise MemoryError()']
1530        self.assertEqual(actual, expected)
1531
1532    def test_anchors_for_simple_return_statements_are_elided(self):
1533        def g():
1534            1/0
1535
1536        def f():
1537            return g()
1538
1539        result_lines = self.get_exception(f)
1540        expected = ['Traceback (most recent call last):',
1541            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1542            "    callable()",
1543            "    ~~~~~~~~^^",
1544            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1545            "    return g()",
1546            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1547            "    1/0",
1548            "    ~^~"
1549        ]
1550        self.assertEqual(result_lines, expected)
1551
1552        def g():
1553            1/0
1554
1555        def f():
1556            return g() + 1
1557
1558        result_lines = self.get_exception(f)
1559        expected = ['Traceback (most recent call last):',
1560            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1561            "    callable()",
1562            "    ~~~~~~~~^^",
1563            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1564            "    return g() + 1",
1565            "           ~^^",
1566            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1567            "    1/0",
1568            "    ~^~"
1569        ]
1570        self.assertEqual(result_lines, expected)
1571
1572        def g(*args):
1573            1/0
1574
1575        def f():
1576            return g(1,
1577                     2, 4,
1578                     5)
1579
1580        result_lines = self.get_exception(f)
1581        expected = ['Traceback (most recent call last):',
1582            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1583            "    callable()",
1584            "    ~~~~~~~~^^",
1585            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1586            "    return g(1,",
1587            "             2, 4,",
1588            "             5)",
1589            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1590            "    1/0",
1591            "    ~^~"
1592        ]
1593        self.assertEqual(result_lines, expected)
1594
1595        def g(*args):
1596            1/0
1597
1598        def f():
1599            return g(1,
1600                     2, 4,
1601                     5) + 1
1602
1603        result_lines = self.get_exception(f)
1604        expected = ['Traceback (most recent call last):',
1605            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1606            "    callable()",
1607            "    ~~~~~~~~^^",
1608            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1609            "    return g(1,",
1610            "           ~^^^",
1611            "             2, 4,",
1612            "             ^^^^^",
1613            "             5) + 1",
1614            "             ^^",
1615            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1616            "    1/0",
1617            "    ~^~"
1618        ]
1619        self.assertEqual(result_lines, expected)
1620
1621    def test_anchors_for_simple_assign_statements_are_elided(self):
1622        def g():
1623            1/0
1624
1625        def f():
1626            x = g()
1627
1628        result_lines = self.get_exception(f)
1629        expected = ['Traceback (most recent call last):',
1630            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1631            "    callable()",
1632            "    ~~~~~~~~^^",
1633            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1634            "    x = g()",
1635            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1636            "    1/0",
1637            "    ~^~"
1638        ]
1639        self.assertEqual(result_lines, expected)
1640
1641        def g(*args):
1642            1/0
1643
1644        def f():
1645            x = g(1,
1646                  2, 3,
1647                  4)
1648
1649        result_lines = self.get_exception(f)
1650        expected = ['Traceback (most recent call last):',
1651            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1652            "    callable()",
1653            "    ~~~~~~~~^^",
1654            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1655            "    x = g(1,",
1656            "          2, 3,",
1657            "          4)",
1658            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1659            "    1/0",
1660            "    ~^~"
1661        ]
1662        self.assertEqual(result_lines, expected)
1663
1664        def g():
1665            1/0
1666
1667        def f():
1668            x = y = g()
1669
1670        result_lines = self.get_exception(f)
1671        expected = ['Traceback (most recent call last):',
1672            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1673            "    callable()",
1674            "    ~~~~~~~~^^",
1675            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1676            "    x = y = g()",
1677            "            ~^^",
1678            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1679            "    1/0",
1680            "    ~^~"
1681        ]
1682        self.assertEqual(result_lines, expected)
1683
1684        def g(*args):
1685            1/0
1686
1687        def f():
1688            x = y = g(1,
1689                      2, 3,
1690                      4)
1691
1692        result_lines = self.get_exception(f)
1693        expected = ['Traceback (most recent call last):',
1694            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
1695            "    callable()",
1696            "    ~~~~~~~~^^",
1697            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1698            "    x = y = g(1,",
1699            "            ~^^^",
1700            "              2, 3,",
1701            "              ^^^^^",
1702            "              4)",
1703            "              ^^",
1704            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1705            "    1/0",
1706            "    ~^~"
1707        ]
1708        self.assertEqual(result_lines, expected)
1709
1710
1711@requires_debug_ranges()
1712class PurePythonTracebackErrorCaretTests(
1713    PurePythonExceptionFormattingMixin,
1714    TracebackErrorLocationCaretTestBase,
1715    unittest.TestCase,
1716):
1717    """
1718    Same set of tests as above using the pure Python implementation of
1719    traceback printing in traceback.py.
1720    """
1721
1722
1723@cpython_only
1724@requires_debug_ranges()
1725class CPythonTracebackErrorCaretTests(
1726    CAPIExceptionFormattingMixin,
1727    TracebackErrorLocationCaretTestBase,
1728    unittest.TestCase,
1729):
1730    """
1731    Same set of tests as above but with Python's internal traceback printing.
1732    """
1733
1734@cpython_only
1735@requires_debug_ranges()
1736class CPythonTracebackLegacyErrorCaretTests(
1737    CAPIExceptionFormattingLegacyMixin,
1738    TracebackErrorLocationCaretTestBase,
1739    unittest.TestCase,
1740):
1741    """
1742    Same set of tests as above but with Python's legacy internal traceback printing.
1743    """
1744
1745
1746class TracebackFormatMixin:
1747    DEBUG_RANGES = True
1748
1749    def some_exception(self):
1750        raise KeyError('blah')
1751
1752    def _filter_debug_ranges(self, expected):
1753        return [line for line in expected if not set(line.strip()) <= set("^~")]
1754
1755    def _maybe_filter_debug_ranges(self, expected):
1756        if not self.DEBUG_RANGES:
1757            return self._filter_debug_ranges(expected)
1758        return expected
1759
1760    @cpython_only
1761    def check_traceback_format(self, cleanup_func=None):
1762        from _testcapi import traceback_print
1763        try:
1764            self.some_exception()
1765        except KeyError as e:
1766            tb = e.__traceback__
1767            if cleanup_func is not None:
1768                # Clear the inner frames, not this one
1769                cleanup_func(tb.tb_next)
1770            traceback_fmt = 'Traceback (most recent call last):\n' + \
1771                            ''.join(traceback.format_tb(tb))
1772            # clear caret lines from traceback_fmt since internal API does
1773            # not emit them
1774            traceback_fmt = "\n".join(
1775                self._filter_debug_ranges(traceback_fmt.splitlines())
1776            ) + "\n"
1777            file_ = StringIO()
1778            traceback_print(tb, file_)
1779            python_fmt  = file_.getvalue()
1780            # Call all _tb and _exc functions
1781            with captured_output("stderr") as tbstderr:
1782                traceback.print_tb(tb)
1783            tbfile = StringIO()
1784            traceback.print_tb(tb, file=tbfile)
1785            with captured_output("stderr") as excstderr:
1786                traceback.print_exc()
1787            excfmt = traceback.format_exc()
1788            excfile = StringIO()
1789            traceback.print_exc(file=excfile)
1790        else:
1791            raise Error("unable to create test traceback string")
1792
1793        # Make sure that Python and the traceback module format the same thing
1794        self.assertEqual(traceback_fmt, python_fmt)
1795        # Now verify the _tb func output
1796        self.assertEqual(tbstderr.getvalue(), tbfile.getvalue())
1797        # Now verify the _exc func output
1798        self.assertEqual(excstderr.getvalue(), excfile.getvalue())
1799        self.assertEqual(excfmt, excfile.getvalue())
1800
1801        # Make sure that the traceback is properly indented.
1802        tb_lines = python_fmt.splitlines()
1803        banner = tb_lines[0]
1804        self.assertEqual(len(tb_lines), 5)
1805        location, source_line = tb_lines[-2], tb_lines[-1]
1806        self.assertTrue(banner.startswith('Traceback'))
1807        self.assertTrue(location.startswith('  File'))
1808        self.assertTrue(source_line.startswith('    raise'))
1809
1810    def test_traceback_format(self):
1811        self.check_traceback_format()
1812
1813    def test_traceback_format_with_cleared_frames(self):
1814        # Check that traceback formatting also works with a clear()ed frame
1815        def cleanup_tb(tb):
1816            tb.tb_frame.clear()
1817        self.check_traceback_format(cleanup_tb)
1818
1819    def test_stack_format(self):
1820        # Verify _stack functions. Note we have to use _getframe(1) to
1821        # compare them without this frame appearing in the output
1822        with captured_output("stderr") as ststderr:
1823            traceback.print_stack(sys._getframe(1))
1824        stfile = StringIO()
1825        traceback.print_stack(sys._getframe(1), file=stfile)
1826        self.assertEqual(ststderr.getvalue(), stfile.getvalue())
1827
1828        stfmt = traceback.format_stack(sys._getframe(1))
1829
1830        self.assertEqual(ststderr.getvalue(), "".join(stfmt))
1831
1832    def test_print_stack(self):
1833        def prn():
1834            traceback.print_stack()
1835        with captured_output("stderr") as stderr:
1836            prn()
1837        lineno = prn.__code__.co_firstlineno
1838        self.assertEqual(stderr.getvalue().splitlines()[-4:], [
1839            '  File "%s", line %d, in test_print_stack' % (__file__, lineno+3),
1840            '    prn()',
1841            '  File "%s", line %d, in prn' % (__file__, lineno+1),
1842            '    traceback.print_stack()',
1843        ])
1844
1845    # issue 26823 - Shrink recursive tracebacks
1846    def _check_recursive_traceback_display(self, render_exc):
1847        # Always show full diffs when this test fails
1848        # Note that rearranging things may require adjusting
1849        # the relative line numbers in the expected tracebacks
1850        self.maxDiff = None
1851
1852        # Check hitting the recursion limit
1853        def f():
1854            f()
1855
1856        with captured_output("stderr") as stderr_f:
1857            try:
1858                f()
1859            except RecursionError:
1860                render_exc()
1861            else:
1862                self.fail("no recursion occurred")
1863
1864        lineno_f = f.__code__.co_firstlineno
1865        result_f = (
1866            'Traceback (most recent call last):\n'
1867            f'  File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
1868            '    f()\n'
1869            '    ~^^\n'
1870            f'  File "{__file__}", line {lineno_f+1}, in f\n'
1871            '    f()\n'
1872            '    ~^^\n'
1873            f'  File "{__file__}", line {lineno_f+1}, in f\n'
1874            '    f()\n'
1875            '    ~^^\n'
1876            f'  File "{__file__}", line {lineno_f+1}, in f\n'
1877            '    f()\n'
1878            '    ~^^\n'
1879            # XXX: The following line changes depending on whether the tests
1880            # are run through the interactive interpreter or with -m
1881            # It also varies depending on the platform (stack size)
1882            # Fortunately, we don't care about exactness here, so we use regex
1883            r'  \[Previous line repeated (\d+) more times\]' '\n'
1884            'RecursionError: maximum recursion depth exceeded\n'
1885        )
1886
1887        expected = self._maybe_filter_debug_ranges(result_f.splitlines())
1888        actual = stderr_f.getvalue().splitlines()
1889
1890        # Check the output text matches expectations
1891        # 2nd last line contains the repetition count
1892        self.assertEqual(actual[:-2], expected[:-2])
1893        self.assertRegex(actual[-2], expected[-2])
1894        # last line can have additional text appended
1895        self.assertIn(expected[-1], actual[-1])
1896
1897        # Check the recursion count is roughly as expected
1898        rec_limit = sys.getrecursionlimit()
1899        self.assertIn(int(re.search(r"\d+", actual[-2]).group()), range(rec_limit-60, rec_limit))
1900
1901        # Check a known (limited) number of recursive invocations
1902        def g(count=10):
1903            if count:
1904                return g(count-1) + 1
1905            raise ValueError
1906
1907        with captured_output("stderr") as stderr_g:
1908            try:
1909                g()
1910            except ValueError:
1911                render_exc()
1912            else:
1913                self.fail("no value error was raised")
1914
1915        lineno_g = g.__code__.co_firstlineno
1916        result_g = (
1917            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1918            '    return g(count-1) + 1\n'
1919            '           ~^^^^^^^^^\n'
1920            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1921            '    return g(count-1) + 1\n'
1922            '           ~^^^^^^^^^\n'
1923            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1924            '    return g(count-1) + 1\n'
1925            '           ~^^^^^^^^^\n'
1926            '  [Previous line repeated 7 more times]\n'
1927            f'  File "{__file__}", line {lineno_g+3}, in g\n'
1928            '    raise ValueError\n'
1929            'ValueError\n'
1930        )
1931        tb_line = (
1932            'Traceback (most recent call last):\n'
1933            f'  File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
1934            '    g()\n'
1935            '    ~^^\n'
1936        )
1937        expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
1938        actual = stderr_g.getvalue().splitlines()
1939        self.assertEqual(actual, expected)
1940
1941        # Check 2 different repetitive sections
1942        def h(count=10):
1943            if count:
1944                return h(count-1)
1945            g()
1946
1947        with captured_output("stderr") as stderr_h:
1948            try:
1949                h()
1950            except ValueError:
1951                render_exc()
1952            else:
1953                self.fail("no value error was raised")
1954
1955        lineno_h = h.__code__.co_firstlineno
1956        result_h = (
1957            'Traceback (most recent call last):\n'
1958            f'  File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
1959            '    h()\n'
1960            '    ~^^\n'
1961            f'  File "{__file__}", line {lineno_h+2}, in h\n'
1962            '    return h(count-1)\n'
1963            f'  File "{__file__}", line {lineno_h+2}, in h\n'
1964            '    return h(count-1)\n'
1965            f'  File "{__file__}", line {lineno_h+2}, in h\n'
1966            '    return h(count-1)\n'
1967            '  [Previous line repeated 7 more times]\n'
1968            f'  File "{__file__}", line {lineno_h+3}, in h\n'
1969            '    g()\n'
1970            '    ~^^\n'
1971        )
1972        expected = self._maybe_filter_debug_ranges((result_h + result_g).splitlines())
1973        actual = stderr_h.getvalue().splitlines()
1974        self.assertEqual(actual, expected)
1975
1976        # Check the boundary conditions. First, test just below the cutoff.
1977        with captured_output("stderr") as stderr_g:
1978            try:
1979                g(traceback._RECURSIVE_CUTOFF)
1980            except ValueError:
1981                render_exc()
1982            else:
1983                self.fail("no error raised")
1984        result_g = (
1985            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1986            '    return g(count-1) + 1\n'
1987            '           ~^^^^^^^^^\n'
1988            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1989            '    return g(count-1) + 1\n'
1990            '           ~^^^^^^^^^\n'
1991            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1992            '    return g(count-1) + 1\n'
1993            '           ~^^^^^^^^^\n'
1994            f'  File "{__file__}", line {lineno_g+3}, in g\n'
1995            '    raise ValueError\n'
1996            'ValueError\n'
1997        )
1998        tb_line = (
1999            'Traceback (most recent call last):\n'
2000            f'  File "{__file__}", line {lineno_g+77}, in _check_recursive_traceback_display\n'
2001            '    g(traceback._RECURSIVE_CUTOFF)\n'
2002            '    ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
2003        )
2004        expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
2005        actual = stderr_g.getvalue().splitlines()
2006        self.assertEqual(actual, expected)
2007
2008        # Second, test just above the cutoff.
2009        with captured_output("stderr") as stderr_g:
2010            try:
2011                g(traceback._RECURSIVE_CUTOFF + 1)
2012            except ValueError:
2013                render_exc()
2014            else:
2015                self.fail("no error raised")
2016        result_g = (
2017            f'  File "{__file__}", line {lineno_g+2}, in g\n'
2018            '    return g(count-1) + 1\n'
2019            '           ~^^^^^^^^^\n'
2020            f'  File "{__file__}", line {lineno_g+2}, in g\n'
2021            '    return g(count-1) + 1\n'
2022            '           ~^^^^^^^^^\n'
2023            f'  File "{__file__}", line {lineno_g+2}, in g\n'
2024            '    return g(count-1) + 1\n'
2025            '           ~^^^^^^^^^\n'
2026            '  [Previous line repeated 1 more time]\n'
2027            f'  File "{__file__}", line {lineno_g+3}, in g\n'
2028            '    raise ValueError\n'
2029            'ValueError\n'
2030        )
2031        tb_line = (
2032            'Traceback (most recent call last):\n'
2033            f'  File "{__file__}", line {lineno_g+109}, in _check_recursive_traceback_display\n'
2034            '    g(traceback._RECURSIVE_CUTOFF + 1)\n'
2035            '    ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
2036        )
2037        expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
2038        actual = stderr_g.getvalue().splitlines()
2039        self.assertEqual(actual, expected)
2040
2041    @requires_debug_ranges()
2042    def test_recursive_traceback(self):
2043        if self.DEBUG_RANGES:
2044            self._check_recursive_traceback_display(traceback.print_exc)
2045        else:
2046            from _testcapi import exception_print
2047            def render_exc():
2048                exception_print(sys.exception())
2049            self._check_recursive_traceback_display(render_exc)
2050
2051    def test_format_stack(self):
2052        def fmt():
2053            return traceback.format_stack()
2054        result = fmt()
2055        lineno = fmt.__code__.co_firstlineno
2056        self.assertEqual(result[-2:], [
2057            '  File "%s", line %d, in test_format_stack\n'
2058            '    result = fmt()\n' % (__file__, lineno+2),
2059            '  File "%s", line %d, in fmt\n'
2060            '    return traceback.format_stack()\n' % (__file__, lineno+1),
2061        ])
2062
2063    @cpython_only
2064    def test_unhashable(self):
2065        from _testcapi import exception_print
2066
2067        class UnhashableException(Exception):
2068            def __eq__(self, other):
2069                return True
2070
2071        ex1 = UnhashableException('ex1')
2072        ex2 = UnhashableException('ex2')
2073        try:
2074            raise ex2 from ex1
2075        except UnhashableException:
2076            try:
2077                raise ex1
2078            except UnhashableException as e:
2079                exc_val = e
2080
2081        with captured_output("stderr") as stderr_f:
2082            exception_print(exc_val)
2083
2084        tb = stderr_f.getvalue().strip().splitlines()
2085        self.assertEqual(11, len(tb))
2086        self.assertEqual(context_message.strip(), tb[5])
2087        self.assertIn('UnhashableException: ex2', tb[3])
2088        self.assertIn('UnhashableException: ex1', tb[10])
2089
2090    def deep_eg(self):
2091        e = TypeError(1)
2092        for i in range(2000):
2093            e = ExceptionGroup('eg', [e])
2094        return e
2095
2096    @cpython_only
2097    def test_exception_group_deep_recursion_capi(self):
2098        from _testcapi import exception_print
2099        LIMIT = 75
2100        eg = self.deep_eg()
2101        with captured_output("stderr") as stderr_f:
2102            with support.infinite_recursion(max_depth=LIMIT):
2103                exception_print(eg)
2104        output = stderr_f.getvalue()
2105        self.assertIn('ExceptionGroup', output)
2106        self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
2107
2108    def test_exception_group_deep_recursion_traceback(self):
2109        LIMIT = 75
2110        eg = self.deep_eg()
2111        with captured_output("stderr") as stderr_f:
2112            with support.infinite_recursion(max_depth=LIMIT):
2113                traceback.print_exception(type(eg), eg, eg.__traceback__)
2114        output = stderr_f.getvalue()
2115        self.assertIn('ExceptionGroup', output)
2116        self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
2117
2118    @cpython_only
2119    def test_print_exception_bad_type_capi(self):
2120        from _testcapi import exception_print
2121        with captured_output("stderr") as stderr:
2122            with support.catch_unraisable_exception():
2123                exception_print(42)
2124        self.assertEqual(
2125            stderr.getvalue(),
2126            ('TypeError: print_exception(): '
2127             'Exception expected for value, int found\n')
2128        )
2129
2130    def test_print_exception_bad_type_python(self):
2131        msg = "Exception expected for value, int found"
2132        with self.assertRaisesRegex(TypeError, msg):
2133            traceback.print_exception(42)
2134
2135
2136cause_message = (
2137    "\nThe above exception was the direct cause "
2138    "of the following exception:\n\n")
2139
2140context_message = (
2141    "\nDuring handling of the above exception, "
2142    "another exception occurred:\n\n")
2143
2144boundaries = re.compile(
2145    '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
2146
2147class TestTracebackFormat(unittest.TestCase, TracebackFormatMixin):
2148    pass
2149
2150@cpython_only
2151class TestFallbackTracebackFormat(unittest.TestCase, TracebackFormatMixin):
2152    DEBUG_RANGES = False
2153    def setUp(self) -> None:
2154        self.original_unraisable_hook = sys.unraisablehook
2155        sys.unraisablehook = lambda *args: None
2156        self.original_hook = traceback._print_exception_bltin
2157        traceback._print_exception_bltin = lambda *args: 1/0
2158        return super().setUp()
2159
2160    def tearDown(self) -> None:
2161        traceback._print_exception_bltin = self.original_hook
2162        sys.unraisablehook = self.original_unraisable_hook
2163        return super().tearDown()
2164
2165class BaseExceptionReportingTests:
2166
2167    def get_exception(self, exception_or_callable):
2168        if isinstance(exception_or_callable, BaseException):
2169            return exception_or_callable
2170        try:
2171            exception_or_callable()
2172        except Exception as e:
2173            return e
2174
2175    callable_line = get_exception.__code__.co_firstlineno + 4
2176
2177    def zero_div(self):
2178        1/0 # In zero_div
2179
2180    def check_zero_div(self, msg):
2181        lines = msg.splitlines()
2182        if has_no_debug_ranges():
2183            self.assertTrue(lines[-3].startswith('  File'))
2184            self.assertIn('1/0 # In zero_div', lines[-2])
2185        else:
2186            self.assertTrue(lines[-4].startswith('  File'))
2187            self.assertIn('1/0 # In zero_div', lines[-3])
2188        self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
2189
2190    def test_simple(self):
2191        try:
2192            1/0 # Marker
2193        except ZeroDivisionError as _:
2194            e = _
2195        lines = self.get_report(e).splitlines()
2196        if has_no_debug_ranges():
2197            self.assertEqual(len(lines), 4)
2198            self.assertTrue(lines[3].startswith('ZeroDivisionError'))
2199        else:
2200            self.assertEqual(len(lines), 5)
2201            self.assertTrue(lines[4].startswith('ZeroDivisionError'))
2202        self.assertTrue(lines[0].startswith('Traceback'))
2203        self.assertTrue(lines[1].startswith('  File'))
2204        self.assertIn('1/0 # Marker', lines[2])
2205
2206    def test_cause(self):
2207        def inner_raise():
2208            try:
2209                self.zero_div()
2210            except ZeroDivisionError as e:
2211                raise KeyError from e
2212        def outer_raise():
2213            inner_raise() # Marker
2214        blocks = boundaries.split(self.get_report(outer_raise))
2215        self.assertEqual(len(blocks), 3)
2216        self.assertEqual(blocks[1], cause_message)
2217        self.check_zero_div(blocks[0])
2218        self.assertIn('inner_raise() # Marker', blocks[2])
2219
2220    def test_context(self):
2221        def inner_raise():
2222            try:
2223                self.zero_div()
2224            except ZeroDivisionError:
2225                raise KeyError
2226        def outer_raise():
2227            inner_raise() # Marker
2228        blocks = boundaries.split(self.get_report(outer_raise))
2229        self.assertEqual(len(blocks), 3)
2230        self.assertEqual(blocks[1], context_message)
2231        self.check_zero_div(blocks[0])
2232        self.assertIn('inner_raise() # Marker', blocks[2])
2233
2234    def test_context_suppression(self):
2235        try:
2236            try:
2237                raise Exception
2238            except Exception:
2239                raise ZeroDivisionError from None
2240        except ZeroDivisionError as _:
2241            e = _
2242        lines = self.get_report(e).splitlines()
2243        self.assertEqual(len(lines), 4)
2244        self.assertTrue(lines[3].startswith('ZeroDivisionError'))
2245        self.assertTrue(lines[0].startswith('Traceback'))
2246        self.assertTrue(lines[1].startswith('  File'))
2247        self.assertIn('ZeroDivisionError from None', lines[2])
2248
2249    def test_cause_and_context(self):
2250        # When both a cause and a context are set, only the cause should be
2251        # displayed and the context should be muted.
2252        def inner_raise():
2253            try:
2254                self.zero_div()
2255            except ZeroDivisionError as _e:
2256                e = _e
2257            try:
2258                xyzzy
2259            except NameError:
2260                raise KeyError from e
2261        def outer_raise():
2262            inner_raise() # Marker
2263        blocks = boundaries.split(self.get_report(outer_raise))
2264        self.assertEqual(len(blocks), 3)
2265        self.assertEqual(blocks[1], cause_message)
2266        self.check_zero_div(blocks[0])
2267        self.assertIn('inner_raise() # Marker', blocks[2])
2268
2269    def test_cause_recursive(self):
2270        def inner_raise():
2271            try:
2272                try:
2273                    self.zero_div()
2274                except ZeroDivisionError as e:
2275                    z = e
2276                    raise KeyError from e
2277            except KeyError as e:
2278                raise z from e
2279        def outer_raise():
2280            inner_raise() # Marker
2281        blocks = boundaries.split(self.get_report(outer_raise))
2282        self.assertEqual(len(blocks), 3)
2283        self.assertEqual(blocks[1], cause_message)
2284        # The first block is the KeyError raised from the ZeroDivisionError
2285        self.assertIn('raise KeyError from e', blocks[0])
2286        self.assertNotIn('1/0', blocks[0])
2287        # The second block (apart from the boundary) is the ZeroDivisionError
2288        # re-raised from the KeyError
2289        self.assertIn('inner_raise() # Marker', blocks[2])
2290        self.check_zero_div(blocks[2])
2291
2292    def test_syntax_error_offset_at_eol(self):
2293        # See #10186.
2294        def e():
2295            raise SyntaxError('', ('', 0, 5, 'hello'))
2296        msg = self.get_report(e).splitlines()
2297        self.assertEqual(msg[-2], "        ^")
2298        def e():
2299            exec("x = 5 | 4 |")
2300        msg = self.get_report(e).splitlines()
2301        self.assertEqual(msg[-2], '               ^')
2302
2303    def test_syntax_error_no_lineno(self):
2304        # See #34463.
2305
2306        # Without filename
2307        e = SyntaxError('bad syntax')
2308        msg = self.get_report(e).splitlines()
2309        self.assertEqual(msg,
2310            ['SyntaxError: bad syntax'])
2311        e.lineno = 100
2312        msg = self.get_report(e).splitlines()
2313        self.assertEqual(msg,
2314            ['  File "<string>", line 100', 'SyntaxError: bad syntax'])
2315
2316        # With filename
2317        e = SyntaxError('bad syntax')
2318        e.filename = 'myfile.py'
2319
2320        msg = self.get_report(e).splitlines()
2321        self.assertEqual(msg,
2322            ['SyntaxError: bad syntax (myfile.py)'])
2323        e.lineno = 100
2324        msg = self.get_report(e).splitlines()
2325        self.assertEqual(msg,
2326            ['  File "myfile.py", line 100', 'SyntaxError: bad syntax'])
2327
2328    def test_message_none(self):
2329        # A message that looks like "None" should not be treated specially
2330        err = self.get_report(Exception(None))
2331        self.assertIn('Exception: None\n', err)
2332        err = self.get_report(Exception('None'))
2333        self.assertIn('Exception: None\n', err)
2334        err = self.get_report(Exception())
2335        self.assertIn('Exception\n', err)
2336        err = self.get_report(Exception(''))
2337        self.assertIn('Exception\n', err)
2338
2339    def test_syntax_error_various_offsets(self):
2340        for offset in range(-5, 10):
2341            for add in [0, 2]:
2342                text = " " * add + "text%d" % offset
2343                expected = ['  File "file.py", line 1']
2344                if offset < 1:
2345                    expected.append("    %s" % text.lstrip())
2346                elif offset <= 6:
2347                    expected.append("    %s" % text.lstrip())
2348                    # Set the caret length to match the length of the text minus the offset.
2349                    caret_length = max(1, len(text.lstrip()) - offset + 1)
2350                    expected.append("    %s%s" % (" " * (offset - 1), "^" * caret_length))
2351                else:
2352                    caret_length = max(1, len(text.lstrip()) - 4)
2353                    expected.append("    %s" % text.lstrip())
2354                    expected.append("    %s%s" % (" " * 5, "^" * caret_length))
2355                expected.append("SyntaxError: msg")
2356                expected.append("")
2357                err = self.get_report(SyntaxError("msg", ("file.py", 1, offset + add, text)))
2358                exp = "\n".join(expected)
2359                self.assertEqual(exp, err)
2360
2361    def test_exception_with_note(self):
2362        e = ValueError(123)
2363        vanilla = self.get_report(e)
2364
2365        e.add_note('My Note')
2366        self.assertEqual(self.get_report(e), vanilla + 'My Note\n')
2367
2368        del e.__notes__
2369        e.add_note('')
2370        self.assertEqual(self.get_report(e), vanilla + '\n')
2371
2372        del e.__notes__
2373        e.add_note('Your Note')
2374        self.assertEqual(self.get_report(e), vanilla + 'Your Note\n')
2375
2376        del e.__notes__
2377        self.assertEqual(self.get_report(e), vanilla)
2378
2379    def test_exception_with_invalid_notes(self):
2380        e = ValueError(123)
2381        vanilla = self.get_report(e)
2382
2383        # non-sequence __notes__
2384        class BadThing:
2385            def __str__(self):
2386                return 'bad str'
2387
2388            def __repr__(self):
2389                return 'bad repr'
2390
2391        # unprintable, non-sequence __notes__
2392        class Unprintable:
2393            def __repr__(self):
2394                raise ValueError('bad value')
2395
2396        e.__notes__ = BadThing()
2397        notes_repr = 'bad repr'
2398        self.assertEqual(self.get_report(e), vanilla + notes_repr + '\n')
2399
2400        e.__notes__ = Unprintable()
2401        err_msg = '<__notes__ repr() failed>'
2402        self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
2403
2404        # non-string item in the __notes__ sequence
2405        e.__notes__  = [BadThing(), 'Final Note']
2406        bad_note = 'bad str'
2407        self.assertEqual(self.get_report(e), vanilla + bad_note + '\nFinal Note\n')
2408
2409        # unprintable, non-string item in the __notes__ sequence
2410        e.__notes__  = [Unprintable(), 'Final Note']
2411        err_msg = '<note str() failed>'
2412        self.assertEqual(self.get_report(e), vanilla + err_msg + '\nFinal Note\n')
2413
2414        e.__notes__  = "please do not explode me"
2415        err_msg = "'please do not explode me'"
2416        self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
2417
2418        e.__notes__  = b"please do not show me as numbers"
2419        err_msg = "b'please do not show me as numbers'"
2420        self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
2421
2422        # an exception with a broken __getattr__ raising a non expected error
2423        class BrokenException(Exception):
2424            broken = False
2425            def __getattr__(self, name):
2426                if self.broken:
2427                    raise ValueError(f'no {name}')
2428
2429        e = BrokenException(123)
2430        vanilla = self.get_report(e)
2431        e.broken = True
2432        self.assertEqual(
2433            self.get_report(e),
2434            vanilla + "Ignored error getting __notes__: ValueError('no __notes__')\n")
2435
2436    def test_exception_with_multiple_notes(self):
2437        for e in [ValueError(42), SyntaxError('bad syntax')]:
2438            with self.subTest(e=e):
2439                vanilla = self.get_report(e)
2440
2441                e.add_note('Note 1')
2442                e.add_note('Note 2')
2443                e.add_note('Note 3')
2444
2445                self.assertEqual(
2446                    self.get_report(e),
2447                    vanilla + 'Note 1\n' + 'Note 2\n' + 'Note 3\n')
2448
2449                del e.__notes__
2450                e.add_note('Note 4')
2451                del e.__notes__
2452                e.add_note('Note 5')
2453                e.add_note('Note 6')
2454
2455                self.assertEqual(
2456                    self.get_report(e),
2457                    vanilla + 'Note 5\n' + 'Note 6\n')
2458
2459    def test_exception_qualname(self):
2460        class A:
2461            class B:
2462                class X(Exception):
2463                    def __str__(self):
2464                        return "I am X"
2465
2466        err = self.get_report(A.B.X())
2467        str_value = 'I am X'
2468        str_name = '.'.join([A.B.X.__module__, A.B.X.__qualname__])
2469        exp = "%s: %s\n" % (str_name, str_value)
2470        self.assertEqual(exp, MODULE_PREFIX + err)
2471
2472    def test_exception_modulename(self):
2473        class X(Exception):
2474            def __str__(self):
2475                return "I am X"
2476
2477        for modulename in '__main__', 'builtins', 'some_module':
2478            X.__module__ = modulename
2479            with self.subTest(modulename=modulename):
2480                err = self.get_report(X())
2481                str_value = 'I am X'
2482                if modulename in ['builtins', '__main__']:
2483                    str_name = X.__qualname__
2484                else:
2485                    str_name = '.'.join([X.__module__, X.__qualname__])
2486                exp = "%s: %s\n" % (str_name, str_value)
2487                self.assertEqual(exp, err)
2488
2489    def test_exception_angle_bracketed_filename(self):
2490        src = textwrap.dedent("""
2491            try:
2492                raise ValueError(42)
2493            except Exception as e:
2494                exc = e
2495            """)
2496
2497        code = compile(src, "<does not exist>", "exec")
2498        g, l = {}, {}
2499        exec(code, g, l)
2500        err = self.get_report(l['exc'])
2501        exp = '  File "<does not exist>", line 3, in <module>\nValueError: 42\n'
2502        self.assertIn(exp, err)
2503
2504    def test_exception_modulename_not_unicode(self):
2505        class X(Exception):
2506            def __str__(self):
2507                return "I am X"
2508
2509        X.__module__ = 42
2510
2511        err = self.get_report(X())
2512        exp = f'<unknown>.{X.__qualname__}: I am X\n'
2513        self.assertEqual(exp, err)
2514
2515    def test_exception_bad__str__(self):
2516        class X(Exception):
2517            def __str__(self):
2518                1/0
2519        err = self.get_report(X())
2520        str_value = '<exception str() failed>'
2521        str_name = '.'.join([X.__module__, X.__qualname__])
2522        self.assertEqual(MODULE_PREFIX + err, f"{str_name}: {str_value}\n")
2523
2524
2525    # #### Exception Groups ####
2526
2527    def test_exception_group_basic(self):
2528        def exc():
2529            raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
2530
2531        expected = (
2532             f'  + Exception Group Traceback (most recent call last):\n'
2533             f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
2534             f'  |     exception_or_callable()\n'
2535             f'  |     ~~~~~~~~~~~~~~~~~~~~~^^\n'
2536             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
2537             f'  |     raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
2538             f'  | ExceptionGroup: eg (2 sub-exceptions)\n'
2539             f'  +-+---------------- 1 ----------------\n'
2540             f'    | ValueError: 1\n'
2541             f'    +---------------- 2 ----------------\n'
2542             f'    | TypeError: 2\n'
2543             f'    +------------------------------------\n')
2544
2545        report = self.get_report(exc)
2546        self.assertEqual(report, expected)
2547
2548    def test_exception_group_cause(self):
2549        def exc():
2550            EG = ExceptionGroup
2551            try:
2552                raise EG("eg1", [ValueError(1), TypeError(2)])
2553            except Exception as e:
2554                raise EG("eg2", [ValueError(3), TypeError(4)]) from e
2555
2556        expected = (f'  + Exception Group Traceback (most recent call last):\n'
2557                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
2558                    f'  |     raise EG("eg1", [ValueError(1), TypeError(2)])\n'
2559                    f'  | ExceptionGroup: eg1 (2 sub-exceptions)\n'
2560                    f'  +-+---------------- 1 ----------------\n'
2561                    f'    | ValueError: 1\n'
2562                    f'    +---------------- 2 ----------------\n'
2563                    f'    | TypeError: 2\n'
2564                    f'    +------------------------------------\n'
2565                    f'\n'
2566                    f'The above exception was the direct cause of the following exception:\n'
2567                    f'\n'
2568                    f'  + Exception Group Traceback (most recent call last):\n'
2569                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
2570                    f'  |     exception_or_callable()\n'
2571                    f'  |     ~~~~~~~~~~~~~~~~~~~~~^^\n'
2572                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
2573                    f'  |     raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
2574                    f'  | ExceptionGroup: eg2 (2 sub-exceptions)\n'
2575                    f'  +-+---------------- 1 ----------------\n'
2576                    f'    | ValueError: 3\n'
2577                    f'    +---------------- 2 ----------------\n'
2578                    f'    | TypeError: 4\n'
2579                    f'    +------------------------------------\n')
2580
2581        report = self.get_report(exc)
2582        self.assertEqual(report, expected)
2583
2584    def test_exception_group_context_with_context(self):
2585        def exc():
2586            EG = ExceptionGroup
2587            try:
2588                try:
2589                    raise EG("eg1", [ValueError(1), TypeError(2)])
2590                except EG:
2591                    raise EG("eg2", [ValueError(3), TypeError(4)])
2592            except EG:
2593                raise ImportError(5)
2594
2595        expected = (
2596             f'  + Exception Group Traceback (most recent call last):\n'
2597             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
2598             f'  |     raise EG("eg1", [ValueError(1), TypeError(2)])\n'
2599             f'  | ExceptionGroup: eg1 (2 sub-exceptions)\n'
2600             f'  +-+---------------- 1 ----------------\n'
2601             f'    | ValueError: 1\n'
2602             f'    +---------------- 2 ----------------\n'
2603             f'    | TypeError: 2\n'
2604             f'    +------------------------------------\n'
2605             f'\n'
2606             f'During handling of the above exception, another exception occurred:\n'
2607             f'\n'
2608             f'  + Exception Group Traceback (most recent call last):\n'
2609             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
2610             f'  |     raise EG("eg2", [ValueError(3), TypeError(4)])\n'
2611             f'  | ExceptionGroup: eg2 (2 sub-exceptions)\n'
2612             f'  +-+---------------- 1 ----------------\n'
2613             f'    | ValueError: 3\n'
2614             f'    +---------------- 2 ----------------\n'
2615             f'    | TypeError: 4\n'
2616             f'    +------------------------------------\n'
2617             f'\n'
2618             f'During handling of the above exception, another exception occurred:\n'
2619             f'\n'
2620             f'Traceback (most recent call last):\n'
2621             f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
2622             f'    exception_or_callable()\n'
2623             f'    ~~~~~~~~~~~~~~~~~~~~~^^\n'
2624             f'  File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
2625             f'    raise ImportError(5)\n'
2626             f'ImportError: 5\n')
2627
2628        report = self.get_report(exc)
2629        self.assertEqual(report, expected)
2630
2631    def test_exception_group_nested(self):
2632        def exc():
2633            EG = ExceptionGroup
2634            VE = ValueError
2635            TE = TypeError
2636            try:
2637                try:
2638                    raise EG("nested", [TE(2), TE(3)])
2639                except Exception as e:
2640                    exc = e
2641                raise EG("eg", [VE(1), exc, VE(4)])
2642            except EG:
2643                raise EG("top", [VE(5)])
2644
2645        expected = (f'  + Exception Group Traceback (most recent call last):\n'
2646                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
2647                    f'  |     raise EG("eg", [VE(1), exc, VE(4)])\n'
2648                    f'  | ExceptionGroup: eg (3 sub-exceptions)\n'
2649                    f'  +-+---------------- 1 ----------------\n'
2650                    f'    | ValueError: 1\n'
2651                    f'    +---------------- 2 ----------------\n'
2652                    f'    | Exception Group Traceback (most recent call last):\n'
2653                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
2654                    f'    |     raise EG("nested", [TE(2), TE(3)])\n'
2655                    f'    | ExceptionGroup: nested (2 sub-exceptions)\n'
2656                    f'    +-+---------------- 1 ----------------\n'
2657                    f'      | TypeError: 2\n'
2658                    f'      +---------------- 2 ----------------\n'
2659                    f'      | TypeError: 3\n'
2660                    f'      +------------------------------------\n'
2661                    f'    +---------------- 3 ----------------\n'
2662                    f'    | ValueError: 4\n'
2663                    f'    +------------------------------------\n'
2664                    f'\n'
2665                    f'During handling of the above exception, another exception occurred:\n'
2666                    f'\n'
2667                    f'  + Exception Group Traceback (most recent call last):\n'
2668                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
2669                    f'  |     exception_or_callable()\n'
2670                    f'  |     ~~~~~~~~~~~~~~~~~~~~~^^\n'
2671                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
2672                    f'  |     raise EG("top", [VE(5)])\n'
2673                    f'  | ExceptionGroup: top (1 sub-exception)\n'
2674                    f'  +-+---------------- 1 ----------------\n'
2675                    f'    | ValueError: 5\n'
2676                    f'    +------------------------------------\n')
2677
2678        report = self.get_report(exc)
2679        self.assertEqual(report, expected)
2680
2681    def test_exception_group_width_limit(self):
2682        excs = []
2683        for i in range(1000):
2684            excs.append(ValueError(i))
2685        eg = ExceptionGroup('eg', excs)
2686
2687        expected = ('  | ExceptionGroup: eg (1000 sub-exceptions)\n'
2688                    '  +-+---------------- 1 ----------------\n'
2689                    '    | ValueError: 0\n'
2690                    '    +---------------- 2 ----------------\n'
2691                    '    | ValueError: 1\n'
2692                    '    +---------------- 3 ----------------\n'
2693                    '    | ValueError: 2\n'
2694                    '    +---------------- 4 ----------------\n'
2695                    '    | ValueError: 3\n'
2696                    '    +---------------- 5 ----------------\n'
2697                    '    | ValueError: 4\n'
2698                    '    +---------------- 6 ----------------\n'
2699                    '    | ValueError: 5\n'
2700                    '    +---------------- 7 ----------------\n'
2701                    '    | ValueError: 6\n'
2702                    '    +---------------- 8 ----------------\n'
2703                    '    | ValueError: 7\n'
2704                    '    +---------------- 9 ----------------\n'
2705                    '    | ValueError: 8\n'
2706                    '    +---------------- 10 ----------------\n'
2707                    '    | ValueError: 9\n'
2708                    '    +---------------- 11 ----------------\n'
2709                    '    | ValueError: 10\n'
2710                    '    +---------------- 12 ----------------\n'
2711                    '    | ValueError: 11\n'
2712                    '    +---------------- 13 ----------------\n'
2713                    '    | ValueError: 12\n'
2714                    '    +---------------- 14 ----------------\n'
2715                    '    | ValueError: 13\n'
2716                    '    +---------------- 15 ----------------\n'
2717                    '    | ValueError: 14\n'
2718                    '    +---------------- ... ----------------\n'
2719                    '    | and 985 more exceptions\n'
2720                    '    +------------------------------------\n')
2721
2722        report = self.get_report(eg)
2723        self.assertEqual(report, expected)
2724
2725    def test_exception_group_depth_limit(self):
2726        exc = TypeError('bad type')
2727        for i in range(1000):
2728            exc = ExceptionGroup(
2729                f'eg{i}',
2730                [ValueError(i), exc, ValueError(-i)])
2731
2732        expected = ('  | ExceptionGroup: eg999 (3 sub-exceptions)\n'
2733                    '  +-+---------------- 1 ----------------\n'
2734                    '    | ValueError: 999\n'
2735                    '    +---------------- 2 ----------------\n'
2736                    '    | ExceptionGroup: eg998 (3 sub-exceptions)\n'
2737                    '    +-+---------------- 1 ----------------\n'
2738                    '      | ValueError: 998\n'
2739                    '      +---------------- 2 ----------------\n'
2740                    '      | ExceptionGroup: eg997 (3 sub-exceptions)\n'
2741                    '      +-+---------------- 1 ----------------\n'
2742                    '        | ValueError: 997\n'
2743                    '        +---------------- 2 ----------------\n'
2744                    '        | ExceptionGroup: eg996 (3 sub-exceptions)\n'
2745                    '        +-+---------------- 1 ----------------\n'
2746                    '          | ValueError: 996\n'
2747                    '          +---------------- 2 ----------------\n'
2748                    '          | ExceptionGroup: eg995 (3 sub-exceptions)\n'
2749                    '          +-+---------------- 1 ----------------\n'
2750                    '            | ValueError: 995\n'
2751                    '            +---------------- 2 ----------------\n'
2752                    '            | ExceptionGroup: eg994 (3 sub-exceptions)\n'
2753                    '            +-+---------------- 1 ----------------\n'
2754                    '              | ValueError: 994\n'
2755                    '              +---------------- 2 ----------------\n'
2756                    '              | ExceptionGroup: eg993 (3 sub-exceptions)\n'
2757                    '              +-+---------------- 1 ----------------\n'
2758                    '                | ValueError: 993\n'
2759                    '                +---------------- 2 ----------------\n'
2760                    '                | ExceptionGroup: eg992 (3 sub-exceptions)\n'
2761                    '                +-+---------------- 1 ----------------\n'
2762                    '                  | ValueError: 992\n'
2763                    '                  +---------------- 2 ----------------\n'
2764                    '                  | ExceptionGroup: eg991 (3 sub-exceptions)\n'
2765                    '                  +-+---------------- 1 ----------------\n'
2766                    '                    | ValueError: 991\n'
2767                    '                    +---------------- 2 ----------------\n'
2768                    '                    | ExceptionGroup: eg990 (3 sub-exceptions)\n'
2769                    '                    +-+---------------- 1 ----------------\n'
2770                    '                      | ValueError: 990\n'
2771                    '                      +---------------- 2 ----------------\n'
2772                    '                      | ... (max_group_depth is 10)\n'
2773                    '                      +---------------- 3 ----------------\n'
2774                    '                      | ValueError: -990\n'
2775                    '                      +------------------------------------\n'
2776                    '                    +---------------- 3 ----------------\n'
2777                    '                    | ValueError: -991\n'
2778                    '                    +------------------------------------\n'
2779                    '                  +---------------- 3 ----------------\n'
2780                    '                  | ValueError: -992\n'
2781                    '                  +------------------------------------\n'
2782                    '                +---------------- 3 ----------------\n'
2783                    '                | ValueError: -993\n'
2784                    '                +------------------------------------\n'
2785                    '              +---------------- 3 ----------------\n'
2786                    '              | ValueError: -994\n'
2787                    '              +------------------------------------\n'
2788                    '            +---------------- 3 ----------------\n'
2789                    '            | ValueError: -995\n'
2790                    '            +------------------------------------\n'
2791                    '          +---------------- 3 ----------------\n'
2792                    '          | ValueError: -996\n'
2793                    '          +------------------------------------\n'
2794                    '        +---------------- 3 ----------------\n'
2795                    '        | ValueError: -997\n'
2796                    '        +------------------------------------\n'
2797                    '      +---------------- 3 ----------------\n'
2798                    '      | ValueError: -998\n'
2799                    '      +------------------------------------\n'
2800                    '    +---------------- 3 ----------------\n'
2801                    '    | ValueError: -999\n'
2802                    '    +------------------------------------\n')
2803
2804        report = self.get_report(exc)
2805        self.assertEqual(report, expected)
2806
2807    def test_exception_group_with_notes(self):
2808        def exc():
2809            try:
2810                excs = []
2811                for msg in ['bad value', 'terrible value']:
2812                    try:
2813                        raise ValueError(msg)
2814                    except ValueError as e:
2815                        e.add_note(f'the {msg}')
2816                        excs.append(e)
2817                raise ExceptionGroup("nested", excs)
2818            except ExceptionGroup as e:
2819                e.add_note(('>> Multi line note\n'
2820                            '>> Because I am such\n'
2821                            '>> an important exception.\n'
2822                            '>> empty lines work too\n'
2823                            '\n'
2824                            '(that was an empty line)'))
2825                raise
2826
2827        expected = (f'  + Exception Group Traceback (most recent call last):\n'
2828                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
2829                    f'  |     exception_or_callable()\n'
2830                    f'  |     ~~~~~~~~~~~~~~~~~~~~~^^\n'
2831                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
2832                    f'  |     raise ExceptionGroup("nested", excs)\n'
2833                    f'  | ExceptionGroup: nested (2 sub-exceptions)\n'
2834                    f'  | >> Multi line note\n'
2835                    f'  | >> Because I am such\n'
2836                    f'  | >> an important exception.\n'
2837                    f'  | >> empty lines work too\n'
2838                    f'  | \n'
2839                    f'  | (that was an empty line)\n'
2840                    f'  +-+---------------- 1 ----------------\n'
2841                    f'    | Traceback (most recent call last):\n'
2842                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
2843                    f'    |     raise ValueError(msg)\n'
2844                    f'    | ValueError: bad value\n'
2845                    f'    | the bad value\n'
2846                    f'    +---------------- 2 ----------------\n'
2847                    f'    | Traceback (most recent call last):\n'
2848                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
2849                    f'    |     raise ValueError(msg)\n'
2850                    f'    | ValueError: terrible value\n'
2851                    f'    | the terrible value\n'
2852                    f'    +------------------------------------\n')
2853
2854        report = self.get_report(exc)
2855        self.assertEqual(report, expected)
2856
2857    def test_exception_group_with_multiple_notes(self):
2858        def exc():
2859            try:
2860                excs = []
2861                for msg in ['bad value', 'terrible value']:
2862                    try:
2863                        raise ValueError(msg)
2864                    except ValueError as e:
2865                        e.add_note(f'the {msg}')
2866                        e.add_note(f'Goodbye {msg}')
2867                        excs.append(e)
2868                raise ExceptionGroup("nested", excs)
2869            except ExceptionGroup as e:
2870                e.add_note(('>> Multi line note\n'
2871                            '>> Because I am such\n'
2872                            '>> an important exception.\n'
2873                            '>> empty lines work too\n'
2874                            '\n'
2875                            '(that was an empty line)'))
2876                e.add_note('Goodbye!')
2877                raise
2878
2879        expected = (f'  + Exception Group Traceback (most recent call last):\n'
2880                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
2881                    f'  |     exception_or_callable()\n'
2882                    f'  |     ~~~~~~~~~~~~~~~~~~~~~^^\n'
2883                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
2884                    f'  |     raise ExceptionGroup("nested", excs)\n'
2885                    f'  | ExceptionGroup: nested (2 sub-exceptions)\n'
2886                    f'  | >> Multi line note\n'
2887                    f'  | >> Because I am such\n'
2888                    f'  | >> an important exception.\n'
2889                    f'  | >> empty lines work too\n'
2890                    f'  | \n'
2891                    f'  | (that was an empty line)\n'
2892                    f'  | Goodbye!\n'
2893                    f'  +-+---------------- 1 ----------------\n'
2894                    f'    | Traceback (most recent call last):\n'
2895                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
2896                    f'    |     raise ValueError(msg)\n'
2897                    f'    | ValueError: bad value\n'
2898                    f'    | the bad value\n'
2899                    f'    | Goodbye bad value\n'
2900                    f'    +---------------- 2 ----------------\n'
2901                    f'    | Traceback (most recent call last):\n'
2902                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
2903                    f'    |     raise ValueError(msg)\n'
2904                    f'    | ValueError: terrible value\n'
2905                    f'    | the terrible value\n'
2906                    f'    | Goodbye terrible value\n'
2907                    f'    +------------------------------------\n')
2908
2909        report = self.get_report(exc)
2910        self.assertEqual(report, expected)
2911
2912    def test_KeyboardInterrupt_at_first_line_of_frame(self):
2913        # see GH-93249
2914        def f():
2915            return sys._getframe()
2916
2917        tb_next = None
2918        frame = f()
2919        lasti = 0
2920        lineno = f.__code__.co_firstlineno
2921        tb = types.TracebackType(tb_next, frame, lasti, lineno)
2922
2923        exc = KeyboardInterrupt()
2924        exc.__traceback__ = tb
2925
2926        expected = (f'Traceback (most recent call last):\n'
2927                    f'  File "{__file__}", line {lineno}, in f\n'
2928                    f'    def f():\n'
2929                    f'\n'
2930                    f'KeyboardInterrupt\n')
2931
2932        report = self.get_report(exc)
2933        # remove trailing writespace:
2934        report = '\n'.join([l.rstrip() for l in report.split('\n')])
2935        self.assertEqual(report, expected)
2936
2937
2938class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
2939    #
2940    # This checks reporting through the 'traceback' module, with both
2941    # format_exception() and print_exception().
2942    #
2943
2944    def get_report(self, e):
2945        e = self.get_exception(e)
2946        s = ''.join(
2947            traceback.format_exception(type(e), e, e.__traceback__))
2948        with captured_output("stderr") as sio:
2949            traceback.print_exception(type(e), e, e.__traceback__)
2950        self.assertEqual(sio.getvalue(), s)
2951        return s
2952
2953
2954class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
2955    #
2956    # This checks built-in reporting by the interpreter.
2957    #
2958
2959    @cpython_only
2960    def get_report(self, e):
2961        from _testcapi import exception_print
2962        e = self.get_exception(e)
2963        with captured_output("stderr") as s:
2964            exception_print(e)
2965        return s.getvalue()
2966
2967
2968class LimitTests(unittest.TestCase):
2969
2970    ''' Tests for limit argument.
2971        It's enough to test extact_tb, extract_stack and format_exception '''
2972
2973    def last_raises1(self):
2974        raise Exception('Last raised')
2975
2976    def last_raises2(self):
2977        self.last_raises1()
2978
2979    def last_raises3(self):
2980        self.last_raises2()
2981
2982    def last_raises4(self):
2983        self.last_raises3()
2984
2985    def last_raises5(self):
2986        self.last_raises4()
2987
2988    def last_returns_frame1(self):
2989        return sys._getframe()
2990
2991    def last_returns_frame2(self):
2992        return self.last_returns_frame1()
2993
2994    def last_returns_frame3(self):
2995        return self.last_returns_frame2()
2996
2997    def last_returns_frame4(self):
2998        return self.last_returns_frame3()
2999
3000    def last_returns_frame5(self):
3001        return self.last_returns_frame4()
3002
3003    def test_extract_stack(self):
3004        frame = self.last_returns_frame5()
3005        def extract(**kwargs):
3006            return traceback.extract_stack(frame, **kwargs)
3007        def assertEqualExcept(actual, expected, ignore):
3008            self.assertEqual(actual[:ignore], expected[:ignore])
3009            self.assertEqual(actual[ignore+1:], expected[ignore+1:])
3010            self.assertEqual(len(actual), len(expected))
3011
3012        with support.swap_attr(sys, 'tracebacklimit', 1000):
3013            nolim = extract()
3014            self.assertGreater(len(nolim), 5)
3015            self.assertEqual(extract(limit=2), nolim[-2:])
3016            assertEqualExcept(extract(limit=100), nolim[-100:], -5-1)
3017            self.assertEqual(extract(limit=-2), nolim[:2])
3018            assertEqualExcept(extract(limit=-100), nolim[:100], len(nolim)-5-1)
3019            self.assertEqual(extract(limit=0), [])
3020            del sys.tracebacklimit
3021            assertEqualExcept(extract(), nolim, -5-1)
3022            sys.tracebacklimit = 2
3023            self.assertEqual(extract(), nolim[-2:])
3024            self.assertEqual(extract(limit=3), nolim[-3:])
3025            self.assertEqual(extract(limit=-3), nolim[:3])
3026            sys.tracebacklimit = 0
3027            self.assertEqual(extract(), [])
3028            sys.tracebacklimit = -1
3029            self.assertEqual(extract(), [])
3030
3031    def test_extract_tb(self):
3032        try:
3033            self.last_raises5()
3034        except Exception as e:
3035            tb = e.__traceback__
3036        def extract(**kwargs):
3037            return traceback.extract_tb(tb, **kwargs)
3038
3039        with support.swap_attr(sys, 'tracebacklimit', 1000):
3040            nolim = extract()
3041            self.assertEqual(len(nolim), 5+1)
3042            self.assertEqual(extract(limit=2), nolim[:2])
3043            self.assertEqual(extract(limit=10), nolim)
3044            self.assertEqual(extract(limit=-2), nolim[-2:])
3045            self.assertEqual(extract(limit=-10), nolim)
3046            self.assertEqual(extract(limit=0), [])
3047            del sys.tracebacklimit
3048            self.assertEqual(extract(), nolim)
3049            sys.tracebacklimit = 2
3050            self.assertEqual(extract(), nolim[:2])
3051            self.assertEqual(extract(limit=3), nolim[:3])
3052            self.assertEqual(extract(limit=-3), nolim[-3:])
3053            sys.tracebacklimit = 0
3054            self.assertEqual(extract(), [])
3055            sys.tracebacklimit = -1
3056            self.assertEqual(extract(), [])
3057
3058    def test_format_exception(self):
3059        try:
3060            self.last_raises5()
3061        except Exception as e:
3062            exc = e
3063        # [1:-1] to exclude "Traceback (...)" header and
3064        # exception type and value
3065        def extract(**kwargs):
3066            return traceback.format_exception(exc, **kwargs)[1:-1]
3067
3068        with support.swap_attr(sys, 'tracebacklimit', 1000):
3069            nolim = extract()
3070            self.assertEqual(len(nolim), 5+1)
3071            self.assertEqual(extract(limit=2), nolim[:2])
3072            self.assertEqual(extract(limit=10), nolim)
3073            self.assertEqual(extract(limit=-2), nolim[-2:])
3074            self.assertEqual(extract(limit=-10), nolim)
3075            self.assertEqual(extract(limit=0), [])
3076            del sys.tracebacklimit
3077            self.assertEqual(extract(), nolim)
3078            sys.tracebacklimit = 2
3079            self.assertEqual(extract(), nolim[:2])
3080            self.assertEqual(extract(limit=3), nolim[:3])
3081            self.assertEqual(extract(limit=-3), nolim[-3:])
3082            sys.tracebacklimit = 0
3083            self.assertEqual(extract(), [])
3084            sys.tracebacklimit = -1
3085            self.assertEqual(extract(), [])
3086
3087
3088class MiscTracebackCases(unittest.TestCase):
3089    #
3090    # Check non-printing functions in traceback module
3091    #
3092
3093    def test_clear(self):
3094        def outer():
3095            middle()
3096        def middle():
3097            inner()
3098        def inner():
3099            i = 1
3100            1/0
3101
3102        try:
3103            outer()
3104        except BaseException as e:
3105            tb = e.__traceback__
3106
3107        # Initial assertion: there's one local in the inner frame.
3108        inner_frame = tb.tb_next.tb_next.tb_next.tb_frame
3109        self.assertEqual(len(inner_frame.f_locals), 1)
3110
3111        # Clear traceback frames
3112        traceback.clear_frames(tb)
3113
3114        # Local variable dict should now be empty.
3115        self.assertEqual(len(inner_frame.f_locals), 0)
3116
3117    def test_extract_stack(self):
3118        def extract():
3119            return traceback.extract_stack()
3120        result = extract()
3121        lineno = extract.__code__.co_firstlineno
3122        self.assertEqual(result[-2:], [
3123            (__file__, lineno+2, 'test_extract_stack', 'result = extract()'),
3124            (__file__, lineno+1, 'extract', 'return traceback.extract_stack()'),
3125            ])
3126        self.assertEqual(len(result[0]), 4)
3127
3128
3129class TestFrame(unittest.TestCase):
3130
3131    def test_basics(self):
3132        linecache.clearcache()
3133        linecache.lazycache("f", globals())
3134        f = traceback.FrameSummary("f", 1, "dummy")
3135        self.assertEqual(f,
3136            ("f", 1, "dummy", '"""Test cases for traceback module"""'))
3137        self.assertEqual(tuple(f),
3138            ("f", 1, "dummy", '"""Test cases for traceback module"""'))
3139        self.assertEqual(f, traceback.FrameSummary("f", 1, "dummy"))
3140        self.assertEqual(f, tuple(f))
3141        # Since tuple.__eq__ doesn't support FrameSummary, the equality
3142        # operator fallbacks to FrameSummary.__eq__.
3143        self.assertEqual(tuple(f), f)
3144        self.assertIsNone(f.locals)
3145        self.assertNotEqual(f, object())
3146        self.assertEqual(f, ALWAYS_EQ)
3147
3148    def test_lazy_lines(self):
3149        linecache.clearcache()
3150        f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
3151        self.assertEqual(None, f._lines)
3152        linecache.lazycache("f", globals())
3153        self.assertEqual(
3154            '"""Test cases for traceback module"""',
3155            f.line)
3156
3157    def test_no_line(self):
3158        f = traceback.FrameSummary("f", None, "dummy")
3159        self.assertEqual(f.line, None)
3160
3161    def test_explicit_line(self):
3162        f = traceback.FrameSummary("f", 1, "dummy", line="line")
3163        self.assertEqual("line", f.line)
3164
3165    def test_len(self):
3166        f = traceback.FrameSummary("f", 1, "dummy", line="line")
3167        self.assertEqual(len(f), 4)
3168
3169
3170class TestStack(unittest.TestCase):
3171
3172    def test_walk_stack(self):
3173        def deeper():
3174            return list(traceback.walk_stack(None))
3175        s1 = list(traceback.walk_stack(None))
3176        s2 = deeper()
3177        self.assertEqual(len(s2) - len(s1), 1)
3178        self.assertEqual(s2[1:], s1)
3179
3180    def test_walk_tb(self):
3181        try:
3182            1/0
3183        except Exception as e:
3184            tb = e.__traceback__
3185        s = list(traceback.walk_tb(tb))
3186        self.assertEqual(len(s), 1)
3187
3188    def test_extract_stack(self):
3189        s = traceback.StackSummary.extract(traceback.walk_stack(None))
3190        self.assertIsInstance(s, traceback.StackSummary)
3191
3192    def test_extract_stack_limit(self):
3193        s = traceback.StackSummary.extract(traceback.walk_stack(None), limit=5)
3194        self.assertEqual(len(s), 5)
3195
3196    def test_extract_stack_lookup_lines(self):
3197        linecache.clearcache()
3198        linecache.updatecache('/foo.py', globals())
3199        c = test_code('/foo.py', 'method')
3200        f = test_frame(c, None, None)
3201        s = traceback.StackSummary.extract(iter([(f, 6)]), lookup_lines=True)
3202        linecache.clearcache()
3203        self.assertEqual(s[0].line, "import sys")
3204
3205    def test_extract_stackup_deferred_lookup_lines(self):
3206        linecache.clearcache()
3207        c = test_code('/foo.py', 'method')
3208        f = test_frame(c, None, None)
3209        s = traceback.StackSummary.extract(iter([(f, 6)]), lookup_lines=False)
3210        self.assertEqual({}, linecache.cache)
3211        linecache.updatecache('/foo.py', globals())
3212        self.assertEqual(s[0].line, "import sys")
3213
3214    def test_from_list(self):
3215        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
3216        self.assertEqual(
3217            ['  File "foo.py", line 1, in fred\n    line\n'],
3218            s.format())
3219
3220    def test_from_list_edited_stack(self):
3221        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
3222        s[0] = ('foo.py', 2, 'fred', 'line')
3223        s2 = traceback.StackSummary.from_list(s)
3224        self.assertEqual(
3225            ['  File "foo.py", line 2, in fred\n    line\n'],
3226            s2.format())
3227
3228    def test_format_smoke(self):
3229        # For detailed tests see the format_list tests, which consume the same
3230        # code.
3231        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
3232        self.assertEqual(
3233            ['  File "foo.py", line 1, in fred\n    line\n'],
3234            s.format())
3235
3236    def test_locals(self):
3237        linecache.updatecache('/foo.py', globals())
3238        c = test_code('/foo.py', 'method')
3239        f = test_frame(c, globals(), {'something': 1})
3240        s = traceback.StackSummary.extract(iter([(f, 6)]), capture_locals=True)
3241        self.assertEqual(s[0].locals, {'something': '1'})
3242
3243    def test_no_locals(self):
3244        linecache.updatecache('/foo.py', globals())
3245        c = test_code('/foo.py', 'method')
3246        f = test_frame(c, globals(), {'something': 1})
3247        s = traceback.StackSummary.extract(iter([(f, 6)]))
3248        self.assertEqual(s[0].locals, None)
3249
3250    def test_format_locals(self):
3251        def some_inner(k, v):
3252            a = 1
3253            b = 2
3254            return traceback.StackSummary.extract(
3255                traceback.walk_stack(None), capture_locals=True, limit=1)
3256        s = some_inner(3, 4)
3257        self.assertEqual(
3258            ['  File "%s", line %d, in some_inner\n'
3259             '    return traceback.StackSummary.extract(\n'
3260             '    a = 1\n'
3261             '    b = 2\n'
3262             '    k = 3\n'
3263             '    v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
3264            ], s.format())
3265
3266    def test_custom_format_frame(self):
3267        class CustomStackSummary(traceback.StackSummary):
3268            def format_frame_summary(self, frame_summary, colorize=False):
3269                return f'{frame_summary.filename}:{frame_summary.lineno}'
3270
3271        def some_inner():
3272            return CustomStackSummary.extract(
3273                traceback.walk_stack(None), limit=1)
3274
3275        s = some_inner()
3276        self.assertEqual(
3277            s.format(),
3278            [f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
3279
3280    def test_dropping_frames(self):
3281        def f():
3282            1/0
3283
3284        def g():
3285            try:
3286                f()
3287            except Exception as e:
3288                return e.__traceback__
3289
3290        tb = g()
3291
3292        class Skip_G(traceback.StackSummary):
3293            def format_frame_summary(self, frame_summary, colorize=False):
3294                if frame_summary.name == 'g':
3295                    return None
3296                return super().format_frame_summary(frame_summary)
3297
3298        stack = Skip_G.extract(
3299            traceback.walk_tb(tb)).format()
3300
3301        self.assertEqual(len(stack), 1)
3302        lno = f.__code__.co_firstlineno + 1
3303        self.assertEqual(
3304            stack[0],
3305            f'  File "{__file__}", line {lno}, in f\n    1/0\n'
3306        )
3307
3308    def test_summary_should_show_carets(self):
3309        # See: https://github.com/python/cpython/issues/122353
3310
3311        # statement to execute and to get a ZeroDivisionError for a traceback
3312        statement = "abcdef = 1 / 0 and 2.0"
3313        colno = statement.index('1 / 0')
3314        end_colno = colno + len('1 / 0')
3315
3316        # Actual line to use when rendering the traceback
3317        # and whose AST will be extracted (it will be empty).
3318        cached_line = '# this line will be used during rendering'
3319        self.addCleanup(unlink, TESTFN)
3320        with open(TESTFN, "w") as file:
3321            file.write(cached_line)
3322        linecache.updatecache(TESTFN, {})
3323
3324        try:
3325            exec(compile(statement, TESTFN, "exec"))
3326        except ZeroDivisionError as exc:
3327            # This is the simplest way to create a StackSummary
3328            # whose FrameSummary items have their column offsets.
3329            s = traceback.TracebackException.from_exception(exc).stack
3330            self.assertIsInstance(s, traceback.StackSummary)
3331            with unittest.mock.patch.object(s, '_should_show_carets',
3332                                            wraps=s._should_show_carets) as ff:
3333                self.assertEqual(len(s), 2)
3334                self.assertListEqual(
3335                    s.format_frame_summary(s[1]).splitlines(),
3336                    [
3337                        f'  File "{TESTFN}", line 1, in <module>',
3338                        f'    {cached_line}'
3339                     ]
3340                )
3341                ff.assert_called_with(colno, end_colno, [cached_line], None)
3342
3343class Unrepresentable:
3344    def __repr__(self) -> str:
3345        raise Exception("Unrepresentable")
3346
3347class TestTracebackException(unittest.TestCase):
3348    def do_test_smoke(self, exc, expected_type_str):
3349        try:
3350            raise exc
3351        except Exception as e:
3352            exc_obj = e
3353            exc = traceback.TracebackException.from_exception(e)
3354            expected_stack = traceback.StackSummary.extract(
3355                traceback.walk_tb(e.__traceback__))
3356        self.assertEqual(None, exc.__cause__)
3357        self.assertEqual(None, exc.__context__)
3358        self.assertEqual(False, exc.__suppress_context__)
3359        self.assertEqual(expected_stack, exc.stack)
3360        with self.assertWarns(DeprecationWarning):
3361            self.assertEqual(type(exc_obj), exc.exc_type)
3362        self.assertEqual(expected_type_str, exc.exc_type_str)
3363        self.assertEqual(str(exc_obj), str(exc))
3364
3365    def test_smoke_builtin(self):
3366        self.do_test_smoke(ValueError(42), 'ValueError')
3367
3368    def test_smoke_user_exception(self):
3369        class MyException(Exception):
3370            pass
3371
3372        if __name__ == '__main__':
3373            expected = ('TestTracebackException.'
3374                        'test_smoke_user_exception.<locals>.MyException')
3375        else:
3376            expected = ('test.test_traceback.TestTracebackException.'
3377                        'test_smoke_user_exception.<locals>.MyException')
3378        self.do_test_smoke(MyException('bad things happened'), expected)
3379
3380    def test_from_exception(self):
3381        # Check all the parameters are accepted.
3382        def foo():
3383            1/0
3384        try:
3385            foo()
3386        except Exception as e:
3387            exc_obj = e
3388            tb = e.__traceback__
3389            self.expected_stack = traceback.StackSummary.extract(
3390                traceback.walk_tb(tb), limit=1, lookup_lines=False,
3391                capture_locals=True)
3392            self.exc = traceback.TracebackException.from_exception(
3393                e, limit=1, lookup_lines=False, capture_locals=True)
3394        expected_stack = self.expected_stack
3395        exc = self.exc
3396        self.assertEqual(None, exc.__cause__)
3397        self.assertEqual(None, exc.__context__)
3398        self.assertEqual(False, exc.__suppress_context__)
3399        self.assertEqual(expected_stack, exc.stack)
3400        with self.assertWarns(DeprecationWarning):
3401            self.assertEqual(type(exc_obj), exc.exc_type)
3402        self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
3403        self.assertEqual(str(exc_obj), str(exc))
3404
3405    def test_cause(self):
3406        try:
3407            try:
3408                1/0
3409            finally:
3410                exc = sys.exception()
3411                exc_context = traceback.TracebackException.from_exception(exc)
3412                cause = Exception("cause")
3413                raise Exception("uh oh") from cause
3414        except Exception as e:
3415            exc_obj = e
3416            exc = traceback.TracebackException.from_exception(e)
3417            expected_stack = traceback.StackSummary.extract(
3418                traceback.walk_tb(e.__traceback__))
3419        exc_cause = traceback.TracebackException(Exception, cause, None)
3420        self.assertEqual(exc_cause, exc.__cause__)
3421        self.assertEqual(exc_context, exc.__context__)
3422        self.assertEqual(True, exc.__suppress_context__)
3423        self.assertEqual(expected_stack, exc.stack)
3424        with self.assertWarns(DeprecationWarning):
3425            self.assertEqual(type(exc_obj), exc.exc_type)
3426        self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
3427        self.assertEqual(str(exc_obj), str(exc))
3428
3429    def test_context(self):
3430        try:
3431            try:
3432                1/0
3433            finally:
3434                exc = sys.exception()
3435                exc_context = traceback.TracebackException.from_exception(exc)
3436                raise Exception("uh oh")
3437        except Exception as e:
3438            exc_obj = e
3439            exc = traceback.TracebackException.from_exception(e)
3440            expected_stack = traceback.StackSummary.extract(
3441                traceback.walk_tb(e.__traceback__))
3442        self.assertEqual(None, exc.__cause__)
3443        self.assertEqual(exc_context, exc.__context__)
3444        self.assertEqual(False, exc.__suppress_context__)
3445        self.assertEqual(expected_stack, exc.stack)
3446        with self.assertWarns(DeprecationWarning):
3447            self.assertEqual(type(exc_obj), exc.exc_type)
3448        self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
3449        self.assertEqual(str(exc_obj), str(exc))
3450
3451    def test_long_context_chain(self):
3452        def f():
3453            try:
3454                1/0
3455            except ZeroDivisionError:
3456                f()
3457
3458        try:
3459            f()
3460        except RecursionError as e:
3461            exc_obj = e
3462        else:
3463            self.fail("Exception not raised")
3464
3465        te = traceback.TracebackException.from_exception(exc_obj)
3466        res = list(te.format())
3467
3468        # many ZeroDiv errors followed by the RecursionError
3469        self.assertGreater(len(res), sys.getrecursionlimit())
3470        self.assertGreater(
3471            len([l for l in res if 'ZeroDivisionError:' in l]),
3472            sys.getrecursionlimit() * 0.5)
3473        self.assertIn(
3474            "RecursionError: maximum recursion depth exceeded", res[-1])
3475
3476    def test_compact_with_cause(self):
3477        try:
3478            try:
3479                1/0
3480            finally:
3481                cause = Exception("cause")
3482                raise Exception("uh oh") from cause
3483        except Exception as e:
3484            exc_obj = e
3485            exc = traceback.TracebackException.from_exception(exc_obj, compact=True)
3486            expected_stack = traceback.StackSummary.extract(
3487                traceback.walk_tb(exc_obj.__traceback__))
3488        exc_cause = traceback.TracebackException(Exception, cause, None)
3489        self.assertEqual(exc_cause, exc.__cause__)
3490        self.assertEqual(None, exc.__context__)
3491        self.assertEqual(True, exc.__suppress_context__)
3492        self.assertEqual(expected_stack, exc.stack)
3493        with self.assertWarns(DeprecationWarning):
3494            self.assertEqual(type(exc_obj), exc.exc_type)
3495        self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
3496        self.assertEqual(str(exc_obj), str(exc))
3497
3498    def test_compact_no_cause(self):
3499        try:
3500            try:
3501                1/0
3502            finally:
3503                exc = sys.exception()
3504                exc_context = traceback.TracebackException.from_exception(exc)
3505                raise Exception("uh oh")
3506        except Exception as e:
3507            exc_obj = e
3508            exc = traceback.TracebackException.from_exception(e, compact=True)
3509            expected_stack = traceback.StackSummary.extract(
3510                traceback.walk_tb(exc_obj.__traceback__))
3511        self.assertEqual(None, exc.__cause__)
3512        self.assertEqual(exc_context, exc.__context__)
3513        self.assertEqual(False, exc.__suppress_context__)
3514        self.assertEqual(expected_stack, exc.stack)
3515        with self.assertWarns(DeprecationWarning):
3516            self.assertEqual(type(exc_obj), exc.exc_type)
3517        self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
3518        self.assertEqual(str(exc_obj), str(exc))
3519
3520    def test_no_save_exc_type(self):
3521        try:
3522            1/0
3523        except Exception as e:
3524            exc = e
3525
3526        te = traceback.TracebackException.from_exception(
3527                 exc, save_exc_type=False)
3528        with self.assertWarns(DeprecationWarning):
3529            self.assertIsNone(te.exc_type)
3530
3531    def test_no_refs_to_exception_and_traceback_objects(self):
3532        try:
3533            1/0
3534        except Exception as e:
3535            exc_obj = e
3536
3537        refcnt1 = sys.getrefcount(exc_obj)
3538        refcnt2 = sys.getrefcount(exc_obj.__traceback__)
3539        exc = traceback.TracebackException.from_exception(exc_obj)
3540        self.assertEqual(sys.getrefcount(exc_obj), refcnt1)
3541        self.assertEqual(sys.getrefcount(exc_obj.__traceback__), refcnt2)
3542
3543    def test_comparison_basic(self):
3544        try:
3545            1/0
3546        except Exception as e:
3547            exc_obj = e
3548            exc = traceback.TracebackException.from_exception(exc_obj)
3549            exc2 = traceback.TracebackException.from_exception(exc_obj)
3550        self.assertIsNot(exc, exc2)
3551        self.assertEqual(exc, exc2)
3552        self.assertNotEqual(exc, object())
3553        self.assertEqual(exc, ALWAYS_EQ)
3554
3555    def test_comparison_params_variations(self):
3556        def raise_exc():
3557            try:
3558                raise ValueError('bad value')
3559            except ValueError:
3560                raise
3561
3562        def raise_with_locals():
3563            x, y = 1, 2
3564            raise_exc()
3565
3566        try:
3567            raise_with_locals()
3568        except Exception as e:
3569            exc_obj = e
3570
3571        exc = traceback.TracebackException.from_exception(exc_obj)
3572        exc1 = traceback.TracebackException.from_exception(exc_obj, limit=10)
3573        exc2 = traceback.TracebackException.from_exception(exc_obj, limit=2)
3574
3575        self.assertEqual(exc, exc1)      # limit=10 gets all frames
3576        self.assertNotEqual(exc, exc2)   # limit=2 truncates the output
3577
3578        # locals change the output
3579        exc3 = traceback.TracebackException.from_exception(exc_obj, capture_locals=True)
3580        self.assertNotEqual(exc, exc3)
3581
3582        # there are no locals in the innermost frame
3583        exc4 = traceback.TracebackException.from_exception(exc_obj, limit=-1)
3584        exc5 = traceback.TracebackException.from_exception(exc_obj, limit=-1, capture_locals=True)
3585        self.assertEqual(exc4, exc5)
3586
3587        # there are locals in the next-to-innermost frame
3588        exc6 = traceback.TracebackException.from_exception(exc_obj, limit=-2)
3589        exc7 = traceback.TracebackException.from_exception(exc_obj, limit=-2, capture_locals=True)
3590        self.assertNotEqual(exc6, exc7)
3591
3592    def test_comparison_equivalent_exceptions_are_equal(self):
3593        excs = []
3594        for _ in range(2):
3595            try:
3596                1/0
3597            except Exception as e:
3598                excs.append(traceback.TracebackException.from_exception(e))
3599        self.assertEqual(excs[0], excs[1])
3600        self.assertEqual(list(excs[0].format()), list(excs[1].format()))
3601
3602    def test_unhashable(self):
3603        class UnhashableException(Exception):
3604            def __eq__(self, other):
3605                return True
3606
3607        ex1 = UnhashableException('ex1')
3608        ex2 = UnhashableException('ex2')
3609        try:
3610            raise ex2 from ex1
3611        except UnhashableException:
3612            try:
3613                raise ex1
3614            except UnhashableException as e:
3615                exc_obj = e
3616        exc = traceback.TracebackException.from_exception(exc_obj)
3617        formatted = list(exc.format())
3618        self.assertIn('UnhashableException: ex2\n', formatted[2])
3619        self.assertIn('UnhashableException: ex1\n', formatted[6])
3620
3621    def test_limit(self):
3622        def recurse(n):
3623            if n:
3624                recurse(n-1)
3625            else:
3626                1/0
3627        try:
3628            recurse(10)
3629        except Exception as e:
3630            exc = traceback.TracebackException.from_exception(e, limit=5)
3631            expected_stack = traceback.StackSummary.extract(
3632                traceback.walk_tb(e.__traceback__), limit=5)
3633        self.assertEqual(expected_stack, exc.stack)
3634
3635    def test_lookup_lines(self):
3636        linecache.clearcache()
3637        e = Exception("uh oh")
3638        c = test_code('/foo.py', 'method')
3639        f = test_frame(c, None, None)
3640        tb = test_tb(f, 6, None, 0)
3641        exc = traceback.TracebackException(Exception, e, tb, lookup_lines=False)
3642        self.assertEqual(linecache.cache, {})
3643        linecache.updatecache('/foo.py', globals())
3644        self.assertEqual(exc.stack[0].line, "import sys")
3645
3646    def test_locals(self):
3647        linecache.updatecache('/foo.py', globals())
3648        e = Exception("uh oh")
3649        c = test_code('/foo.py', 'method')
3650        f = test_frame(c, globals(), {'something': 1, 'other': 'string', 'unrepresentable': Unrepresentable()})
3651        tb = test_tb(f, 6, None, 0)
3652        exc = traceback.TracebackException(
3653            Exception, e, tb, capture_locals=True)
3654        self.assertEqual(
3655            exc.stack[0].locals,
3656            {'something': '1', 'other': "'string'", 'unrepresentable': '<local repr() failed>'})
3657
3658    def test_no_locals(self):
3659        linecache.updatecache('/foo.py', globals())
3660        e = Exception("uh oh")
3661        c = test_code('/foo.py', 'method')
3662        f = test_frame(c, globals(), {'something': 1})
3663        tb = test_tb(f, 6, None, 0)
3664        exc = traceback.TracebackException(Exception, e, tb)
3665        self.assertEqual(exc.stack[0].locals, None)
3666
3667    def test_traceback_header(self):
3668        # do not print a traceback header if exc_traceback is None
3669        # see issue #24695
3670        exc = traceback.TracebackException(Exception, Exception("haven"), None)
3671        self.assertEqual(list(exc.format()), ["Exception: haven\n"])
3672
3673    @requires_debug_ranges()
3674    def test_print(self):
3675        def f():
3676            x = 12
3677            try:
3678                x/0
3679            except Exception as e:
3680                return e
3681        exc = traceback.TracebackException.from_exception(f(), capture_locals=True)
3682        output = StringIO()
3683        exc.print(file=output)
3684        self.assertEqual(
3685            output.getvalue().split('\n')[-5:],
3686            ['    x/0',
3687             '    ~^~',
3688             '    x = 12',
3689             'ZeroDivisionError: division by zero',
3690             ''])
3691
3692
3693class TestTracebackException_ExceptionGroups(unittest.TestCase):
3694    def setUp(self):
3695        super().setUp()
3696        self.eg = self._get_exception_group()
3697
3698    def _get_exception_group(self):
3699        def f():
3700            1/0
3701
3702        def g(v):
3703            raise ValueError(v)
3704
3705        self.lno_f = f.__code__.co_firstlineno
3706        self.lno_g = g.__code__.co_firstlineno
3707
3708        try:
3709            try:
3710                try:
3711                    f()
3712                except Exception as e:
3713                    exc1 = e
3714                try:
3715                    g(42)
3716                except Exception as e:
3717                    exc2 = e
3718                raise ExceptionGroup("eg1", [exc1, exc2])
3719            except ExceptionGroup as e:
3720                exc3 = e
3721            try:
3722                g(24)
3723            except Exception as e:
3724                exc4 = e
3725            raise ExceptionGroup("eg2", [exc3, exc4])
3726        except ExceptionGroup as eg:
3727            return eg
3728        self.fail('Exception Not Raised')
3729
3730    def test_exception_group_construction(self):
3731        eg = self.eg
3732        teg1 = traceback.TracebackException(type(eg), eg, eg.__traceback__)
3733        teg2 = traceback.TracebackException.from_exception(eg)
3734        self.assertIsNot(teg1, teg2)
3735        self.assertEqual(teg1, teg2)
3736
3737    def test_exception_group_format_exception_only(self):
3738        teg = traceback.TracebackException.from_exception(self.eg)
3739        formatted = ''.join(teg.format_exception_only()).split('\n')
3740        expected = "ExceptionGroup: eg2 (2 sub-exceptions)\n".split('\n')
3741
3742        self.assertEqual(formatted, expected)
3743
3744    def test_exception_group_format_exception_onlyi_recursive(self):
3745        teg = traceback.TracebackException.from_exception(self.eg)
3746        formatted = ''.join(teg.format_exception_only(show_group=True)).split('\n')
3747        expected = [
3748                     'ExceptionGroup: eg2 (2 sub-exceptions)',
3749                     '   ExceptionGroup: eg1 (2 sub-exceptions)',
3750                     '      ZeroDivisionError: division by zero',
3751                     '      ValueError: 42',
3752                     '   ValueError: 24',
3753                     ''
3754                   ]
3755
3756        self.assertEqual(formatted, expected)
3757
3758    def test_exception_group_format(self):
3759        teg = traceback.TracebackException.from_exception(self.eg)
3760
3761        formatted = ''.join(teg.format()).split('\n')
3762        lno_f = self.lno_f
3763        lno_g = self.lno_g
3764
3765        expected = [
3766                    f'  + Exception Group Traceback (most recent call last):',
3767                    f'  |   File "{__file__}", line {lno_g+23}, in _get_exception_group',
3768                    f'  |     raise ExceptionGroup("eg2", [exc3, exc4])',
3769                    f'  | ExceptionGroup: eg2 (2 sub-exceptions)',
3770                    f'  +-+---------------- 1 ----------------',
3771                    f'    | Exception Group Traceback (most recent call last):',
3772                    f'    |   File "{__file__}", line {lno_g+16}, in _get_exception_group',
3773                    f'    |     raise ExceptionGroup("eg1", [exc1, exc2])',
3774                    f'    | ExceptionGroup: eg1 (2 sub-exceptions)',
3775                    f'    +-+---------------- 1 ----------------',
3776                    f'      | Traceback (most recent call last):',
3777                    f'      |   File "{__file__}", line {lno_g+9}, in _get_exception_group',
3778                    f'      |     f()',
3779                    f'      |     ~^^',
3780                    f'      |   File "{__file__}", line {lno_f+1}, in f',
3781                    f'      |     1/0',
3782                    f'      |     ~^~',
3783                    f'      | ZeroDivisionError: division by zero',
3784                    f'      +---------------- 2 ----------------',
3785                    f'      | Traceback (most recent call last):',
3786                    f'      |   File "{__file__}", line {lno_g+13}, in _get_exception_group',
3787                    f'      |     g(42)',
3788                    f'      |     ~^^^^',
3789                    f'      |   File "{__file__}", line {lno_g+1}, in g',
3790                    f'      |     raise ValueError(v)',
3791                    f'      | ValueError: 42',
3792                    f'      +------------------------------------',
3793                    f'    +---------------- 2 ----------------',
3794                    f'    | Traceback (most recent call last):',
3795                    f'    |   File "{__file__}", line {lno_g+20}, in _get_exception_group',
3796                    f'    |     g(24)',
3797                    f'    |     ~^^^^',
3798                    f'    |   File "{__file__}", line {lno_g+1}, in g',
3799                    f'    |     raise ValueError(v)',
3800                    f'    | ValueError: 24',
3801                    f'    +------------------------------------',
3802                    f'']
3803
3804        self.assertEqual(formatted, expected)
3805
3806    def test_max_group_width(self):
3807        excs1 = []
3808        excs2 = []
3809        for i in range(3):
3810            excs1.append(ValueError(i))
3811        for i in range(10):
3812            excs2.append(TypeError(i))
3813
3814        EG = ExceptionGroup
3815        eg = EG('eg', [EG('eg1', excs1), EG('eg2', excs2)])
3816
3817        teg = traceback.TracebackException.from_exception(eg, max_group_width=2)
3818        formatted = ''.join(teg.format()).split('\n')
3819
3820        expected = [
3821                    '  | ExceptionGroup: eg (2 sub-exceptions)',
3822                    '  +-+---------------- 1 ----------------',
3823                    '    | ExceptionGroup: eg1 (3 sub-exceptions)',
3824                    '    +-+---------------- 1 ----------------',
3825                    '      | ValueError: 0',
3826                    '      +---------------- 2 ----------------',
3827                    '      | ValueError: 1',
3828                    '      +---------------- ... ----------------',
3829                    '      | and 1 more exception',
3830                    '      +------------------------------------',
3831                    '    +---------------- 2 ----------------',
3832                    '    | ExceptionGroup: eg2 (10 sub-exceptions)',
3833                    '    +-+---------------- 1 ----------------',
3834                    '      | TypeError: 0',
3835                    '      +---------------- 2 ----------------',
3836                    '      | TypeError: 1',
3837                    '      +---------------- ... ----------------',
3838                    '      | and 8 more exceptions',
3839                    '      +------------------------------------',
3840                    '']
3841
3842        self.assertEqual(formatted, expected)
3843
3844    def test_max_group_depth(self):
3845        exc = TypeError('bad type')
3846        for i in range(3):
3847            exc = ExceptionGroup('exc', [ValueError(-i), exc, ValueError(i)])
3848
3849        teg = traceback.TracebackException.from_exception(exc, max_group_depth=2)
3850        formatted = ''.join(teg.format()).split('\n')
3851
3852        expected = [
3853                    '  | ExceptionGroup: exc (3 sub-exceptions)',
3854                    '  +-+---------------- 1 ----------------',
3855                    '    | ValueError: -2',
3856                    '    +---------------- 2 ----------------',
3857                    '    | ExceptionGroup: exc (3 sub-exceptions)',
3858                    '    +-+---------------- 1 ----------------',
3859                    '      | ValueError: -1',
3860                    '      +---------------- 2 ----------------',
3861                    '      | ... (max_group_depth is 2)',
3862                    '      +---------------- 3 ----------------',
3863                    '      | ValueError: 1',
3864                    '      +------------------------------------',
3865                    '    +---------------- 3 ----------------',
3866                    '    | ValueError: 2',
3867                    '    +------------------------------------',
3868                    '']
3869
3870        self.assertEqual(formatted, expected)
3871
3872    def test_comparison(self):
3873        try:
3874            raise self.eg
3875        except ExceptionGroup as e:
3876            exc = e
3877        for _ in range(5):
3878            try:
3879                raise exc
3880            except Exception as e:
3881                exc_obj = e
3882        exc = traceback.TracebackException.from_exception(exc_obj)
3883        exc2 = traceback.TracebackException.from_exception(exc_obj)
3884        exc3 = traceback.TracebackException.from_exception(exc_obj, limit=300)
3885        ne = traceback.TracebackException.from_exception(exc_obj, limit=3)
3886        self.assertIsNot(exc, exc2)
3887        self.assertEqual(exc, exc2)
3888        self.assertEqual(exc, exc3)
3889        self.assertNotEqual(exc, ne)
3890        self.assertNotEqual(exc, object())
3891        self.assertEqual(exc, ALWAYS_EQ)
3892
3893
3894global_for_suggestions = None
3895
3896
3897class SuggestionFormattingTestBase:
3898    def get_suggestion(self, obj, attr_name=None):
3899        if attr_name is not None:
3900            def callable():
3901                getattr(obj, attr_name)
3902        else:
3903            callable = obj
3904
3905        result_lines = self.get_exception(
3906            callable, slice_start=-1, slice_end=None
3907        )
3908        return result_lines[0]
3909
3910    def test_getattr_suggestions(self):
3911        class Substitution:
3912            noise = more_noise = a = bc = None
3913            blech = None
3914
3915        class Elimination:
3916            noise = more_noise = a = bc = None
3917            blch = None
3918
3919        class Addition:
3920            noise = more_noise = a = bc = None
3921            bluchin = None
3922
3923        class SubstitutionOverElimination:
3924            blach = None
3925            bluc = None
3926
3927        class SubstitutionOverAddition:
3928            blach = None
3929            bluchi = None
3930
3931        class EliminationOverAddition:
3932            blucha = None
3933            bluc = None
3934
3935        class CaseChangeOverSubstitution:
3936            Luch = None
3937            fluch = None
3938            BLuch = None
3939
3940        for cls, suggestion in [
3941            (Addition, "'bluchin'?"),
3942            (Substitution, "'blech'?"),
3943            (Elimination, "'blch'?"),
3944            (Addition, "'bluchin'?"),
3945            (SubstitutionOverElimination, "'blach'?"),
3946            (SubstitutionOverAddition, "'blach'?"),
3947            (EliminationOverAddition, "'bluc'?"),
3948            (CaseChangeOverSubstitution, "'BLuch'?"),
3949        ]:
3950            actual = self.get_suggestion(cls(), 'bluch')
3951            self.assertIn(suggestion, actual)
3952
3953    def test_getattr_suggestions_underscored(self):
3954        class A:
3955            bluch = None
3956
3957        self.assertIn("'bluch'", self.get_suggestion(A(), 'blach'))
3958        self.assertIn("'bluch'", self.get_suggestion(A(), '_luch'))
3959        self.assertIn("'bluch'", self.get_suggestion(A(), '_bluch'))
3960
3961        class B:
3962            _bluch = None
3963            def method(self, name):
3964                getattr(self, name)
3965
3966        self.assertIn("'_bluch'", self.get_suggestion(B(), '_blach'))
3967        self.assertIn("'_bluch'", self.get_suggestion(B(), '_luch'))
3968        self.assertNotIn("'_bluch'", self.get_suggestion(B(), 'bluch'))
3969
3970        self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_blach')))
3971        self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_luch')))
3972        self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, 'bluch')))
3973
3974    def test_getattr_suggestions_do_not_trigger_for_long_attributes(self):
3975        class A:
3976            blech = None
3977
3978        actual = self.get_suggestion(A(), 'somethingverywrong')
3979        self.assertNotIn("blech", actual)
3980
3981    def test_getattr_error_bad_suggestions_do_not_trigger_for_small_names(self):
3982        class MyClass:
3983            vvv = mom = w = id = pytho = None
3984
3985        for name in ("b", "v", "m", "py"):
3986            with self.subTest(name=name):
3987                actual = self.get_suggestion(MyClass, name)
3988                self.assertNotIn("Did you mean", actual)
3989                self.assertNotIn("'vvv", actual)
3990                self.assertNotIn("'mom'", actual)
3991                self.assertNotIn("'id'", actual)
3992                self.assertNotIn("'w'", actual)
3993                self.assertNotIn("'pytho'", actual)
3994
3995    def test_getattr_suggestions_do_not_trigger_for_big_dicts(self):
3996        class A:
3997            blech = None
3998        # A class with a very big __dict__ will not be considered
3999        # for suggestions.
4000        for index in range(2000):
4001            setattr(A, f"index_{index}", None)
4002
4003        actual = self.get_suggestion(A(), 'bluch')
4004        self.assertNotIn("blech", actual)
4005
4006    def test_getattr_suggestions_no_args(self):
4007        class A:
4008            blech = None
4009            def __getattr__(self, attr):
4010                raise AttributeError()
4011
4012        actual = self.get_suggestion(A(), 'bluch')
4013        self.assertIn("blech", actual)
4014
4015        class A:
4016            blech = None
4017            def __getattr__(self, attr):
4018                raise AttributeError
4019
4020        actual = self.get_suggestion(A(), 'bluch')
4021        self.assertIn("blech", actual)
4022
4023    def test_getattr_suggestions_invalid_args(self):
4024        class NonStringifyClass:
4025            __str__ = None
4026            __repr__ = None
4027
4028        class A:
4029            blech = None
4030            def __getattr__(self, attr):
4031                raise AttributeError(NonStringifyClass())
4032
4033        class B:
4034            blech = None
4035            def __getattr__(self, attr):
4036                raise AttributeError("Error", 23)
4037
4038        class C:
4039            blech = None
4040            def __getattr__(self, attr):
4041                raise AttributeError(23)
4042
4043        for cls in [A, B, C]:
4044            actual = self.get_suggestion(cls(), 'bluch')
4045            self.assertIn("blech", actual)
4046
4047    def test_getattr_suggestions_for_same_name(self):
4048        class A:
4049            def __dir__(self):
4050                return ['blech']
4051        actual = self.get_suggestion(A(), 'blech')
4052        self.assertNotIn("Did you mean", actual)
4053
4054    def test_attribute_error_with_failing_dict(self):
4055        class T:
4056            bluch = 1
4057            def __dir__(self):
4058                raise AttributeError("oh no!")
4059
4060        actual = self.get_suggestion(T(), 'blich')
4061        self.assertNotIn("blech", actual)
4062        self.assertNotIn("oh no!", actual)
4063
4064    def test_attribute_error_with_bad_name(self):
4065        def raise_attribute_error_with_bad_name():
4066            raise AttributeError(name=12, obj=23)
4067
4068        result_lines = self.get_exception(
4069            raise_attribute_error_with_bad_name, slice_start=-1, slice_end=None
4070        )
4071        self.assertNotIn("?", result_lines[-1])
4072
4073    def test_attribute_error_inside_nested_getattr(self):
4074        class A:
4075            bluch = 1
4076
4077        class B:
4078            def __getattribute__(self, attr):
4079                a = A()
4080                return a.blich
4081
4082        actual = self.get_suggestion(B(), 'something')
4083        self.assertIn("Did you mean", actual)
4084        self.assertIn("bluch", actual)
4085
4086    def make_module(self, code):
4087        tmpdir = Path(tempfile.mkdtemp())
4088        self.addCleanup(shutil.rmtree, tmpdir)
4089
4090        sys.path.append(str(tmpdir))
4091        self.addCleanup(sys.path.pop)
4092
4093        mod_name = ''.join(random.choices(string.ascii_letters, k=16))
4094        module = tmpdir / (mod_name + ".py")
4095        module.write_text(code)
4096
4097        return mod_name
4098
4099    def get_import_from_suggestion(self, mod_dict, name):
4100        modname = self.make_module(mod_dict)
4101
4102        def callable():
4103            try:
4104                exec(f"from {modname} import {name}")
4105            except ImportError as e:
4106                raise e from None
4107            except Exception as e:
4108                self.fail(f"Expected ImportError but got {type(e)}")
4109        self.addCleanup(forget, modname)
4110
4111        result_lines = self.get_exception(
4112            callable, slice_start=-1, slice_end=None
4113        )
4114        return result_lines[0]
4115
4116    def test_import_from_suggestions(self):
4117        substitution = textwrap.dedent("""\
4118            noise = more_noise = a = bc = None
4119            blech = None
4120        """)
4121
4122        elimination = textwrap.dedent("""
4123            noise = more_noise = a = bc = None
4124            blch = None
4125        """)
4126
4127        addition = textwrap.dedent("""
4128            noise = more_noise = a = bc = None
4129            bluchin = None
4130        """)
4131
4132        substitutionOverElimination = textwrap.dedent("""
4133            blach = None
4134            bluc = None
4135        """)
4136
4137        substitutionOverAddition = textwrap.dedent("""
4138            blach = None
4139            bluchi = None
4140        """)
4141
4142        eliminationOverAddition = textwrap.dedent("""
4143            blucha = None
4144            bluc = None
4145        """)
4146
4147        caseChangeOverSubstitution = textwrap.dedent("""
4148            Luch = None
4149            fluch = None
4150            BLuch = None
4151        """)
4152
4153        for code, suggestion in [
4154            (addition, "'bluchin'?"),
4155            (substitution, "'blech'?"),
4156            (elimination, "'blch'?"),
4157            (addition, "'bluchin'?"),
4158            (substitutionOverElimination, "'blach'?"),
4159            (substitutionOverAddition, "'blach'?"),
4160            (eliminationOverAddition, "'bluc'?"),
4161            (caseChangeOverSubstitution, "'BLuch'?"),
4162        ]:
4163            actual = self.get_import_from_suggestion(code, 'bluch')
4164            self.assertIn(suggestion, actual)
4165
4166    def test_import_from_suggestions_underscored(self):
4167        code = "bluch = None"
4168        self.assertIn("'bluch'", self.get_import_from_suggestion(code, 'blach'))
4169        self.assertIn("'bluch'", self.get_import_from_suggestion(code, '_luch'))
4170        self.assertIn("'bluch'", self.get_import_from_suggestion(code, '_bluch'))
4171
4172        code = "_bluch = None"
4173        self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_blach'))
4174        self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_luch'))
4175        self.assertNotIn("'_bluch'", self.get_import_from_suggestion(code, 'bluch'))
4176
4177    def test_import_from_suggestions_do_not_trigger_for_long_attributes(self):
4178        code = "blech = None"
4179
4180        actual = self.get_suggestion(code, 'somethingverywrong')
4181        self.assertNotIn("blech", actual)
4182
4183    def test_import_from_error_bad_suggestions_do_not_trigger_for_small_names(self):
4184        code = "vvv = mom = w = id = pytho = None"
4185
4186        for name in ("b", "v", "m", "py"):
4187            with self.subTest(name=name):
4188                actual = self.get_import_from_suggestion(code, name)
4189                self.assertNotIn("Did you mean", actual)
4190                self.assertNotIn("'vvv'", actual)
4191                self.assertNotIn("'mom'", actual)
4192                self.assertNotIn("'id'", actual)
4193                self.assertNotIn("'w'", actual)
4194                self.assertNotIn("'pytho'", actual)
4195
4196    def test_import_from_suggestions_do_not_trigger_for_big_namespaces(self):
4197        # A module with lots of names will not be considered for suggestions.
4198        chunks = [f"index_{index} = " for index in range(200)]
4199        chunks.append(" None")
4200        code = " ".join(chunks)
4201        actual = self.get_import_from_suggestion(code, 'bluch')
4202        self.assertNotIn("blech", actual)
4203
4204    def test_import_from_error_with_bad_name(self):
4205        def raise_attribute_error_with_bad_name():
4206            raise ImportError(name=12, obj=23, name_from=11)
4207
4208        result_lines = self.get_exception(
4209            raise_attribute_error_with_bad_name, slice_start=-1, slice_end=None
4210        )
4211        self.assertNotIn("?", result_lines[-1])
4212
4213    def test_name_error_suggestions(self):
4214        def Substitution():
4215            noise = more_noise = a = bc = None
4216            blech = None
4217            print(bluch)
4218
4219        def Elimination():
4220            noise = more_noise = a = bc = None
4221            blch = None
4222            print(bluch)
4223
4224        def Addition():
4225            noise = more_noise = a = bc = None
4226            bluchin = None
4227            print(bluch)
4228
4229        def SubstitutionOverElimination():
4230            blach = None
4231            bluc = None
4232            print(bluch)
4233
4234        def SubstitutionOverAddition():
4235            blach = None
4236            bluchi = None
4237            print(bluch)
4238
4239        def EliminationOverAddition():
4240            blucha = None
4241            bluc = None
4242            print(bluch)
4243
4244        for func, suggestion in [(Substitution, "'blech'?"),
4245                                (Elimination, "'blch'?"),
4246                                (Addition, "'bluchin'?"),
4247                                (EliminationOverAddition, "'blucha'?"),
4248                                (SubstitutionOverElimination, "'blach'?"),
4249                                (SubstitutionOverAddition, "'blach'?")]:
4250            actual = self.get_suggestion(func)
4251            self.assertIn(suggestion, actual)
4252
4253    def test_name_error_suggestions_from_globals(self):
4254        def func():
4255            print(global_for_suggestio)
4256        actual = self.get_suggestion(func)
4257        self.assertIn("'global_for_suggestions'?", actual)
4258
4259    def test_name_error_suggestions_from_builtins(self):
4260        def func():
4261            print(ZeroDivisionErrrrr)
4262        actual = self.get_suggestion(func)
4263        self.assertIn("'ZeroDivisionError'?", actual)
4264
4265    def test_name_error_suggestions_from_builtins_when_builtins_is_module(self):
4266        def func():
4267            custom_globals = globals().copy()
4268            custom_globals["__builtins__"] = builtins
4269            print(eval("ZeroDivisionErrrrr", custom_globals))
4270        actual = self.get_suggestion(func)
4271        self.assertIn("'ZeroDivisionError'?", actual)
4272
4273    def test_name_error_suggestions_do_not_trigger_for_long_names(self):
4274        def func():
4275            somethingverywronghehehehehehe = None
4276            print(somethingverywronghe)
4277        actual = self.get_suggestion(func)
4278        self.assertNotIn("somethingverywronghehe", actual)
4279
4280    def test_name_error_bad_suggestions_do_not_trigger_for_small_names(self):
4281
4282        def f_b():
4283            vvv = mom = w = id = pytho = None
4284            b
4285
4286        def f_v():
4287            vvv = mom = w = id = pytho = None
4288            v
4289
4290        def f_m():
4291            vvv = mom = w = id = pytho = None
4292            m
4293
4294        def f_py():
4295            vvv = mom = w = id = pytho = None
4296            py
4297
4298        for name, func in (("b", f_b), ("v", f_v), ("m", f_m), ("py", f_py)):
4299            with self.subTest(name=name):
4300                actual = self.get_suggestion(func)
4301                self.assertNotIn("you mean", actual)
4302                self.assertNotIn("vvv", actual)
4303                self.assertNotIn("mom", actual)
4304                self.assertNotIn("'id'", actual)
4305                self.assertNotIn("'w'", actual)
4306                self.assertNotIn("'pytho'", actual)
4307
4308    def test_name_error_suggestions_do_not_trigger_for_too_many_locals(self):
4309        def func():
4310            # Mutating locals() is unreliable, so we need to do it by hand
4311            a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = a10 = \
4312            a11 = a12 = a13 = a14 = a15 = a16 = a17 = a18 = a19 = a20 = \
4313            a21 = a22 = a23 = a24 = a25 = a26 = a27 = a28 = a29 = a30 = \
4314            a31 = a32 = a33 = a34 = a35 = a36 = a37 = a38 = a39 = a40 = \
4315            a41 = a42 = a43 = a44 = a45 = a46 = a47 = a48 = a49 = a50 = \
4316            a51 = a52 = a53 = a54 = a55 = a56 = a57 = a58 = a59 = a60 = \
4317            a61 = a62 = a63 = a64 = a65 = a66 = a67 = a68 = a69 = a70 = \
4318            a71 = a72 = a73 = a74 = a75 = a76 = a77 = a78 = a79 = a80 = \
4319            a81 = a82 = a83 = a84 = a85 = a86 = a87 = a88 = a89 = a90 = \
4320            a91 = a92 = a93 = a94 = a95 = a96 = a97 = a98 = a99 = a100 = \
4321            a101 = a102 = a103 = a104 = a105 = a106 = a107 = a108 = a109 = a110 = \
4322            a111 = a112 = a113 = a114 = a115 = a116 = a117 = a118 = a119 = a120 = \
4323            a121 = a122 = a123 = a124 = a125 = a126 = a127 = a128 = a129 = a130 = \
4324            a131 = a132 = a133 = a134 = a135 = a136 = a137 = a138 = a139 = a140 = \
4325            a141 = a142 = a143 = a144 = a145 = a146 = a147 = a148 = a149 = a150 = \
4326            a151 = a152 = a153 = a154 = a155 = a156 = a157 = a158 = a159 = a160 = \
4327            a161 = a162 = a163 = a164 = a165 = a166 = a167 = a168 = a169 = a170 = \
4328            a171 = a172 = a173 = a174 = a175 = a176 = a177 = a178 = a179 = a180 = \
4329            a181 = a182 = a183 = a184 = a185 = a186 = a187 = a188 = a189 = a190 = \
4330            a191 = a192 = a193 = a194 = a195 = a196 = a197 = a198 = a199 = a200 = \
4331            a201 = a202 = a203 = a204 = a205 = a206 = a207 = a208 = a209 = a210 = \
4332            a211 = a212 = a213 = a214 = a215 = a216 = a217 = a218 = a219 = a220 = \
4333            a221 = a222 = a223 = a224 = a225 = a226 = a227 = a228 = a229 = a230 = \
4334            a231 = a232 = a233 = a234 = a235 = a236 = a237 = a238 = a239 = a240 = \
4335            a241 = a242 = a243 = a244 = a245 = a246 = a247 = a248 = a249 = a250 = \
4336            a251 = a252 = a253 = a254 = a255 = a256 = a257 = a258 = a259 = a260 = \
4337            a261 = a262 = a263 = a264 = a265 = a266 = a267 = a268 = a269 = a270 = \
4338            a271 = a272 = a273 = a274 = a275 = a276 = a277 = a278 = a279 = a280 = \
4339            a281 = a282 = a283 = a284 = a285 = a286 = a287 = a288 = a289 = a290 = \
4340            a291 = a292 = a293 = a294 = a295 = a296 = a297 = a298 = a299 = a300 = \
4341            a301 = a302 = a303 = a304 = a305 = a306 = a307 = a308 = a309 = a310 = \
4342            a311 = a312 = a313 = a314 = a315 = a316 = a317 = a318 = a319 = a320 = \
4343            a321 = a322 = a323 = a324 = a325 = a326 = a327 = a328 = a329 = a330 = \
4344            a331 = a332 = a333 = a334 = a335 = a336 = a337 = a338 = a339 = a340 = \
4345            a341 = a342 = a343 = a344 = a345 = a346 = a347 = a348 = a349 = a350 = \
4346            a351 = a352 = a353 = a354 = a355 = a356 = a357 = a358 = a359 = a360 = \
4347            a361 = a362 = a363 = a364 = a365 = a366 = a367 = a368 = a369 = a370 = \
4348            a371 = a372 = a373 = a374 = a375 = a376 = a377 = a378 = a379 = a380 = \
4349            a381 = a382 = a383 = a384 = a385 = a386 = a387 = a388 = a389 = a390 = \
4350            a391 = a392 = a393 = a394 = a395 = a396 = a397 = a398 = a399 = a400 = \
4351            a401 = a402 = a403 = a404 = a405 = a406 = a407 = a408 = a409 = a410 = \
4352            a411 = a412 = a413 = a414 = a415 = a416 = a417 = a418 = a419 = a420 = \
4353            a421 = a422 = a423 = a424 = a425 = a426 = a427 = a428 = a429 = a430 = \
4354            a431 = a432 = a433 = a434 = a435 = a436 = a437 = a438 = a439 = a440 = \
4355            a441 = a442 = a443 = a444 = a445 = a446 = a447 = a448 = a449 = a450 = \
4356            a451 = a452 = a453 = a454 = a455 = a456 = a457 = a458 = a459 = a460 = \
4357            a461 = a462 = a463 = a464 = a465 = a466 = a467 = a468 = a469 = a470 = \
4358            a471 = a472 = a473 = a474 = a475 = a476 = a477 = a478 = a479 = a480 = \
4359            a481 = a482 = a483 = a484 = a485 = a486 = a487 = a488 = a489 = a490 = \
4360            a491 = a492 = a493 = a494 = a495 = a496 = a497 = a498 = a499 = a500 = \
4361            a501 = a502 = a503 = a504 = a505 = a506 = a507 = a508 = a509 = a510 = \
4362            a511 = a512 = a513 = a514 = a515 = a516 = a517 = a518 = a519 = a520 = \
4363            a521 = a522 = a523 = a524 = a525 = a526 = a527 = a528 = a529 = a530 = \
4364            a531 = a532 = a533 = a534 = a535 = a536 = a537 = a538 = a539 = a540 = \
4365            a541 = a542 = a543 = a544 = a545 = a546 = a547 = a548 = a549 = a550 = \
4366            a551 = a552 = a553 = a554 = a555 = a556 = a557 = a558 = a559 = a560 = \
4367            a561 = a562 = a563 = a564 = a565 = a566 = a567 = a568 = a569 = a570 = \
4368            a571 = a572 = a573 = a574 = a575 = a576 = a577 = a578 = a579 = a580 = \
4369            a581 = a582 = a583 = a584 = a585 = a586 = a587 = a588 = a589 = a590 = \
4370            a591 = a592 = a593 = a594 = a595 = a596 = a597 = a598 = a599 = a600 = \
4371            a601 = a602 = a603 = a604 = a605 = a606 = a607 = a608 = a609 = a610 = \
4372            a611 = a612 = a613 = a614 = a615 = a616 = a617 = a618 = a619 = a620 = \
4373            a621 = a622 = a623 = a624 = a625 = a626 = a627 = a628 = a629 = a630 = \
4374            a631 = a632 = a633 = a634 = a635 = a636 = a637 = a638 = a639 = a640 = \
4375            a641 = a642 = a643 = a644 = a645 = a646 = a647 = a648 = a649 = a650 = \
4376            a651 = a652 = a653 = a654 = a655 = a656 = a657 = a658 = a659 = a660 = \
4377            a661 = a662 = a663 = a664 = a665 = a666 = a667 = a668 = a669 = a670 = \
4378            a671 = a672 = a673 = a674 = a675 = a676 = a677 = a678 = a679 = a680 = \
4379            a681 = a682 = a683 = a684 = a685 = a686 = a687 = a688 = a689 = a690 = \
4380            a691 = a692 = a693 = a694 = a695 = a696 = a697 = a698 = a699 = a700 = \
4381            a701 = a702 = a703 = a704 = a705 = a706 = a707 = a708 = a709 = a710 = \
4382            a711 = a712 = a713 = a714 = a715 = a716 = a717 = a718 = a719 = a720 = \
4383            a721 = a722 = a723 = a724 = a725 = a726 = a727 = a728 = a729 = a730 = \
4384            a731 = a732 = a733 = a734 = a735 = a736 = a737 = a738 = a739 = a740 = \
4385            a741 = a742 = a743 = a744 = a745 = a746 = a747 = a748 = a749 = a750 = \
4386            a751 = a752 = a753 = a754 = a755 = a756 = a757 = a758 = a759 = a760 = \
4387            a761 = a762 = a763 = a764 = a765 = a766 = a767 = a768 = a769 = a770 = \
4388            a771 = a772 = a773 = a774 = a775 = a776 = a777 = a778 = a779 = a780 = \
4389            a781 = a782 = a783 = a784 = a785 = a786 = a787 = a788 = a789 = a790 = \
4390            a791 = a792 = a793 = a794 = a795 = a796 = a797 = a798 = a799 = a800 \
4391                = None
4392            print(a0)
4393
4394        actual = self.get_suggestion(func)
4395        self.assertNotRegex(actual, r"NameError.*a1")
4396
4397    def test_name_error_with_custom_exceptions(self):
4398        def func():
4399            blech = None
4400            raise NameError()
4401
4402        actual = self.get_suggestion(func)
4403        self.assertNotIn("blech", actual)
4404
4405        def func():
4406            blech = None
4407            raise NameError
4408
4409        actual = self.get_suggestion(func)
4410        self.assertNotIn("blech", actual)
4411
4412    def test_name_error_with_instance(self):
4413        class A:
4414            def __init__(self):
4415                self.blech = None
4416            def foo(self):
4417                blich = 1
4418                x = blech
4419
4420        instance = A()
4421        actual = self.get_suggestion(instance.foo)
4422        self.assertIn("self.blech", actual)
4423
4424    def test_unbound_local_error_with_instance(self):
4425        class A:
4426            def __init__(self):
4427                self.blech = None
4428            def foo(self):
4429                blich = 1
4430                x = blech
4431                blech = 1
4432
4433        instance = A()
4434        actual = self.get_suggestion(instance.foo)
4435        self.assertNotIn("self.blech", actual)
4436
4437    def test_unbound_local_error_does_not_match(self):
4438        def func():
4439            something = 3
4440            print(somethong)
4441            somethong = 3
4442
4443        actual = self.get_suggestion(func)
4444        self.assertNotIn("something", actual)
4445
4446    def test_name_error_for_stdlib_modules(self):
4447        def func():
4448            stream = io.StringIO()
4449
4450        actual = self.get_suggestion(func)
4451        self.assertIn("forget to import 'io'", actual)
4452
4453    def test_name_error_for_private_stdlib_modules(self):
4454        def func():
4455            stream = _io.StringIO()
4456
4457        actual = self.get_suggestion(func)
4458        self.assertIn("forget to import '_io'", actual)
4459
4460
4461
4462class PurePythonSuggestionFormattingTests(
4463    PurePythonExceptionFormattingMixin,
4464    SuggestionFormattingTestBase,
4465    unittest.TestCase,
4466):
4467    """
4468    Same set of tests as above using the pure Python implementation of
4469    traceback printing in traceback.py.
4470    """
4471
4472
4473@cpython_only
4474class CPythonSuggestionFormattingTests(
4475    CAPIExceptionFormattingMixin,
4476    SuggestionFormattingTestBase,
4477    unittest.TestCase,
4478):
4479    """
4480    Same set of tests as above but with Python's internal traceback printing.
4481    """
4482
4483
4484class MiscTest(unittest.TestCase):
4485
4486    def test_all(self):
4487        expected = set()
4488        denylist = {'print_list'}
4489        for name in dir(traceback):
4490            if name.startswith('_') or name in denylist:
4491                continue
4492            module_object = getattr(traceback, name)
4493            if getattr(module_object, '__module__', None) == 'traceback':
4494                expected.add(name)
4495        self.assertCountEqual(traceback.__all__, expected)
4496
4497    def test_levenshtein_distance(self):
4498        # copied from _testinternalcapi.test_edit_cost
4499        # to also exercise the Python implementation
4500
4501        def CHECK(a, b, expected):
4502            actual = traceback._levenshtein_distance(a, b, 4044)
4503            self.assertEqual(actual, expected)
4504
4505        CHECK("", "", 0)
4506        CHECK("", "a", 2)
4507        CHECK("a", "A", 1)
4508        CHECK("Apple", "Aple", 2)
4509        CHECK("Banana", "B@n@n@", 6)
4510        CHECK("Cherry", "Cherry!", 2)
4511        CHECK("---0---", "------", 2)
4512        CHECK("abc", "y", 6)
4513        CHECK("aa", "bb", 4)
4514        CHECK("aaaaa", "AAAAA", 5)
4515        CHECK("wxyz", "wXyZ", 2)
4516        CHECK("wxyz", "wXyZ123", 8)
4517        CHECK("Python", "Java", 12)
4518        CHECK("Java", "C#", 8)
4519        CHECK("AbstractFoobarManager", "abstract_foobar_manager", 3+2*2)
4520        CHECK("CPython", "PyPy", 10)
4521        CHECK("CPython", "pypy", 11)
4522        CHECK("AttributeError", "AttributeErrop", 2)
4523        CHECK("AttributeError", "AttributeErrorTests", 10)
4524        CHECK("ABA", "AAB", 4)
4525
4526    @support.requires_resource('cpu')
4527    def test_levenshtein_distance_short_circuit(self):
4528        if not LEVENSHTEIN_DATA_FILE.is_file():
4529            self.fail(
4530                f"{LEVENSHTEIN_DATA_FILE} is missing."
4531                f" Run `make regen-test-levenshtein`"
4532            )
4533
4534        with LEVENSHTEIN_DATA_FILE.open("r") as f:
4535            examples = json.load(f)
4536        for a, b, expected in examples:
4537            res1 = traceback._levenshtein_distance(a, b, 1000)
4538            self.assertEqual(res1, expected, msg=(a, b))
4539
4540            for threshold in [expected, expected + 1, expected + 2]:
4541                # big enough thresholds shouldn't change the result
4542                res2 = traceback._levenshtein_distance(a, b, threshold)
4543                self.assertEqual(res2, expected, msg=(a, b, threshold))
4544
4545            for threshold in range(expected):
4546                # for small thresholds, the only piece of information
4547                # we receive is "strings not close enough".
4548                res3 = traceback._levenshtein_distance(a, b, threshold)
4549                self.assertGreater(res3, threshold, msg=(a, b, threshold))
4550
4551class TestColorizedTraceback(unittest.TestCase):
4552    def test_colorized_traceback(self):
4553        def foo(*args):
4554            x = {'a':{'b': None}}
4555            y = x['a']['b']['c']
4556
4557        def baz2(*args):
4558            return (lambda *args: foo(*args))(1,2,3,4)
4559
4560        def baz1(*args):
4561            return baz2(1,2,3,4)
4562
4563        def bar():
4564            return baz1(1,
4565                    2,3
4566                    ,4)
4567        try:
4568            bar()
4569        except Exception as e:
4570            exc = traceback.TracebackException.from_exception(
4571                e, capture_locals=True
4572            )
4573        lines = "".join(exc.format(colorize=True))
4574        red = _colorize.ANSIColors.RED
4575        boldr = _colorize.ANSIColors.BOLD_RED
4576        reset = _colorize.ANSIColors.RESET
4577        self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines)
4578        self.assertIn("return " + red + "(lambda *args: foo(*args))" + reset + boldr + "(1,2,3,4)" + reset, lines)
4579        self.assertIn("return (lambda *args: " + red + "foo" + reset + boldr + "(*args)" + reset + ")(1,2,3,4)", lines)
4580        self.assertIn("return baz2(1,2,3,4)", lines)
4581        self.assertIn("return baz1(1,\n            2,3\n            ,4)", lines)
4582        self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines)
4583
4584    def test_colorized_syntax_error(self):
4585        try:
4586            compile("a $ b", "<string>", "exec")
4587        except SyntaxError as e:
4588            exc = traceback.TracebackException.from_exception(
4589                e, capture_locals=True
4590            )
4591        actual = "".join(exc.format(colorize=True))
4592        red = _colorize.ANSIColors.RED
4593        magenta = _colorize.ANSIColors.MAGENTA
4594        boldm = _colorize.ANSIColors.BOLD_MAGENTA
4595        boldr = _colorize.ANSIColors.BOLD_RED
4596        reset = _colorize.ANSIColors.RESET
4597        expected = "".join([
4598        f'  File {magenta}"<string>"{reset}, line {magenta}1{reset}\n',
4599        f'    a {boldr}${reset} b\n',
4600        f'      {boldr}^{reset}\n',
4601        f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n']
4602        )
4603        self.assertIn(expected, actual)
4604
4605    def test_colorized_traceback_is_the_default(self):
4606        def foo():
4607            1/0
4608
4609        from _testcapi import exception_print
4610        try:
4611            foo()
4612            self.fail("No exception thrown.")
4613        except Exception as e:
4614            with captured_output("stderr") as tbstderr:
4615                with unittest.mock.patch('_colorize.can_colorize', return_value=True):
4616                    exception_print(e)
4617            actual = tbstderr.getvalue().splitlines()
4618
4619        red = _colorize.ANSIColors.RED
4620        boldr = _colorize.ANSIColors.BOLD_RED
4621        magenta = _colorize.ANSIColors.MAGENTA
4622        boldm = _colorize.ANSIColors.BOLD_MAGENTA
4623        reset = _colorize.ANSIColors.RESET
4624        lno_foo = foo.__code__.co_firstlineno
4625        expected = ['Traceback (most recent call last):',
4626            f'  File {magenta}"{__file__}"{reset}, '
4627            f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}',
4628            f'    {red}foo{reset+boldr}(){reset}',
4629            f'    {red}~~~{reset+boldr}^^{reset}',
4630            f'  File {magenta}"{__file__}"{reset}, '
4631            f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}',
4632            f'    {red}1{reset+boldr}/{reset+red}0{reset}',
4633            f'    {red}~{reset+boldr}^{reset+red}~{reset}',
4634            f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}']
4635        self.assertEqual(actual, expected)
4636
4637    def test_colorized_traceback_from_exception_group(self):
4638        def foo():
4639            exceptions = []
4640            try:
4641                1 / 0
4642            except ZeroDivisionError as inner_exc:
4643                exceptions.append(inner_exc)
4644            raise ExceptionGroup("test", exceptions)
4645
4646        try:
4647            foo()
4648        except Exception as e:
4649            exc = traceback.TracebackException.from_exception(
4650                e, capture_locals=True
4651            )
4652
4653        red = _colorize.ANSIColors.RED
4654        boldr = _colorize.ANSIColors.BOLD_RED
4655        magenta = _colorize.ANSIColors.MAGENTA
4656        boldm = _colorize.ANSIColors.BOLD_MAGENTA
4657        reset = _colorize.ANSIColors.RESET
4658        lno_foo = foo.__code__.co_firstlineno
4659        actual = "".join(exc.format(colorize=True)).splitlines()
4660        expected = [f"  + Exception Group Traceback (most recent call last):",
4661                   f'  |   File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}',
4662                   f'  |     {red}foo{reset}{boldr}(){reset}',
4663                   f'  |     {red}~~~{reset}{boldr}^^{reset}',
4664                   f"  |     e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
4665                   f"  |     foo = {foo}",
4666                   f'  |     self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
4667                   f'  |   File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}',
4668                   f'  |     raise ExceptionGroup("test", exceptions)',
4669                   f"  |     exceptions = [ZeroDivisionError('division by zero')]",
4670                   f'  | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset}',
4671                   f'  +-+---------------- 1 ----------------',
4672                   f'    | Traceback (most recent call last):',
4673                   f'    |   File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset}',
4674                   f'    |     {red}1 {reset}{boldr}/{reset}{red} 0{reset}',
4675                   f'    |     {red}~~{reset}{boldr}^{reset}{red}~~{reset}',
4676                   f"    |     exceptions = [ZeroDivisionError('division by zero')]",
4677                   f'    | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}',
4678                   f'    +------------------------------------']
4679        self.assertEqual(actual, expected)
4680
4681if __name__ == "__main__":
4682    unittest.main()
4683