1"""Test cases for traceback module"""
2
3from collections import namedtuple
4from io import StringIO
5import linecache
6import sys
7import types
8import inspect
9import unittest
10import re
11from test import support
12from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
13                          requires_debug_ranges, has_no_debug_ranges,
14                          requires_subprocess)
15from test.support.os_helper import TESTFN, unlink
16from test.support.script_helper import assert_python_ok, assert_python_failure
17
18import os
19import textwrap
20import traceback
21from functools import partial
22
23MODULE_PREFIX = f'{__name__}.' if __name__ == '__main__' else ''
24
25test_code = namedtuple('code', ['co_filename', 'co_name'])
26test_code.co_positions = lambda _: iter([(6, 6, 0, 0)])
27test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
28test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti'])
29
30
31class TracebackCases(unittest.TestCase):
32    # For now, a very minimal set of tests.  I want to be sure that
33    # formatting of SyntaxErrors works based on changes for 2.1.
34
35    def get_exception_format(self, func, exc):
36        try:
37            func()
38        except exc as value:
39            return traceback.format_exception_only(exc, value)
40        else:
41            raise ValueError("call did not raise exception")
42
43    def syntax_error_with_caret(self):
44        compile("def fact(x):\n\treturn x!\n", "?", "exec")
45
46    def syntax_error_with_caret_2(self):
47        compile("1 +\n", "?", "exec")
48
49    def syntax_error_with_caret_range(self):
50        compile("f(x, y for y in range(30), z)", "?", "exec")
51
52    def syntax_error_bad_indentation(self):
53        compile("def spam():\n  print(1)\n print(2)", "?", "exec")
54
55    def syntax_error_with_caret_non_ascii(self):
56        compile('Python = "\u1e54\xfd\u0163\u0125\xf2\xf1" +', "?", "exec")
57
58    def syntax_error_bad_indentation2(self):
59        compile(" print(2)", "?", "exec")
60
61    def tokenizer_error_with_caret_range(self):
62        compile("blech  (  ", "?", "exec")
63
64    def test_caret(self):
65        err = self.get_exception_format(self.syntax_error_with_caret,
66                                        SyntaxError)
67        self.assertEqual(len(err), 4)
68        self.assertTrue(err[1].strip() == "return x!")
69        self.assertIn("^", err[2]) # third line has caret
70        self.assertEqual(err[1].find("!"), err[2].find("^")) # in the right place
71        self.assertEqual(err[2].count("^"), 1)
72
73        err = self.get_exception_format(self.syntax_error_with_caret_2,
74                                        SyntaxError)
75        self.assertIn("^", err[2]) # third line has caret
76        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
77        self.assertEqual(err[1].find("+") + 1, err[2].find("^"))  # in the right place
78        self.assertEqual(err[2].count("^"), 1)
79
80        err = self.get_exception_format(self.syntax_error_with_caret_non_ascii,
81                                        SyntaxError)
82        self.assertIn("^", err[2]) # third line has caret
83        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
84        self.assertEqual(err[1].find("+") + 1, err[2].find("^"))  # in the right place
85        self.assertEqual(err[2].count("^"), 1)
86
87        err = self.get_exception_format(self.syntax_error_with_caret_range,
88                                        SyntaxError)
89        self.assertIn("^", err[2]) # third line has caret
90        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
91        self.assertEqual(err[1].find("y"), err[2].find("^"))  # in the right place
92        self.assertEqual(err[2].count("^"), len("y for y in range(30)"))
93
94        err = self.get_exception_format(self.tokenizer_error_with_caret_range,
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("("), err[2].find("^"))  # in the right place
99        self.assertEqual(err[2].count("^"), 1)
100
101    def test_nocaret(self):
102        exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
103        err = traceback.format_exception_only(SyntaxError, exc)
104        self.assertEqual(len(err), 3)
105        self.assertEqual(err[1].strip(), "bad syntax")
106
107    def test_no_caret_with_no_debug_ranges_flag(self):
108        # Make sure that if `-X no_debug_ranges` is used, there are no carets
109        # in the traceback.
110        try:
111            with open(TESTFN, 'w') as f:
112                f.write("x = 1 / 0\n")
113
114            _, _, stderr = assert_python_failure(
115                '-X', 'no_debug_ranges', TESTFN)
116
117            lines = stderr.splitlines()
118            self.assertEqual(len(lines), 4)
119            self.assertEqual(lines[0], b'Traceback (most recent call last):')
120            self.assertIn(b'line 1, in <module>', lines[1])
121            self.assertEqual(lines[2], b'    x = 1 / 0')
122            self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
123        finally:
124            unlink(TESTFN)
125
126    def test_no_caret_with_no_debug_ranges_flag_python_traceback(self):
127        code = textwrap.dedent("""
128            import traceback
129            try:
130                x = 1 / 0
131            except:
132                traceback.print_exc()
133            """)
134        try:
135            with open(TESTFN, 'w') as f:
136                f.write(code)
137
138            _, _, stderr = assert_python_ok(
139                '-X', 'no_debug_ranges', TESTFN)
140
141            lines = stderr.splitlines()
142            self.assertEqual(len(lines), 4)
143            self.assertEqual(lines[0], b'Traceback (most recent call last):')
144            self.assertIn(b'line 4, in <module>', lines[1])
145            self.assertEqual(lines[2], b'    x = 1 / 0')
146            self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
147        finally:
148            unlink(TESTFN)
149
150    def test_recursion_error_during_traceback(self):
151        code = textwrap.dedent("""
152                import sys
153                from weakref import ref
154
155                sys.setrecursionlimit(15)
156
157                def f():
158                    ref(lambda: 0, [])
159                    f()
160
161                try:
162                    f()
163                except RecursionError:
164                    pass
165        """)
166        try:
167            with open(TESTFN, 'w') as f:
168                f.write(code)
169
170            rc, _, _ = assert_python_ok(TESTFN)
171            self.assertEqual(rc, 0)
172        finally:
173            unlink(TESTFN)
174
175    def test_bad_indentation(self):
176        err = self.get_exception_format(self.syntax_error_bad_indentation,
177                                        IndentationError)
178        self.assertEqual(len(err), 4)
179        self.assertEqual(err[1].strip(), "print(2)")
180        self.assertIn("^", err[2])
181        self.assertEqual(err[1].find(")") + 1, err[2].find("^"))
182
183        # No caret for "unexpected indent"
184        err = self.get_exception_format(self.syntax_error_bad_indentation2,
185                                        IndentationError)
186        self.assertEqual(len(err), 3)
187        self.assertEqual(err[1].strip(), "print(2)")
188
189    def test_base_exception(self):
190        # Test that exceptions derived from BaseException are formatted right
191        e = KeyboardInterrupt()
192        lst = traceback.format_exception_only(e.__class__, e)
193        self.assertEqual(lst, ['KeyboardInterrupt\n'])
194
195    def test_format_exception_only_bad__str__(self):
196        class X(Exception):
197            def __str__(self):
198                1/0
199        err = traceback.format_exception_only(X, X())
200        self.assertEqual(len(err), 1)
201        str_value = '<exception str() failed>'
202        if X.__module__ in ('__main__', 'builtins'):
203            str_name = X.__qualname__
204        else:
205            str_name = '.'.join([X.__module__, X.__qualname__])
206        self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
207
208    @requires_subprocess()
209    def test_encoded_file(self):
210        # Test that tracebacks are correctly printed for encoded source files:
211        # - correct line number (Issue2384)
212        # - respect file encoding (Issue3975)
213        import sys, subprocess
214
215        # The spawned subprocess has its stdout redirected to a PIPE, and its
216        # encoding may be different from the current interpreter, on Windows
217        # at least.
218        process = subprocess.Popen([sys.executable, "-c",
219                                    "import sys; print(sys.stdout.encoding)"],
220                                   stdout=subprocess.PIPE,
221                                   stderr=subprocess.STDOUT)
222        stdout, stderr = process.communicate()
223        output_encoding = str(stdout, 'ascii').splitlines()[0]
224
225        def do_test(firstlines, message, charset, lineno):
226            # Raise the message in a subprocess, and catch the output
227            try:
228                with open(TESTFN, "w", encoding=charset) as output:
229                    output.write("""{0}if 1:
230                        import traceback;
231                        raise RuntimeError('{1}')
232                        """.format(firstlines, message))
233
234                process = subprocess.Popen([sys.executable, TESTFN],
235                    stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
236                stdout, stderr = process.communicate()
237                stdout = stdout.decode(output_encoding).splitlines()
238            finally:
239                unlink(TESTFN)
240
241            # The source lines are encoded with the 'backslashreplace' handler
242            encoded_message = message.encode(output_encoding,
243                                             'backslashreplace')
244            # and we just decoded them with the output_encoding.
245            message_ascii = encoded_message.decode(output_encoding)
246
247            err_line = "raise RuntimeError('{0}')".format(message_ascii)
248            err_msg = "RuntimeError: {0}".format(message_ascii)
249
250            self.assertIn(("line %s" % lineno), stdout[1],
251                "Invalid line number: {0!r} instead of {1}".format(
252                    stdout[1], lineno))
253            self.assertTrue(stdout[2].endswith(err_line),
254                "Invalid traceback line: {0!r} instead of {1!r}".format(
255                    stdout[2], err_line))
256            actual_err_msg = stdout[3]
257            self.assertTrue(actual_err_msg == err_msg,
258                "Invalid error message: {0!r} instead of {1!r}".format(
259                    actual_err_msg, err_msg))
260
261        do_test("", "foo", "ascii", 3)
262        for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
263            if charset == "ascii":
264                text = "foo"
265            elif charset == "GBK":
266                text = "\u4E02\u5100"
267            else:
268                text = "h\xe9 ho"
269            do_test("# coding: {0}\n".format(charset),
270                    text, charset, 4)
271            do_test("#!shebang\n# coding: {0}\n".format(charset),
272                    text, charset, 5)
273            do_test(" \t\f\n# coding: {0}\n".format(charset),
274                    text, charset, 5)
275        # Issue #18960: coding spec should have no effect
276        do_test("x=0\n# coding: GBK\n", "h\xe9 ho", 'utf-8', 5)
277
278    def test_print_traceback_at_exit(self):
279        # Issue #22599: Ensure that it is possible to use the traceback module
280        # to display an exception at Python exit
281        code = textwrap.dedent("""
282            import sys
283            import traceback
284
285            class PrintExceptionAtExit(object):
286                def __init__(self):
287                    try:
288                        x = 1 / 0
289                    except Exception:
290                        self.exc_info = sys.exc_info()
291                        # self.exc_info[1] (traceback) contains frames:
292                        # explicitly clear the reference to self in the current
293                        # frame to break a reference cycle
294                        self = None
295
296                def __del__(self):
297                    traceback.print_exception(*self.exc_info)
298
299            # Keep a reference in the module namespace to call the destructor
300            # when the module is unloaded
301            obj = PrintExceptionAtExit()
302        """)
303        rc, stdout, stderr = assert_python_ok('-c', code)
304        expected = [b'Traceback (most recent call last):',
305                    b'  File "<string>", line 8, in __init__',
306                    b'ZeroDivisionError: division by zero']
307        self.assertEqual(stderr.splitlines(), expected)
308
309    def test_print_exception(self):
310        output = StringIO()
311        traceback.print_exception(
312            Exception, Exception("projector"), None, file=output
313        )
314        self.assertEqual(output.getvalue(), "Exception: projector\n")
315
316    def test_print_exception_exc(self):
317        output = StringIO()
318        traceback.print_exception(Exception("projector"), file=output)
319        self.assertEqual(output.getvalue(), "Exception: projector\n")
320
321    def test_format_exception_exc(self):
322        e = Exception("projector")
323        output = traceback.format_exception(e)
324        self.assertEqual(output, ["Exception: projector\n"])
325        with self.assertRaisesRegex(ValueError, 'Both or neither'):
326            traceback.format_exception(e.__class__, e)
327        with self.assertRaisesRegex(ValueError, 'Both or neither'):
328            traceback.format_exception(e.__class__, tb=e.__traceback__)
329        with self.assertRaisesRegex(TypeError, 'positional-only'):
330            traceback.format_exception(exc=e)
331
332    def test_format_exception_only_exc(self):
333        output = traceback.format_exception_only(Exception("projector"))
334        self.assertEqual(output, ["Exception: projector\n"])
335
336    def test_exception_is_None(self):
337        NONE_EXC_STRING = 'NoneType: None\n'
338        excfile = StringIO()
339        traceback.print_exception(None, file=excfile)
340        self.assertEqual(excfile.getvalue(), NONE_EXC_STRING)
341
342        excfile = StringIO()
343        traceback.print_exception(None, None, None, file=excfile)
344        self.assertEqual(excfile.getvalue(), NONE_EXC_STRING)
345
346        excfile = StringIO()
347        traceback.print_exc(None, file=excfile)
348        self.assertEqual(excfile.getvalue(), NONE_EXC_STRING)
349
350        self.assertEqual(traceback.format_exc(None), NONE_EXC_STRING)
351        self.assertEqual(traceback.format_exception(None), [NONE_EXC_STRING])
352        self.assertEqual(
353            traceback.format_exception(None, None, None), [NONE_EXC_STRING])
354        self.assertEqual(
355            traceback.format_exception_only(None), [NONE_EXC_STRING])
356        self.assertEqual(
357            traceback.format_exception_only(None, None), [NONE_EXC_STRING])
358
359    def test_signatures(self):
360        self.assertEqual(
361            str(inspect.signature(traceback.print_exception)),
362            ('(exc, /, value=<implicit>, tb=<implicit>, '
363             'limit=None, file=None, chain=True)'))
364
365        self.assertEqual(
366            str(inspect.signature(traceback.format_exception)),
367            ('(exc, /, value=<implicit>, tb=<implicit>, limit=None, '
368             'chain=True)'))
369
370        self.assertEqual(
371            str(inspect.signature(traceback.format_exception_only)),
372            '(exc, /, value=<implicit>)')
373
374
375@requires_debug_ranges()
376class TracebackErrorLocationCaretTests(unittest.TestCase):
377    """
378    Tests for printing code error expressions as part of PEP 657
379    """
380    def get_exception(self, callable):
381        try:
382            callable()
383            self.fail("No exception thrown.")
384        except:
385            return traceback.format_exc().splitlines()[:-1]
386
387    callable_line = get_exception.__code__.co_firstlineno + 2
388
389    def test_basic_caret(self):
390        # NOTE: In caret tests, "if True:" is used as a way to force indicator
391        #   display, since the raising expression spans only part of the line.
392        def f():
393            if True: raise ValueError("basic caret tests")
394
395        lineno_f = f.__code__.co_firstlineno
396        expected_f = (
397            'Traceback (most recent call last):\n'
398            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
399            '    callable()\n'
400            f'  File "{__file__}", line {lineno_f+1}, in f\n'
401            '    if True: raise ValueError("basic caret tests")\n'
402            '             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
403        )
404        result_lines = self.get_exception(f)
405        self.assertEqual(result_lines, expected_f.splitlines())
406
407    def test_line_with_unicode(self):
408        # Make sure that even if a line contains multi-byte unicode characters
409        # the correct carets are printed.
410        def f_with_unicode():
411            if True: raise ValueError("Ĥellö Wörld")
412
413        lineno_f = f_with_unicode.__code__.co_firstlineno
414        expected_f = (
415            'Traceback (most recent call last):\n'
416            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
417            '    callable()\n'
418            f'  File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
419            '    if True: raise ValueError("Ĥellö Wörld")\n'
420            '             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
421        )
422        result_lines = self.get_exception(f_with_unicode)
423        self.assertEqual(result_lines, expected_f.splitlines())
424
425    def test_caret_in_type_annotation(self):
426        def f_with_type():
427            def foo(a: THIS_DOES_NOT_EXIST ) -> int:
428                return 0
429
430        lineno_f = f_with_type.__code__.co_firstlineno
431        expected_f = (
432            'Traceback (most recent call last):\n'
433            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
434            '    callable()\n'
435            f'  File "{__file__}", line {lineno_f+1}, in f_with_type\n'
436            '    def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
437            '               ^^^^^^^^^^^^^^^^^^^\n'
438        )
439        result_lines = self.get_exception(f_with_type)
440        self.assertEqual(result_lines, expected_f.splitlines())
441
442    def test_caret_multiline_expression(self):
443        # Make sure no carets are printed for expressions spanning multiple
444        # lines.
445        def f_with_multiline():
446            if True: raise ValueError(
447                "error over multiple lines"
448            )
449
450        lineno_f = f_with_multiline.__code__.co_firstlineno
451        expected_f = (
452            'Traceback (most recent call last):\n'
453            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
454            '    callable()\n'
455            f'  File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
456            '    if True: raise ValueError(\n'
457            '             ^^^^^^^^^^^^^^^^^'
458        )
459        result_lines = self.get_exception(f_with_multiline)
460        self.assertEqual(result_lines, expected_f.splitlines())
461
462    def test_caret_multiline_expression_syntax_error(self):
463        # Make sure an expression spanning multiple lines that has
464        # a syntax error is correctly marked with carets.
465        code = textwrap.dedent("""
466        def foo(*args, **kwargs):
467            pass
468
469        a, b, c = 1, 2, 3
470
471        foo(a, z
472                for z in
473                    range(10), b, c)
474        """)
475
476        def f_with_multiline():
477            # Need to defer the compilation until in self.get_exception(..)
478            return compile(code, "?", "exec")
479
480        lineno_f = f_with_multiline.__code__.co_firstlineno
481
482        expected_f = (
483            'Traceback (most recent call last):\n'
484            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
485            '    callable()\n'
486            f'  File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
487            '    return compile(code, "?", "exec")\n'
488            '           ^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
489            '  File "?", line 7\n'
490            '    foo(a, z\n'
491            '           ^'
492            )
493
494        result_lines = self.get_exception(f_with_multiline)
495        self.assertEqual(result_lines, expected_f.splitlines())
496
497    def test_caret_multiline_expression_bin_op(self):
498        # Make sure no carets are printed for expressions spanning multiple
499        # lines.
500        def f_with_multiline():
501            return (
502                2 + 1 /
503                0
504            )
505
506        lineno_f = f_with_multiline.__code__.co_firstlineno
507        expected_f = (
508            'Traceback (most recent call last):\n'
509            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
510            '    callable()\n'
511            f'  File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
512            '    2 + 1 /\n'
513            '        ^^^'
514        )
515        result_lines = self.get_exception(f_with_multiline)
516        self.assertEqual(result_lines, expected_f.splitlines())
517
518    def test_caret_for_binary_operators(self):
519        def f_with_binary_operator():
520            divisor = 20
521            return 10 + divisor / 0 + 30
522
523        lineno_f = f_with_binary_operator.__code__.co_firstlineno
524        expected_error = (
525            'Traceback (most recent call last):\n'
526            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
527            '    callable()\n'
528            f'  File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
529            '    return 10 + divisor / 0 + 30\n'
530            '                ~~~~~~~~^~~\n'
531        )
532        result_lines = self.get_exception(f_with_binary_operator)
533        self.assertEqual(result_lines, expected_error.splitlines())
534
535    def test_caret_for_binary_operators_with_unicode(self):
536        def f_with_binary_operator():
537            áóí = 20
538            return 10 + áóí / 0 + 30
539
540        lineno_f = f_with_binary_operator.__code__.co_firstlineno
541        expected_error = (
542            'Traceback (most recent call last):\n'
543            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
544            '    callable()\n'
545            f'  File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
546            '    return 10 + áóí / 0 + 30\n'
547            '                ~~~~^~~\n'
548        )
549        result_lines = self.get_exception(f_with_binary_operator)
550        self.assertEqual(result_lines, expected_error.splitlines())
551
552    def test_caret_for_binary_operators_two_char(self):
553        def f_with_binary_operator():
554            divisor = 20
555            return 10 + divisor // 0 + 30
556
557        lineno_f = f_with_binary_operator.__code__.co_firstlineno
558        expected_error = (
559            'Traceback (most recent call last):\n'
560            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
561            '    callable()\n'
562            f'  File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
563            '    return 10 + divisor // 0 + 30\n'
564            '                ~~~~~~~~^^~~\n'
565        )
566        result_lines = self.get_exception(f_with_binary_operator)
567        self.assertEqual(result_lines, expected_error.splitlines())
568
569    def test_caret_for_subscript(self):
570        def f_with_subscript():
571            some_dict = {'x': {'y': None}}
572            return some_dict['x']['y']['z']
573
574        lineno_f = f_with_subscript.__code__.co_firstlineno
575        expected_error = (
576            'Traceback (most recent call last):\n'
577            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
578            '    callable()\n'
579            f'  File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
580            "    return some_dict['x']['y']['z']\n"
581            '           ~~~~~~~~~~~~~~~~~~~^^^^^\n'
582        )
583        result_lines = self.get_exception(f_with_subscript)
584        self.assertEqual(result_lines, expected_error.splitlines())
585
586    def test_caret_for_subscript_unicode(self):
587        def f_with_subscript():
588            some_dict = {'ó': {'á': {'í': {'theta': 1}}}}
589            return some_dict['ó']['á']['í']['beta']
590
591        lineno_f = f_with_subscript.__code__.co_firstlineno
592        expected_error = (
593            'Traceback (most recent call last):\n'
594            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
595            '    callable()\n'
596            f'  File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
597            "    return some_dict['ó']['á']['í']['beta']\n"
598            '           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n'
599        )
600        result_lines = self.get_exception(f_with_subscript)
601        self.assertEqual(result_lines, expected_error.splitlines())
602
603    def test_traceback_specialization_with_syntax_error(self):
604        bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")
605
606        with open(TESTFN, "w") as file:
607            # make the file's contents invalid
608            file.write("1 $ 0 / 1 / 2\n")
609        self.addCleanup(unlink, TESTFN)
610
611        func = partial(exec, bytecode)
612        result_lines = self.get_exception(func)
613
614        lineno_f = bytecode.co_firstlineno
615        expected_error = (
616            'Traceback (most recent call last):\n'
617            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
618            '    callable()\n'
619            f'  File "{TESTFN}", line {lineno_f}, in <module>\n'
620            "    1 $ 0 / 1 / 2\n"
621            '    ^^^^^\n'
622        )
623        self.assertEqual(result_lines, expected_error.splitlines())
624
625    def test_traceback_very_long_line(self):
626        source = "if True: " + "a" * 256
627        bytecode = compile(source, TESTFN, "exec")
628
629        with open(TESTFN, "w") as file:
630            file.write(source)
631        self.addCleanup(unlink, TESTFN)
632
633        func = partial(exec, bytecode)
634        result_lines = self.get_exception(func)
635
636        lineno_f = bytecode.co_firstlineno
637        expected_error = (
638            'Traceback (most recent call last):\n'
639            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
640            '    callable()\n'
641            f'  File "{TESTFN}", line {lineno_f}, in <module>\n'
642            f'    {source}\n'
643            f'    {" "*len("if True: ") + "^"*256}\n'
644        )
645        self.assertEqual(result_lines, expected_error.splitlines())
646
647    def test_secondary_caret_not_elided(self):
648        # Always show a line's indicators if they include the secondary character.
649        def f_with_subscript():
650            some_dict = {'x': {'y': None}}
651            some_dict['x']['y']['z']
652
653        lineno_f = f_with_subscript.__code__.co_firstlineno
654        expected_error = (
655            'Traceback (most recent call last):\n'
656            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
657            '    callable()\n'
658            f'  File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
659            "    some_dict['x']['y']['z']\n"
660            '    ~~~~~~~~~~~~~~~~~~~^^^^^\n'
661        )
662        result_lines = self.get_exception(f_with_subscript)
663        self.assertEqual(result_lines, expected_error.splitlines())
664
665    def test_caret_exception_group(self):
666        # Notably, this covers whether indicators handle margin strings correctly.
667        # (Exception groups use margin strings to display vertical indicators.)
668        # The implementation must account for both "indent" and "margin" offsets.
669
670        def exc():
671            if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
672
673        expected_error = (
674             f'  + Exception Group Traceback (most recent call last):\n'
675             f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
676             f'  |     callable()\n'
677             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
678             f'  |     if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
679             f'  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
680             f'  | ExceptionGroup: eg (2 sub-exceptions)\n'
681             f'  +-+---------------- 1 ----------------\n'
682             f'    | ValueError: 1\n'
683             f'    +---------------- 2 ----------------\n'
684             f'    | TypeError: 2\n')
685
686        result_lines = self.get_exception(exc)
687        self.assertEqual(result_lines, expected_error.splitlines())
688
689    def assertSpecialized(self, func, expected_specialization):
690        result_lines = self.get_exception(func)
691        specialization_line = result_lines[-1]
692        self.assertEqual(specialization_line.lstrip(), expected_specialization)
693
694    def test_specialization_variations(self):
695        self.assertSpecialized(lambda: 1/0,
696                                      "~^~")
697        self.assertSpecialized(lambda: 1/0/3,
698                                      "~^~")
699        self.assertSpecialized(lambda: 1 / 0,
700                                      "~~^~~")
701        self.assertSpecialized(lambda: 1 / 0 / 3,
702                                      "~~^~~")
703        self.assertSpecialized(lambda: 1/ 0,
704                                      "~^~~")
705        self.assertSpecialized(lambda: 1/ 0/3,
706                                      "~^~~")
707        self.assertSpecialized(lambda: 1    /  0,
708                                      "~~~~~^~~~")
709        self.assertSpecialized(lambda: 1    /  0   / 5,
710                                      "~~~~~^~~~")
711        self.assertSpecialized(lambda: 1 /0,
712                                      "~~^~")
713        self.assertSpecialized(lambda: 1//0,
714                                      "~^^~")
715        self.assertSpecialized(lambda: 1//0//4,
716                                      "~^^~")
717        self.assertSpecialized(lambda: 1 // 0,
718                                      "~~^^~~")
719        self.assertSpecialized(lambda: 1 // 0 // 4,
720                                      "~~^^~~")
721        self.assertSpecialized(lambda: 1 //0,
722                                      "~~^^~")
723        self.assertSpecialized(lambda: 1// 0,
724                                      "~^^~~")
725
726    def test_decorator_application_lineno_correct(self):
727        def dec_error(func):
728            raise TypeError
729        def dec_fine(func):
730            return func
731        def applydecs():
732            @dec_error
733            @dec_fine
734            def g(): pass
735        result_lines = self.get_exception(applydecs)
736        lineno_applydescs = applydecs.__code__.co_firstlineno
737        lineno_dec_error = dec_error.__code__.co_firstlineno
738        expected_error = (
739            'Traceback (most recent call last):\n'
740            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
741            '    callable()\n'
742            f'  File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
743            '    @dec_error\n'
744            '     ^^^^^^^^^\n'
745            f'  File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
746            '    raise TypeError\n'
747        )
748        self.assertEqual(result_lines, expected_error.splitlines())
749
750        def applydecs_class():
751            @dec_error
752            @dec_fine
753            class A: pass
754        result_lines = self.get_exception(applydecs_class)
755        lineno_applydescs_class = applydecs_class.__code__.co_firstlineno
756        expected_error = (
757            'Traceback (most recent call last):\n'
758            f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
759            '    callable()\n'
760            f'  File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
761            '    @dec_error\n'
762            '     ^^^^^^^^^\n'
763            f'  File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
764            '    raise TypeError\n'
765        )
766        self.assertEqual(result_lines, expected_error.splitlines())
767
768    def test_multiline_method_call_a(self):
769        def f():
770            (None
771                .method
772            )()
773        actual = self.get_exception(f)
774        expected = [
775            f"Traceback (most recent call last):",
776            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
777            f"    callable()",
778            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
779            f"    .method",
780            f"     ^^^^^^",
781        ]
782        self.assertEqual(actual, expected)
783
784    def test_multiline_method_call_b(self):
785        def f():
786            (None.
787                method
788            )()
789        actual = self.get_exception(f)
790        expected = [
791            f"Traceback (most recent call last):",
792            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
793            f"    callable()",
794            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
795            f"    method",
796        ]
797        self.assertEqual(actual, expected)
798
799    def test_multiline_method_call_c(self):
800        def f():
801            (None
802                . method
803            )()
804        actual = self.get_exception(f)
805        expected = [
806            f"Traceback (most recent call last):",
807            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
808            f"    callable()",
809            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
810            f"    . method",
811            f"      ^^^^^^",
812        ]
813        self.assertEqual(actual, expected)
814
815    def test_wide_characters_unicode_with_problematic_byte_offset(self):
816        def f():
817            width
818
819        actual = self.get_exception(f)
820        expected = [
821            f"Traceback (most recent call last):",
822            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
823            f"    callable()",
824            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
825            f"    width",
826        ]
827        self.assertEqual(actual, expected)
828
829
830    def test_byte_offset_with_wide_characters_middle(self):
831        def f():
832            width = 1
833            raise ValueError(width)
834
835        actual = self.get_exception(f)
836        expected = [
837            f"Traceback (most recent call last):",
838            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
839            f"    callable()",
840            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
841            f"    raise ValueError(width)",
842        ]
843        self.assertEqual(actual, expected)
844
845    def test_byte_offset_multiline(self):
846        def f():
847            www = 1
848            th = 0
849
850            print(1, www(
851                    th))
852
853        actual = self.get_exception(f)
854        expected = [
855            f"Traceback (most recent call last):",
856            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
857            f"    callable()",
858            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 4}, in f",
859            f"    print(1, www(",
860            f"             ^^^^",
861        ]
862        self.assertEqual(actual, expected)
863
864
865@cpython_only
866@requires_debug_ranges()
867class CPythonTracebackErrorCaretTests(TracebackErrorLocationCaretTests):
868    """
869    Same set of tests as above but with Python's internal traceback printing.
870    """
871    def get_exception(self, callable):
872        from _testcapi import exception_print
873        try:
874            callable()
875            self.fail("No exception thrown.")
876        except Exception as e:
877            with captured_output("stderr") as tbstderr:
878                exception_print(e)
879            return tbstderr.getvalue().splitlines()[:-1]
880
881    callable_line = get_exception.__code__.co_firstlineno + 3
882
883
884class TracebackFormatTests(unittest.TestCase):
885
886    def some_exception(self):
887        raise KeyError('blah')
888
889    @cpython_only
890    def check_traceback_format(self, cleanup_func=None):
891        from _testcapi import traceback_print
892        try:
893            self.some_exception()
894        except KeyError:
895            type_, value, tb = sys.exc_info()
896            if cleanup_func is not None:
897                # Clear the inner frames, not this one
898                cleanup_func(tb.tb_next)
899            traceback_fmt = 'Traceback (most recent call last):\n' + \
900                            ''.join(traceback.format_tb(tb))
901            file_ = StringIO()
902            traceback_print(tb, file_)
903            python_fmt  = file_.getvalue()
904            # Call all _tb and _exc functions
905            with captured_output("stderr") as tbstderr:
906                traceback.print_tb(tb)
907            tbfile = StringIO()
908            traceback.print_tb(tb, file=tbfile)
909            with captured_output("stderr") as excstderr:
910                traceback.print_exc()
911            excfmt = traceback.format_exc()
912            excfile = StringIO()
913            traceback.print_exc(file=excfile)
914        else:
915            raise Error("unable to create test traceback string")
916
917        # Make sure that Python and the traceback module format the same thing
918        self.assertEqual(traceback_fmt, python_fmt)
919        # Now verify the _tb func output
920        self.assertEqual(tbstderr.getvalue(), tbfile.getvalue())
921        # Now verify the _exc func output
922        self.assertEqual(excstderr.getvalue(), excfile.getvalue())
923        self.assertEqual(excfmt, excfile.getvalue())
924
925        # Make sure that the traceback is properly indented.
926        tb_lines = python_fmt.splitlines()
927        banner = tb_lines[0]
928        self.assertEqual(len(tb_lines), 5)
929        location, source_line = tb_lines[-2], tb_lines[-1]
930        self.assertTrue(banner.startswith('Traceback'))
931        self.assertTrue(location.startswith('  File'))
932        self.assertTrue(source_line.startswith('    raise'))
933
934    def test_traceback_format(self):
935        self.check_traceback_format()
936
937    def test_traceback_format_with_cleared_frames(self):
938        # Check that traceback formatting also works with a clear()ed frame
939        def cleanup_tb(tb):
940            tb.tb_frame.clear()
941        self.check_traceback_format(cleanup_tb)
942
943    def test_stack_format(self):
944        # Verify _stack functions. Note we have to use _getframe(1) to
945        # compare them without this frame appearing in the output
946        with captured_output("stderr") as ststderr:
947            traceback.print_stack(sys._getframe(1))
948        stfile = StringIO()
949        traceback.print_stack(sys._getframe(1), file=stfile)
950        self.assertEqual(ststderr.getvalue(), stfile.getvalue())
951
952        stfmt = traceback.format_stack(sys._getframe(1))
953
954        self.assertEqual(ststderr.getvalue(), "".join(stfmt))
955
956    def test_print_stack(self):
957        def prn():
958            traceback.print_stack()
959        with captured_output("stderr") as stderr:
960            prn()
961        lineno = prn.__code__.co_firstlineno
962        self.assertEqual(stderr.getvalue().splitlines()[-4:], [
963            '  File "%s", line %d, in test_print_stack' % (__file__, lineno+3),
964            '    prn()',
965            '  File "%s", line %d, in prn' % (__file__, lineno+1),
966            '    traceback.print_stack()',
967        ])
968
969    # issue 26823 - Shrink recursive tracebacks
970    def _check_recursive_traceback_display(self, render_exc):
971        # Always show full diffs when this test fails
972        # Note that rearranging things may require adjusting
973        # the relative line numbers in the expected tracebacks
974        self.maxDiff = None
975
976        # Check hitting the recursion limit
977        def f():
978            f()
979
980        with captured_output("stderr") as stderr_f:
981            try:
982                f()
983            except RecursionError:
984                render_exc()
985            else:
986                self.fail("no recursion occurred")
987
988        lineno_f = f.__code__.co_firstlineno
989        result_f = (
990            'Traceback (most recent call last):\n'
991            f'  File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
992            '    f()\n'
993            f'  File "{__file__}", line {lineno_f+1}, in f\n'
994            '    f()\n'
995            f'  File "{__file__}", line {lineno_f+1}, in f\n'
996            '    f()\n'
997            f'  File "{__file__}", line {lineno_f+1}, in f\n'
998            '    f()\n'
999            # XXX: The following line changes depending on whether the tests
1000            # are run through the interactive interpreter or with -m
1001            # It also varies depending on the platform (stack size)
1002            # Fortunately, we don't care about exactness here, so we use regex
1003            r'  \[Previous line repeated (\d+) more times\]' '\n'
1004            'RecursionError: maximum recursion depth exceeded\n'
1005        )
1006
1007        expected = result_f.splitlines()
1008        actual = stderr_f.getvalue().splitlines()
1009
1010        # Check the output text matches expectations
1011        # 2nd last line contains the repetition count
1012        self.assertEqual(actual[:-2], expected[:-2])
1013        self.assertRegex(actual[-2], expected[-2])
1014        # last line can have additional text appended
1015        self.assertIn(expected[-1], actual[-1])
1016
1017        # Check the recursion count is roughly as expected
1018        rec_limit = sys.getrecursionlimit()
1019        self.assertIn(int(re.search(r"\d+", actual[-2]).group()), range(rec_limit-60, rec_limit))
1020
1021        # Check a known (limited) number of recursive invocations
1022        def g(count=10):
1023            if count:
1024                return g(count-1)
1025            raise ValueError
1026
1027        with captured_output("stderr") as stderr_g:
1028            try:
1029                g()
1030            except ValueError:
1031                render_exc()
1032            else:
1033                self.fail("no value error was raised")
1034
1035        lineno_g = g.__code__.co_firstlineno
1036        result_g = (
1037            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1038            '    return g(count-1)\n'
1039            '           ^^^^^^^^^^\n'
1040            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1041            '    return g(count-1)\n'
1042            '           ^^^^^^^^^^\n'
1043            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1044            '    return g(count-1)\n'
1045            '           ^^^^^^^^^^\n'
1046            '  [Previous line repeated 7 more times]\n'
1047            f'  File "{__file__}", line {lineno_g+3}, in g\n'
1048            '    raise ValueError\n'
1049            'ValueError\n'
1050        )
1051        tb_line = (
1052            'Traceback (most recent call last):\n'
1053            f'  File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
1054            '    g()\n'
1055        )
1056        expected = (tb_line + result_g).splitlines()
1057        actual = stderr_g.getvalue().splitlines()
1058        self.assertEqual(actual, expected)
1059
1060        # Check 2 different repetitive sections
1061        def h(count=10):
1062            if count:
1063                return h(count-1)
1064            g()
1065
1066        with captured_output("stderr") as stderr_h:
1067            try:
1068                h()
1069            except ValueError:
1070                render_exc()
1071            else:
1072                self.fail("no value error was raised")
1073
1074        lineno_h = h.__code__.co_firstlineno
1075        result_h = (
1076            'Traceback (most recent call last):\n'
1077            f'  File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
1078            '    h()\n'
1079            f'  File "{__file__}", line {lineno_h+2}, in h\n'
1080            '    return h(count-1)\n'
1081            '           ^^^^^^^^^^\n'
1082            f'  File "{__file__}", line {lineno_h+2}, in h\n'
1083            '    return h(count-1)\n'
1084            '           ^^^^^^^^^^\n'
1085            f'  File "{__file__}", line {lineno_h+2}, in h\n'
1086            '    return h(count-1)\n'
1087            '           ^^^^^^^^^^\n'
1088            '  [Previous line repeated 7 more times]\n'
1089            f'  File "{__file__}", line {lineno_h+3}, in h\n'
1090            '    g()\n'
1091        )
1092        expected = (result_h + result_g).splitlines()
1093        actual = stderr_h.getvalue().splitlines()
1094        self.assertEqual(actual, expected)
1095
1096        # Check the boundary conditions. First, test just below the cutoff.
1097        with captured_output("stderr") as stderr_g:
1098            try:
1099                g(traceback._RECURSIVE_CUTOFF)
1100            except ValueError:
1101                render_exc()
1102            else:
1103                self.fail("no error raised")
1104        result_g = (
1105            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1106            '    return g(count-1)\n'
1107            '           ^^^^^^^^^^\n'
1108            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1109            '    return g(count-1)\n'
1110            '           ^^^^^^^^^^\n'
1111            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1112            '    return g(count-1)\n'
1113            '           ^^^^^^^^^^\n'
1114            f'  File "{__file__}", line {lineno_g+3}, in g\n'
1115            '    raise ValueError\n'
1116            'ValueError\n'
1117        )
1118        tb_line = (
1119            'Traceback (most recent call last):\n'
1120            f'  File "{__file__}", line {lineno_g+77}, in _check_recursive_traceback_display\n'
1121            '    g(traceback._RECURSIVE_CUTOFF)\n'
1122        )
1123        expected = (tb_line + result_g).splitlines()
1124        actual = stderr_g.getvalue().splitlines()
1125        self.assertEqual(actual, expected)
1126
1127        # Second, test just above the cutoff.
1128        with captured_output("stderr") as stderr_g:
1129            try:
1130                g(traceback._RECURSIVE_CUTOFF + 1)
1131            except ValueError:
1132                render_exc()
1133            else:
1134                self.fail("no error raised")
1135        result_g = (
1136            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1137            '    return g(count-1)\n'
1138            '           ^^^^^^^^^^\n'
1139            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1140            '    return g(count-1)\n'
1141            '           ^^^^^^^^^^\n'
1142            f'  File "{__file__}", line {lineno_g+2}, in g\n'
1143            '    return g(count-1)\n'
1144            '           ^^^^^^^^^^\n'
1145            '  [Previous line repeated 1 more time]\n'
1146            f'  File "{__file__}", line {lineno_g+3}, in g\n'
1147            '    raise ValueError\n'
1148            'ValueError\n'
1149        )
1150        tb_line = (
1151            'Traceback (most recent call last):\n'
1152            f'  File "{__file__}", line {lineno_g+108}, in _check_recursive_traceback_display\n'
1153            '    g(traceback._RECURSIVE_CUTOFF + 1)\n'
1154        )
1155        expected = (tb_line + result_g).splitlines()
1156        actual = stderr_g.getvalue().splitlines()
1157        self.assertEqual(actual, expected)
1158
1159    @requires_debug_ranges()
1160    def test_recursive_traceback_python(self):
1161        self._check_recursive_traceback_display(traceback.print_exc)
1162
1163    @cpython_only
1164    @requires_debug_ranges()
1165    def test_recursive_traceback_cpython_internal(self):
1166        from _testcapi import exception_print
1167        def render_exc():
1168            exc_type, exc_value, exc_tb = sys.exc_info()
1169            exception_print(exc_value)
1170        self._check_recursive_traceback_display(render_exc)
1171
1172    def test_format_stack(self):
1173        def fmt():
1174            return traceback.format_stack()
1175        result = fmt()
1176        lineno = fmt.__code__.co_firstlineno
1177        self.assertEqual(result[-2:], [
1178            '  File "%s", line %d, in test_format_stack\n'
1179            '    result = fmt()\n' % (__file__, lineno+2),
1180            '  File "%s", line %d, in fmt\n'
1181            '    return traceback.format_stack()\n' % (__file__, lineno+1),
1182        ])
1183
1184    @cpython_only
1185    def test_unhashable(self):
1186        from _testcapi import exception_print
1187
1188        class UnhashableException(Exception):
1189            def __eq__(self, other):
1190                return True
1191
1192        ex1 = UnhashableException('ex1')
1193        ex2 = UnhashableException('ex2')
1194        try:
1195            raise ex2 from ex1
1196        except UnhashableException:
1197            try:
1198                raise ex1
1199            except UnhashableException:
1200                exc_type, exc_val, exc_tb = sys.exc_info()
1201
1202        with captured_output("stderr") as stderr_f:
1203            exception_print(exc_val)
1204
1205        tb = stderr_f.getvalue().strip().splitlines()
1206        self.assertEqual(11, len(tb))
1207        self.assertEqual(context_message.strip(), tb[5])
1208        self.assertIn('UnhashableException: ex2', tb[3])
1209        self.assertIn('UnhashableException: ex1', tb[10])
1210
1211    def deep_eg(self):
1212        e = TypeError(1)
1213        for i in range(2000):
1214            e = ExceptionGroup('eg', [e])
1215        return e
1216
1217    @cpython_only
1218    def test_exception_group_deep_recursion_capi(self):
1219        from _testcapi import exception_print
1220        LIMIT = 75
1221        eg = self.deep_eg()
1222        with captured_output("stderr") as stderr_f:
1223            with support.infinite_recursion(max_depth=LIMIT):
1224                exception_print(eg)
1225        output = stderr_f.getvalue()
1226        self.assertIn('ExceptionGroup', output)
1227        self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
1228
1229    def test_exception_group_deep_recursion_traceback(self):
1230        LIMIT = 75
1231        eg = self.deep_eg()
1232        with captured_output("stderr") as stderr_f:
1233            with support.infinite_recursion(max_depth=LIMIT):
1234                traceback.print_exception(type(eg), eg, eg.__traceback__)
1235        output = stderr_f.getvalue()
1236        self.assertIn('ExceptionGroup', output)
1237        self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
1238
1239    @cpython_only
1240    def test_print_exception_bad_type_capi(self):
1241        from _testcapi import exception_print
1242        with captured_output("stderr") as stderr:
1243            exception_print(42)
1244        self.assertEqual(
1245            stderr.getvalue(),
1246            ('TypeError: print_exception(): '
1247             'Exception expected for value, int found\n')
1248        )
1249
1250    def test_print_exception_bad_type_python(self):
1251        msg = "Exception expected for value, int found"
1252        with self.assertRaisesRegex(TypeError, msg):
1253            traceback.print_exception(42)
1254
1255
1256cause_message = (
1257    "\nThe above exception was the direct cause "
1258    "of the following exception:\n\n")
1259
1260context_message = (
1261    "\nDuring handling of the above exception, "
1262    "another exception occurred:\n\n")
1263
1264boundaries = re.compile(
1265    '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
1266
1267class BaseExceptionReportingTests:
1268
1269    def get_exception(self, exception_or_callable):
1270        if isinstance(exception_or_callable, BaseException):
1271            return exception_or_callable
1272        try:
1273            exception_or_callable()
1274        except Exception as e:
1275            return e
1276
1277    callable_line = get_exception.__code__.co_firstlineno + 4
1278
1279    def zero_div(self):
1280        1/0 # In zero_div
1281
1282    def check_zero_div(self, msg):
1283        lines = msg.splitlines()
1284        if has_no_debug_ranges():
1285            self.assertTrue(lines[-3].startswith('  File'))
1286            self.assertIn('1/0 # In zero_div', lines[-2])
1287        else:
1288            self.assertTrue(lines[-4].startswith('  File'))
1289            self.assertIn('1/0 # In zero_div', lines[-3])
1290        self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
1291
1292    def test_simple(self):
1293        try:
1294            1/0 # Marker
1295        except ZeroDivisionError as _:
1296            e = _
1297        lines = self.get_report(e).splitlines()
1298        if has_no_debug_ranges():
1299            self.assertEqual(len(lines), 4)
1300            self.assertTrue(lines[3].startswith('ZeroDivisionError'))
1301        else:
1302            self.assertEqual(len(lines), 5)
1303            self.assertTrue(lines[4].startswith('ZeroDivisionError'))
1304        self.assertTrue(lines[0].startswith('Traceback'))
1305        self.assertTrue(lines[1].startswith('  File'))
1306        self.assertIn('1/0 # Marker', lines[2])
1307
1308    def test_cause(self):
1309        def inner_raise():
1310            try:
1311                self.zero_div()
1312            except ZeroDivisionError as e:
1313                raise KeyError from e
1314        def outer_raise():
1315            inner_raise() # Marker
1316        blocks = boundaries.split(self.get_report(outer_raise))
1317        self.assertEqual(len(blocks), 3)
1318        self.assertEqual(blocks[1], cause_message)
1319        self.check_zero_div(blocks[0])
1320        self.assertIn('inner_raise() # Marker', blocks[2])
1321
1322    def test_context(self):
1323        def inner_raise():
1324            try:
1325                self.zero_div()
1326            except ZeroDivisionError:
1327                raise KeyError
1328        def outer_raise():
1329            inner_raise() # Marker
1330        blocks = boundaries.split(self.get_report(outer_raise))
1331        self.assertEqual(len(blocks), 3)
1332        self.assertEqual(blocks[1], context_message)
1333        self.check_zero_div(blocks[0])
1334        self.assertIn('inner_raise() # Marker', blocks[2])
1335
1336    def test_context_suppression(self):
1337        try:
1338            try:
1339                raise Exception
1340            except:
1341                raise ZeroDivisionError from None
1342        except ZeroDivisionError as _:
1343            e = _
1344        lines = self.get_report(e).splitlines()
1345        self.assertEqual(len(lines), 4)
1346        self.assertTrue(lines[3].startswith('ZeroDivisionError'))
1347        self.assertTrue(lines[0].startswith('Traceback'))
1348        self.assertTrue(lines[1].startswith('  File'))
1349        self.assertIn('ZeroDivisionError from None', lines[2])
1350
1351    def test_cause_and_context(self):
1352        # When both a cause and a context are set, only the cause should be
1353        # displayed and the context should be muted.
1354        def inner_raise():
1355            try:
1356                self.zero_div()
1357            except ZeroDivisionError as _e:
1358                e = _e
1359            try:
1360                xyzzy
1361            except NameError:
1362                raise KeyError from e
1363        def outer_raise():
1364            inner_raise() # Marker
1365        blocks = boundaries.split(self.get_report(outer_raise))
1366        self.assertEqual(len(blocks), 3)
1367        self.assertEqual(blocks[1], cause_message)
1368        self.check_zero_div(blocks[0])
1369        self.assertIn('inner_raise() # Marker', blocks[2])
1370
1371    def test_cause_recursive(self):
1372        def inner_raise():
1373            try:
1374                try:
1375                    self.zero_div()
1376                except ZeroDivisionError as e:
1377                    z = e
1378                    raise KeyError from e
1379            except KeyError as e:
1380                raise z from e
1381        def outer_raise():
1382            inner_raise() # Marker
1383        blocks = boundaries.split(self.get_report(outer_raise))
1384        self.assertEqual(len(blocks), 3)
1385        self.assertEqual(blocks[1], cause_message)
1386        # The first block is the KeyError raised from the ZeroDivisionError
1387        self.assertIn('raise KeyError from e', blocks[0])
1388        self.assertNotIn('1/0', blocks[0])
1389        # The second block (apart from the boundary) is the ZeroDivisionError
1390        # re-raised from the KeyError
1391        self.assertIn('inner_raise() # Marker', blocks[2])
1392        self.check_zero_div(blocks[2])
1393
1394    def test_syntax_error_offset_at_eol(self):
1395        # See #10186.
1396        def e():
1397            raise SyntaxError('', ('', 0, 5, 'hello'))
1398        msg = self.get_report(e).splitlines()
1399        self.assertEqual(msg[-2], "        ^")
1400        def e():
1401            exec("x = 5 | 4 |")
1402        msg = self.get_report(e).splitlines()
1403        self.assertEqual(msg[-2], '               ^')
1404
1405    def test_syntax_error_no_lineno(self):
1406        # See #34463.
1407
1408        # Without filename
1409        e = SyntaxError('bad syntax')
1410        msg = self.get_report(e).splitlines()
1411        self.assertEqual(msg,
1412            ['SyntaxError: bad syntax'])
1413        e.lineno = 100
1414        msg = self.get_report(e).splitlines()
1415        self.assertEqual(msg,
1416            ['  File "<string>", line 100', 'SyntaxError: bad syntax'])
1417
1418        # With filename
1419        e = SyntaxError('bad syntax')
1420        e.filename = 'myfile.py'
1421
1422        msg = self.get_report(e).splitlines()
1423        self.assertEqual(msg,
1424            ['SyntaxError: bad syntax (myfile.py)'])
1425        e.lineno = 100
1426        msg = self.get_report(e).splitlines()
1427        self.assertEqual(msg,
1428            ['  File "myfile.py", line 100', 'SyntaxError: bad syntax'])
1429
1430    def test_message_none(self):
1431        # A message that looks like "None" should not be treated specially
1432        err = self.get_report(Exception(None))
1433        self.assertIn('Exception: None\n', err)
1434        err = self.get_report(Exception('None'))
1435        self.assertIn('Exception: None\n', err)
1436        err = self.get_report(Exception())
1437        self.assertIn('Exception\n', err)
1438        err = self.get_report(Exception(''))
1439        self.assertIn('Exception\n', err)
1440
1441    def test_syntax_error_various_offsets(self):
1442        for offset in range(-5, 10):
1443            for add in [0, 2]:
1444                text = " "*add + "text%d" % offset
1445                expected = ['  File "file.py", line 1']
1446                if offset < 1:
1447                    expected.append("    %s" % text.lstrip())
1448                elif offset <= 6:
1449                    expected.append("    %s" % text.lstrip())
1450                    expected.append("    %s^" % (" "*(offset-1)))
1451                else:
1452                    expected.append("    %s" % text.lstrip())
1453                    expected.append("    %s^" % (" "*5))
1454                expected.append("SyntaxError: msg")
1455                expected.append("")
1456                err = self.get_report(SyntaxError("msg", ("file.py", 1, offset+add, text)))
1457                exp = "\n".join(expected)
1458                self.assertEqual(exp, err)
1459
1460    def test_exception_with_note(self):
1461        e = ValueError(123)
1462        vanilla = self.get_report(e)
1463
1464        e.add_note('My Note')
1465        self.assertEqual(self.get_report(e), vanilla + 'My Note\n')
1466
1467        del e.__notes__
1468        e.add_note('')
1469        self.assertEqual(self.get_report(e), vanilla + '\n')
1470
1471        del e.__notes__
1472        e.add_note('Your Note')
1473        self.assertEqual(self.get_report(e), vanilla + 'Your Note\n')
1474
1475        del e.__notes__
1476        self.assertEqual(self.get_report(e), vanilla)
1477
1478    def test_exception_with_invalid_notes(self):
1479        e = ValueError(123)
1480        vanilla = self.get_report(e)
1481
1482        # non-sequence __notes__
1483        class BadThing:
1484            def __str__(self):
1485                return 'bad str'
1486
1487            def __repr__(self):
1488                return 'bad repr'
1489
1490        # unprintable, non-sequence __notes__
1491        class Unprintable:
1492            def __repr__(self):
1493                raise ValueError('bad value')
1494
1495        e.__notes__ = BadThing()
1496        notes_repr = 'bad repr'
1497        self.assertEqual(self.get_report(e), vanilla + notes_repr)
1498
1499        e.__notes__ = Unprintable()
1500        err_msg = '<__notes__ repr() failed>'
1501        self.assertEqual(self.get_report(e), vanilla + err_msg)
1502
1503        # non-string item in the __notes__ sequence
1504        e.__notes__  = [BadThing(), 'Final Note']
1505        bad_note = 'bad str'
1506        self.assertEqual(self.get_report(e), vanilla + bad_note + '\nFinal Note\n')
1507
1508        # unprintable, non-string item in the __notes__ sequence
1509        e.__notes__  = [Unprintable(), 'Final Note']
1510        err_msg = '<note str() failed>'
1511        self.assertEqual(self.get_report(e), vanilla + err_msg + '\nFinal Note\n')
1512
1513    def test_exception_with_note_with_multiple_notes(self):
1514        e = ValueError(42)
1515        vanilla = self.get_report(e)
1516
1517        e.add_note('Note 1')
1518        e.add_note('Note 2')
1519        e.add_note('Note 3')
1520
1521        self.assertEqual(
1522            self.get_report(e),
1523            vanilla + 'Note 1\n' + 'Note 2\n' + 'Note 3\n')
1524
1525        del e.__notes__
1526        e.add_note('Note 4')
1527        del e.__notes__
1528        e.add_note('Note 5')
1529        e.add_note('Note 6')
1530
1531        self.assertEqual(
1532            self.get_report(e),
1533            vanilla + 'Note 5\n' + 'Note 6\n')
1534
1535    def test_exception_qualname(self):
1536        class A:
1537            class B:
1538                class X(Exception):
1539                    def __str__(self):
1540                        return "I am X"
1541
1542        err = self.get_report(A.B.X())
1543        str_value = 'I am X'
1544        str_name = '.'.join([A.B.X.__module__, A.B.X.__qualname__])
1545        exp = "%s: %s\n" % (str_name, str_value)
1546        self.assertEqual(exp, MODULE_PREFIX + err)
1547
1548    def test_exception_modulename(self):
1549        class X(Exception):
1550            def __str__(self):
1551                return "I am X"
1552
1553        for modulename in '__main__', 'builtins', 'some_module':
1554            X.__module__ = modulename
1555            with self.subTest(modulename=modulename):
1556                err = self.get_report(X())
1557                str_value = 'I am X'
1558                if modulename in ['builtins', '__main__']:
1559                    str_name = X.__qualname__
1560                else:
1561                    str_name = '.'.join([X.__module__, X.__qualname__])
1562                exp = "%s: %s\n" % (str_name, str_value)
1563                self.assertEqual(exp, err)
1564
1565    def test_exception_modulename_not_unicode(self):
1566        class X(Exception):
1567            def __str__(self):
1568                return "I am X"
1569
1570        X.__module__ = 42
1571
1572        err = self.get_report(X())
1573        exp = f'<unknown>.{X.__qualname__}: I am X\n'
1574        self.assertEqual(exp, err)
1575
1576    def test_exception_bad__str__(self):
1577        class X(Exception):
1578            def __str__(self):
1579                1/0
1580        err = self.get_report(X())
1581        str_value = '<exception str() failed>'
1582        str_name = '.'.join([X.__module__, X.__qualname__])
1583        self.assertEqual(MODULE_PREFIX + err, f"{str_name}: {str_value}\n")
1584
1585
1586    # #### Exception Groups ####
1587
1588    def test_exception_group_basic(self):
1589        def exc():
1590            raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
1591
1592        expected = (
1593             f'  + Exception Group Traceback (most recent call last):\n'
1594             f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
1595             f'  |     exception_or_callable()\n'
1596             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
1597             f'  |     raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
1598             f'  | ExceptionGroup: eg (2 sub-exceptions)\n'
1599             f'  +-+---------------- 1 ----------------\n'
1600             f'    | ValueError: 1\n'
1601             f'    +---------------- 2 ----------------\n'
1602             f'    | TypeError: 2\n'
1603             f'    +------------------------------------\n')
1604
1605        report = self.get_report(exc)
1606        self.assertEqual(report, expected)
1607
1608    def test_exception_group_cause(self):
1609        def exc():
1610            EG = ExceptionGroup
1611            try:
1612                raise EG("eg1", [ValueError(1), TypeError(2)])
1613            except Exception as e:
1614                raise EG("eg2", [ValueError(3), TypeError(4)]) from e
1615
1616        expected = (f'  + Exception Group Traceback (most recent call last):\n'
1617                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
1618                    f'  |     raise EG("eg1", [ValueError(1), TypeError(2)])\n'
1619                    f'  | ExceptionGroup: eg1 (2 sub-exceptions)\n'
1620                    f'  +-+---------------- 1 ----------------\n'
1621                    f'    | ValueError: 1\n'
1622                    f'    +---------------- 2 ----------------\n'
1623                    f'    | TypeError: 2\n'
1624                    f'    +------------------------------------\n'
1625                    f'\n'
1626                    f'The above exception was the direct cause of the following exception:\n'
1627                    f'\n'
1628                    f'  + Exception Group Traceback (most recent call last):\n'
1629                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
1630                    f'  |     exception_or_callable()\n'
1631                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
1632                    f'  |     raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
1633                    f'  | ExceptionGroup: eg2 (2 sub-exceptions)\n'
1634                    f'  +-+---------------- 1 ----------------\n'
1635                    f'    | ValueError: 3\n'
1636                    f'    +---------------- 2 ----------------\n'
1637                    f'    | TypeError: 4\n'
1638                    f'    +------------------------------------\n')
1639
1640        report = self.get_report(exc)
1641        self.assertEqual(report, expected)
1642
1643    def test_exception_group_context_with_context(self):
1644        def exc():
1645            EG = ExceptionGroup
1646            try:
1647                try:
1648                    raise EG("eg1", [ValueError(1), TypeError(2)])
1649                except:
1650                    raise EG("eg2", [ValueError(3), TypeError(4)])
1651            except:
1652                raise ImportError(5)
1653
1654        expected = (
1655             f'  + Exception Group Traceback (most recent call last):\n'
1656             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
1657             f'  |     raise EG("eg1", [ValueError(1), TypeError(2)])\n'
1658             f'  | ExceptionGroup: eg1 (2 sub-exceptions)\n'
1659             f'  +-+---------------- 1 ----------------\n'
1660             f'    | ValueError: 1\n'
1661             f'    +---------------- 2 ----------------\n'
1662             f'    | TypeError: 2\n'
1663             f'    +------------------------------------\n'
1664             f'\n'
1665             f'During handling of the above exception, another exception occurred:\n'
1666             f'\n'
1667             f'  + Exception Group Traceback (most recent call last):\n'
1668             f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
1669             f'  |     raise EG("eg2", [ValueError(3), TypeError(4)])\n'
1670             f'  | ExceptionGroup: eg2 (2 sub-exceptions)\n'
1671             f'  +-+---------------- 1 ----------------\n'
1672             f'    | ValueError: 3\n'
1673             f'    +---------------- 2 ----------------\n'
1674             f'    | TypeError: 4\n'
1675             f'    +------------------------------------\n'
1676             f'\n'
1677             f'During handling of the above exception, another exception occurred:\n'
1678             f'\n'
1679             f'Traceback (most recent call last):\n'
1680             f'  File "{__file__}", line {self.callable_line}, in get_exception\n'
1681             f'    exception_or_callable()\n'
1682             f'  File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
1683             f'    raise ImportError(5)\n'
1684             f'ImportError: 5\n')
1685
1686        report = self.get_report(exc)
1687        self.assertEqual(report, expected)
1688
1689    def test_exception_group_nested(self):
1690        def exc():
1691            EG = ExceptionGroup
1692            VE = ValueError
1693            TE = TypeError
1694            try:
1695                try:
1696                    raise EG("nested", [TE(2), TE(3)])
1697                except Exception as e:
1698                    exc = e
1699                raise EG("eg", [VE(1), exc, VE(4)])
1700            except:
1701                raise EG("top", [VE(5)])
1702
1703        expected = (f'  + Exception Group Traceback (most recent call last):\n'
1704                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
1705                    f'  |     raise EG("eg", [VE(1), exc, VE(4)])\n'
1706                    f'  | ExceptionGroup: eg (3 sub-exceptions)\n'
1707                    f'  +-+---------------- 1 ----------------\n'
1708                    f'    | ValueError: 1\n'
1709                    f'    +---------------- 2 ----------------\n'
1710                    f'    | Exception Group Traceback (most recent call last):\n'
1711                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
1712                    f'    |     raise EG("nested", [TE(2), TE(3)])\n'
1713                    f'    | ExceptionGroup: nested (2 sub-exceptions)\n'
1714                    f'    +-+---------------- 1 ----------------\n'
1715                    f'      | TypeError: 2\n'
1716                    f'      +---------------- 2 ----------------\n'
1717                    f'      | TypeError: 3\n'
1718                    f'      +------------------------------------\n'
1719                    f'    +---------------- 3 ----------------\n'
1720                    f'    | ValueError: 4\n'
1721                    f'    +------------------------------------\n'
1722                    f'\n'
1723                    f'During handling of the above exception, another exception occurred:\n'
1724                    f'\n'
1725                    f'  + Exception Group Traceback (most recent call last):\n'
1726                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
1727                    f'  |     exception_or_callable()\n'
1728                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
1729                    f'  |     raise EG("top", [VE(5)])\n'
1730                    f'  | ExceptionGroup: top (1 sub-exception)\n'
1731                    f'  +-+---------------- 1 ----------------\n'
1732                    f'    | ValueError: 5\n'
1733                    f'    +------------------------------------\n')
1734
1735        report = self.get_report(exc)
1736        self.assertEqual(report, expected)
1737
1738    def test_exception_group_width_limit(self):
1739        excs = []
1740        for i in range(1000):
1741            excs.append(ValueError(i))
1742        eg = ExceptionGroup('eg', excs)
1743
1744        expected = ('  | ExceptionGroup: eg (1000 sub-exceptions)\n'
1745                    '  +-+---------------- 1 ----------------\n'
1746                    '    | ValueError: 0\n'
1747                    '    +---------------- 2 ----------------\n'
1748                    '    | ValueError: 1\n'
1749                    '    +---------------- 3 ----------------\n'
1750                    '    | ValueError: 2\n'
1751                    '    +---------------- 4 ----------------\n'
1752                    '    | ValueError: 3\n'
1753                    '    +---------------- 5 ----------------\n'
1754                    '    | ValueError: 4\n'
1755                    '    +---------------- 6 ----------------\n'
1756                    '    | ValueError: 5\n'
1757                    '    +---------------- 7 ----------------\n'
1758                    '    | ValueError: 6\n'
1759                    '    +---------------- 8 ----------------\n'
1760                    '    | ValueError: 7\n'
1761                    '    +---------------- 9 ----------------\n'
1762                    '    | ValueError: 8\n'
1763                    '    +---------------- 10 ----------------\n'
1764                    '    | ValueError: 9\n'
1765                    '    +---------------- 11 ----------------\n'
1766                    '    | ValueError: 10\n'
1767                    '    +---------------- 12 ----------------\n'
1768                    '    | ValueError: 11\n'
1769                    '    +---------------- 13 ----------------\n'
1770                    '    | ValueError: 12\n'
1771                    '    +---------------- 14 ----------------\n'
1772                    '    | ValueError: 13\n'
1773                    '    +---------------- 15 ----------------\n'
1774                    '    | ValueError: 14\n'
1775                    '    +---------------- ... ----------------\n'
1776                    '    | and 985 more exceptions\n'
1777                    '    +------------------------------------\n')
1778
1779        report = self.get_report(eg)
1780        self.assertEqual(report, expected)
1781
1782    def test_exception_group_depth_limit(self):
1783        exc = TypeError('bad type')
1784        for i in range(1000):
1785            exc = ExceptionGroup(
1786                f'eg{i}',
1787                [ValueError(i), exc, ValueError(-i)])
1788
1789        expected = ('  | ExceptionGroup: eg999 (3 sub-exceptions)\n'
1790                    '  +-+---------------- 1 ----------------\n'
1791                    '    | ValueError: 999\n'
1792                    '    +---------------- 2 ----------------\n'
1793                    '    | ExceptionGroup: eg998 (3 sub-exceptions)\n'
1794                    '    +-+---------------- 1 ----------------\n'
1795                    '      | ValueError: 998\n'
1796                    '      +---------------- 2 ----------------\n'
1797                    '      | ExceptionGroup: eg997 (3 sub-exceptions)\n'
1798                    '      +-+---------------- 1 ----------------\n'
1799                    '        | ValueError: 997\n'
1800                    '        +---------------- 2 ----------------\n'
1801                    '        | ExceptionGroup: eg996 (3 sub-exceptions)\n'
1802                    '        +-+---------------- 1 ----------------\n'
1803                    '          | ValueError: 996\n'
1804                    '          +---------------- 2 ----------------\n'
1805                    '          | ExceptionGroup: eg995 (3 sub-exceptions)\n'
1806                    '          +-+---------------- 1 ----------------\n'
1807                    '            | ValueError: 995\n'
1808                    '            +---------------- 2 ----------------\n'
1809                    '            | ExceptionGroup: eg994 (3 sub-exceptions)\n'
1810                    '            +-+---------------- 1 ----------------\n'
1811                    '              | ValueError: 994\n'
1812                    '              +---------------- 2 ----------------\n'
1813                    '              | ExceptionGroup: eg993 (3 sub-exceptions)\n'
1814                    '              +-+---------------- 1 ----------------\n'
1815                    '                | ValueError: 993\n'
1816                    '                +---------------- 2 ----------------\n'
1817                    '                | ExceptionGroup: eg992 (3 sub-exceptions)\n'
1818                    '                +-+---------------- 1 ----------------\n'
1819                    '                  | ValueError: 992\n'
1820                    '                  +---------------- 2 ----------------\n'
1821                    '                  | ExceptionGroup: eg991 (3 sub-exceptions)\n'
1822                    '                  +-+---------------- 1 ----------------\n'
1823                    '                    | ValueError: 991\n'
1824                    '                    +---------------- 2 ----------------\n'
1825                    '                    | ExceptionGroup: eg990 (3 sub-exceptions)\n'
1826                    '                    +-+---------------- 1 ----------------\n'
1827                    '                      | ValueError: 990\n'
1828                    '                      +---------------- 2 ----------------\n'
1829                    '                      | ... (max_group_depth is 10)\n'
1830                    '                      +---------------- 3 ----------------\n'
1831                    '                      | ValueError: -990\n'
1832                    '                      +------------------------------------\n'
1833                    '                    +---------------- 3 ----------------\n'
1834                    '                    | ValueError: -991\n'
1835                    '                    +------------------------------------\n'
1836                    '                  +---------------- 3 ----------------\n'
1837                    '                  | ValueError: -992\n'
1838                    '                  +------------------------------------\n'
1839                    '                +---------------- 3 ----------------\n'
1840                    '                | ValueError: -993\n'
1841                    '                +------------------------------------\n'
1842                    '              +---------------- 3 ----------------\n'
1843                    '              | ValueError: -994\n'
1844                    '              +------------------------------------\n'
1845                    '            +---------------- 3 ----------------\n'
1846                    '            | ValueError: -995\n'
1847                    '            +------------------------------------\n'
1848                    '          +---------------- 3 ----------------\n'
1849                    '          | ValueError: -996\n'
1850                    '          +------------------------------------\n'
1851                    '        +---------------- 3 ----------------\n'
1852                    '        | ValueError: -997\n'
1853                    '        +------------------------------------\n'
1854                    '      +---------------- 3 ----------------\n'
1855                    '      | ValueError: -998\n'
1856                    '      +------------------------------------\n'
1857                    '    +---------------- 3 ----------------\n'
1858                    '    | ValueError: -999\n'
1859                    '    +------------------------------------\n')
1860
1861        report = self.get_report(exc)
1862        self.assertEqual(report, expected)
1863
1864    def test_exception_group_with_notes(self):
1865        def exc():
1866            try:
1867                excs = []
1868                for msg in ['bad value', 'terrible value']:
1869                    try:
1870                        raise ValueError(msg)
1871                    except ValueError as e:
1872                        e.add_note(f'the {msg}')
1873                        excs.append(e)
1874                raise ExceptionGroup("nested", excs)
1875            except ExceptionGroup as e:
1876                e.add_note(('>> Multi line note\n'
1877                            '>> Because I am such\n'
1878                            '>> an important exception.\n'
1879                            '>> empty lines work too\n'
1880                            '\n'
1881                            '(that was an empty line)'))
1882                raise
1883
1884        expected = (f'  + Exception Group Traceback (most recent call last):\n'
1885                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
1886                    f'  |     exception_or_callable()\n'
1887                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
1888                    f'  |     raise ExceptionGroup("nested", excs)\n'
1889                    f'  | ExceptionGroup: nested (2 sub-exceptions)\n'
1890                    f'  | >> Multi line note\n'
1891                    f'  | >> Because I am such\n'
1892                    f'  | >> an important exception.\n'
1893                    f'  | >> empty lines work too\n'
1894                    f'  | \n'
1895                    f'  | (that was an empty line)\n'
1896                    f'  +-+---------------- 1 ----------------\n'
1897                    f'    | Traceback (most recent call last):\n'
1898                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
1899                    f'    |     raise ValueError(msg)\n'
1900                    f'    | ValueError: bad value\n'
1901                    f'    | the bad value\n'
1902                    f'    +---------------- 2 ----------------\n'
1903                    f'    | Traceback (most recent call last):\n'
1904                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
1905                    f'    |     raise ValueError(msg)\n'
1906                    f'    | ValueError: terrible value\n'
1907                    f'    | the terrible value\n'
1908                    f'    +------------------------------------\n')
1909
1910        report = self.get_report(exc)
1911        self.assertEqual(report, expected)
1912
1913    def test_exception_group_with_multiple_notes(self):
1914        def exc():
1915            try:
1916                excs = []
1917                for msg in ['bad value', 'terrible value']:
1918                    try:
1919                        raise ValueError(msg)
1920                    except ValueError as e:
1921                        e.add_note(f'the {msg}')
1922                        e.add_note(f'Goodbye {msg}')
1923                        excs.append(e)
1924                raise ExceptionGroup("nested", excs)
1925            except ExceptionGroup as e:
1926                e.add_note(('>> Multi line note\n'
1927                            '>> Because I am such\n'
1928                            '>> an important exception.\n'
1929                            '>> empty lines work too\n'
1930                            '\n'
1931                            '(that was an empty line)'))
1932                e.add_note('Goodbye!')
1933                raise
1934
1935        expected = (f'  + Exception Group Traceback (most recent call last):\n'
1936                    f'  |   File "{__file__}", line {self.callable_line}, in get_exception\n'
1937                    f'  |     exception_or_callable()\n'
1938                    f'  |   File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
1939                    f'  |     raise ExceptionGroup("nested", excs)\n'
1940                    f'  | ExceptionGroup: nested (2 sub-exceptions)\n'
1941                    f'  | >> Multi line note\n'
1942                    f'  | >> Because I am such\n'
1943                    f'  | >> an important exception.\n'
1944                    f'  | >> empty lines work too\n'
1945                    f'  | \n'
1946                    f'  | (that was an empty line)\n'
1947                    f'  | Goodbye!\n'
1948                    f'  +-+---------------- 1 ----------------\n'
1949                    f'    | Traceback (most recent call last):\n'
1950                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
1951                    f'    |     raise ValueError(msg)\n'
1952                    f'    | ValueError: bad value\n'
1953                    f'    | the bad value\n'
1954                    f'    | Goodbye bad value\n'
1955                    f'    +---------------- 2 ----------------\n'
1956                    f'    | Traceback (most recent call last):\n'
1957                    f'    |   File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
1958                    f'    |     raise ValueError(msg)\n'
1959                    f'    | ValueError: terrible value\n'
1960                    f'    | the terrible value\n'
1961                    f'    | Goodbye terrible value\n'
1962                    f'    +------------------------------------\n')
1963
1964        report = self.get_report(exc)
1965        self.assertEqual(report, expected)
1966
1967    def test_KeyboardInterrupt_at_first_line_of_frame(self):
1968        # see GH-93249
1969        def f():
1970            return sys._getframe()
1971
1972        tb_next = None
1973        frame = f()
1974        lasti = 0
1975        lineno = f.__code__.co_firstlineno
1976        tb = types.TracebackType(tb_next, frame, lasti, lineno)
1977
1978        exc = KeyboardInterrupt()
1979        exc.__traceback__ = tb
1980
1981        expected = (f'Traceback (most recent call last):\n'
1982                    f'  File "{__file__}", line {lineno}, in f\n'
1983                    f'    def f():\n'
1984                    f'\n'
1985                    f'KeyboardInterrupt\n')
1986
1987        report = self.get_report(exc)
1988        # remove trailing writespace:
1989        report = '\n'.join([l.rstrip() for l in report.split('\n')])
1990        self.assertEqual(report, expected)
1991
1992
1993class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
1994    #
1995    # This checks reporting through the 'traceback' module, with both
1996    # format_exception() and print_exception().
1997    #
1998
1999    def get_report(self, e):
2000        e = self.get_exception(e)
2001        s = ''.join(
2002            traceback.format_exception(type(e), e, e.__traceback__))
2003        with captured_output("stderr") as sio:
2004            traceback.print_exception(type(e), e, e.__traceback__)
2005        self.assertEqual(sio.getvalue(), s)
2006        return s
2007
2008
2009class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
2010    #
2011    # This checks built-in reporting by the interpreter.
2012    #
2013
2014    @cpython_only
2015    def get_report(self, e):
2016        from _testcapi import exception_print
2017        e = self.get_exception(e)
2018        with captured_output("stderr") as s:
2019            exception_print(e)
2020        return s.getvalue()
2021
2022
2023class LimitTests(unittest.TestCase):
2024
2025    ''' Tests for limit argument.
2026        It's enough to test extact_tb, extract_stack and format_exception '''
2027
2028    def last_raises1(self):
2029        raise Exception('Last raised')
2030
2031    def last_raises2(self):
2032        self.last_raises1()
2033
2034    def last_raises3(self):
2035        self.last_raises2()
2036
2037    def last_raises4(self):
2038        self.last_raises3()
2039
2040    def last_raises5(self):
2041        self.last_raises4()
2042
2043    def last_returns_frame1(self):
2044        return sys._getframe()
2045
2046    def last_returns_frame2(self):
2047        return self.last_returns_frame1()
2048
2049    def last_returns_frame3(self):
2050        return self.last_returns_frame2()
2051
2052    def last_returns_frame4(self):
2053        return self.last_returns_frame3()
2054
2055    def last_returns_frame5(self):
2056        return self.last_returns_frame4()
2057
2058    def test_extract_stack(self):
2059        frame = self.last_returns_frame5()
2060        def extract(**kwargs):
2061            return traceback.extract_stack(frame, **kwargs)
2062        def assertEqualExcept(actual, expected, ignore):
2063            self.assertEqual(actual[:ignore], expected[:ignore])
2064            self.assertEqual(actual[ignore+1:], expected[ignore+1:])
2065            self.assertEqual(len(actual), len(expected))
2066
2067        with support.swap_attr(sys, 'tracebacklimit', 1000):
2068            nolim = extract()
2069            self.assertGreater(len(nolim), 5)
2070            self.assertEqual(extract(limit=2), nolim[-2:])
2071            assertEqualExcept(extract(limit=100), nolim[-100:], -5-1)
2072            self.assertEqual(extract(limit=-2), nolim[:2])
2073            assertEqualExcept(extract(limit=-100), nolim[:100], len(nolim)-5-1)
2074            self.assertEqual(extract(limit=0), [])
2075            del sys.tracebacklimit
2076            assertEqualExcept(extract(), nolim, -5-1)
2077            sys.tracebacklimit = 2
2078            self.assertEqual(extract(), nolim[-2:])
2079            self.assertEqual(extract(limit=3), nolim[-3:])
2080            self.assertEqual(extract(limit=-3), nolim[:3])
2081            sys.tracebacklimit = 0
2082            self.assertEqual(extract(), [])
2083            sys.tracebacklimit = -1
2084            self.assertEqual(extract(), [])
2085
2086    def test_extract_tb(self):
2087        try:
2088            self.last_raises5()
2089        except Exception:
2090            exc_type, exc_value, tb = sys.exc_info()
2091        def extract(**kwargs):
2092            return traceback.extract_tb(tb, **kwargs)
2093
2094        with support.swap_attr(sys, 'tracebacklimit', 1000):
2095            nolim = extract()
2096            self.assertEqual(len(nolim), 5+1)
2097            self.assertEqual(extract(limit=2), nolim[:2])
2098            self.assertEqual(extract(limit=10), nolim)
2099            self.assertEqual(extract(limit=-2), nolim[-2:])
2100            self.assertEqual(extract(limit=-10), nolim)
2101            self.assertEqual(extract(limit=0), [])
2102            del sys.tracebacklimit
2103            self.assertEqual(extract(), nolim)
2104            sys.tracebacklimit = 2
2105            self.assertEqual(extract(), nolim[:2])
2106            self.assertEqual(extract(limit=3), nolim[:3])
2107            self.assertEqual(extract(limit=-3), nolim[-3:])
2108            sys.tracebacklimit = 0
2109            self.assertEqual(extract(), [])
2110            sys.tracebacklimit = -1
2111            self.assertEqual(extract(), [])
2112
2113    def test_format_exception(self):
2114        try:
2115            self.last_raises5()
2116        except Exception:
2117            exc_type, exc_value, tb = sys.exc_info()
2118        # [1:-1] to exclude "Traceback (...)" header and
2119        # exception type and value
2120        def extract(**kwargs):
2121            return traceback.format_exception(exc_type, exc_value, tb, **kwargs)[1:-1]
2122
2123        with support.swap_attr(sys, 'tracebacklimit', 1000):
2124            nolim = extract()
2125            self.assertEqual(len(nolim), 5+1)
2126            self.assertEqual(extract(limit=2), nolim[:2])
2127            self.assertEqual(extract(limit=10), nolim)
2128            self.assertEqual(extract(limit=-2), nolim[-2:])
2129            self.assertEqual(extract(limit=-10), nolim)
2130            self.assertEqual(extract(limit=0), [])
2131            del sys.tracebacklimit
2132            self.assertEqual(extract(), nolim)
2133            sys.tracebacklimit = 2
2134            self.assertEqual(extract(), nolim[:2])
2135            self.assertEqual(extract(limit=3), nolim[:3])
2136            self.assertEqual(extract(limit=-3), nolim[-3:])
2137            sys.tracebacklimit = 0
2138            self.assertEqual(extract(), [])
2139            sys.tracebacklimit = -1
2140            self.assertEqual(extract(), [])
2141
2142
2143class MiscTracebackCases(unittest.TestCase):
2144    #
2145    # Check non-printing functions in traceback module
2146    #
2147
2148    def test_clear(self):
2149        def outer():
2150            middle()
2151        def middle():
2152            inner()
2153        def inner():
2154            i = 1
2155            1/0
2156
2157        try:
2158            outer()
2159        except:
2160            type_, value, tb = sys.exc_info()
2161
2162        # Initial assertion: there's one local in the inner frame.
2163        inner_frame = tb.tb_next.tb_next.tb_next.tb_frame
2164        self.assertEqual(len(inner_frame.f_locals), 1)
2165
2166        # Clear traceback frames
2167        traceback.clear_frames(tb)
2168
2169        # Local variable dict should now be empty.
2170        self.assertEqual(len(inner_frame.f_locals), 0)
2171
2172    def test_extract_stack(self):
2173        def extract():
2174            return traceback.extract_stack()
2175        result = extract()
2176        lineno = extract.__code__.co_firstlineno
2177        self.assertEqual(result[-2:], [
2178            (__file__, lineno+2, 'test_extract_stack', 'result = extract()'),
2179            (__file__, lineno+1, 'extract', 'return traceback.extract_stack()'),
2180            ])
2181        self.assertEqual(len(result[0]), 4)
2182
2183
2184class TestFrame(unittest.TestCase):
2185
2186    def test_basics(self):
2187        linecache.clearcache()
2188        linecache.lazycache("f", globals())
2189        f = traceback.FrameSummary("f", 1, "dummy")
2190        self.assertEqual(f,
2191            ("f", 1, "dummy", '"""Test cases for traceback module"""'))
2192        self.assertEqual(tuple(f),
2193            ("f", 1, "dummy", '"""Test cases for traceback module"""'))
2194        self.assertEqual(f, traceback.FrameSummary("f", 1, "dummy"))
2195        self.assertEqual(f, tuple(f))
2196        # Since tuple.__eq__ doesn't support FrameSummary, the equality
2197        # operator fallbacks to FrameSummary.__eq__.
2198        self.assertEqual(tuple(f), f)
2199        self.assertIsNone(f.locals)
2200        self.assertNotEqual(f, object())
2201        self.assertEqual(f, ALWAYS_EQ)
2202
2203    def test_lazy_lines(self):
2204        linecache.clearcache()
2205        f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
2206        self.assertEqual(None, f._line)
2207        linecache.lazycache("f", globals())
2208        self.assertEqual(
2209            '"""Test cases for traceback module"""',
2210            f.line)
2211
2212    def test_no_line(self):
2213        f = traceback.FrameSummary("f", None, "dummy")
2214        self.assertEqual(f.line, None)
2215
2216    def test_explicit_line(self):
2217        f = traceback.FrameSummary("f", 1, "dummy", line="line")
2218        self.assertEqual("line", f.line)
2219
2220    def test_len(self):
2221        f = traceback.FrameSummary("f", 1, "dummy", line="line")
2222        self.assertEqual(len(f), 4)
2223
2224
2225class TestStack(unittest.TestCase):
2226
2227    def test_walk_stack(self):
2228        def deeper():
2229            return list(traceback.walk_stack(None))
2230        s1 = list(traceback.walk_stack(None))
2231        s2 = deeper()
2232        self.assertEqual(len(s2) - len(s1), 1)
2233        self.assertEqual(s2[1:], s1)
2234
2235    def test_walk_tb(self):
2236        try:
2237            1/0
2238        except Exception:
2239            _, _, tb = sys.exc_info()
2240        s = list(traceback.walk_tb(tb))
2241        self.assertEqual(len(s), 1)
2242
2243    def test_extract_stack(self):
2244        s = traceback.StackSummary.extract(traceback.walk_stack(None))
2245        self.assertIsInstance(s, traceback.StackSummary)
2246
2247    def test_extract_stack_limit(self):
2248        s = traceback.StackSummary.extract(traceback.walk_stack(None), limit=5)
2249        self.assertEqual(len(s), 5)
2250
2251    def test_extract_stack_lookup_lines(self):
2252        linecache.clearcache()
2253        linecache.updatecache('/foo.py', globals())
2254        c = test_code('/foo.py', 'method')
2255        f = test_frame(c, None, None)
2256        s = traceback.StackSummary.extract(iter([(f, 6)]), lookup_lines=True)
2257        linecache.clearcache()
2258        self.assertEqual(s[0].line, "import sys")
2259
2260    def test_extract_stackup_deferred_lookup_lines(self):
2261        linecache.clearcache()
2262        c = test_code('/foo.py', 'method')
2263        f = test_frame(c, None, None)
2264        s = traceback.StackSummary.extract(iter([(f, 6)]), lookup_lines=False)
2265        self.assertEqual({}, linecache.cache)
2266        linecache.updatecache('/foo.py', globals())
2267        self.assertEqual(s[0].line, "import sys")
2268
2269    def test_from_list(self):
2270        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
2271        self.assertEqual(
2272            ['  File "foo.py", line 1, in fred\n    line\n'],
2273            s.format())
2274
2275    def test_from_list_edited_stack(self):
2276        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
2277        s[0] = ('foo.py', 2, 'fred', 'line')
2278        s2 = traceback.StackSummary.from_list(s)
2279        self.assertEqual(
2280            ['  File "foo.py", line 2, in fred\n    line\n'],
2281            s2.format())
2282
2283    def test_format_smoke(self):
2284        # For detailed tests see the format_list tests, which consume the same
2285        # code.
2286        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
2287        self.assertEqual(
2288            ['  File "foo.py", line 1, in fred\n    line\n'],
2289            s.format())
2290
2291    def test_locals(self):
2292        linecache.updatecache('/foo.py', globals())
2293        c = test_code('/foo.py', 'method')
2294        f = test_frame(c, globals(), {'something': 1})
2295        s = traceback.StackSummary.extract(iter([(f, 6)]), capture_locals=True)
2296        self.assertEqual(s[0].locals, {'something': '1'})
2297
2298    def test_no_locals(self):
2299        linecache.updatecache('/foo.py', globals())
2300        c = test_code('/foo.py', 'method')
2301        f = test_frame(c, globals(), {'something': 1})
2302        s = traceback.StackSummary.extract(iter([(f, 6)]))
2303        self.assertEqual(s[0].locals, None)
2304
2305    def test_format_locals(self):
2306        def some_inner(k, v):
2307            a = 1
2308            b = 2
2309            return traceback.StackSummary.extract(
2310                traceback.walk_stack(None), capture_locals=True, limit=1)
2311        s = some_inner(3, 4)
2312        self.assertEqual(
2313            ['  File "%s", line %d, in some_inner\n'
2314             '    return traceback.StackSummary.extract(\n'
2315             '    a = 1\n'
2316             '    b = 2\n'
2317             '    k = 3\n'
2318             '    v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
2319            ], s.format())
2320
2321    def test_custom_format_frame(self):
2322        class CustomStackSummary(traceback.StackSummary):
2323            def format_frame_summary(self, frame_summary):
2324                return f'{frame_summary.filename}:{frame_summary.lineno}'
2325
2326        def some_inner():
2327            return CustomStackSummary.extract(
2328                traceback.walk_stack(None), limit=1)
2329
2330        s = some_inner()
2331        self.assertEqual(
2332            s.format(),
2333            [f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
2334
2335    def test_dropping_frames(self):
2336        def f():
2337            1/0
2338
2339        def g():
2340            try:
2341                f()
2342            except:
2343                return sys.exc_info()
2344
2345        exc_info = g()
2346
2347        class Skip_G(traceback.StackSummary):
2348            def format_frame_summary(self, frame_summary):
2349                if frame_summary.name == 'g':
2350                    return None
2351                return super().format_frame_summary(frame_summary)
2352
2353        stack = Skip_G.extract(
2354            traceback.walk_tb(exc_info[2])).format()
2355
2356        self.assertEqual(len(stack), 1)
2357        lno = f.__code__.co_firstlineno + 1
2358        self.assertEqual(
2359            stack[0],
2360            f'  File "{__file__}", line {lno}, in f\n    1/0\n'
2361        )
2362
2363
2364class TestTracebackException(unittest.TestCase):
2365
2366    def test_smoke(self):
2367        try:
2368            1/0
2369        except Exception:
2370            exc_info = sys.exc_info()
2371            exc = traceback.TracebackException(*exc_info)
2372            expected_stack = traceback.StackSummary.extract(
2373                traceback.walk_tb(exc_info[2]))
2374        self.assertEqual(None, exc.__cause__)
2375        self.assertEqual(None, exc.__context__)
2376        self.assertEqual(False, exc.__suppress_context__)
2377        self.assertEqual(expected_stack, exc.stack)
2378        self.assertEqual(exc_info[0], exc.exc_type)
2379        self.assertEqual(str(exc_info[1]), str(exc))
2380
2381    def test_from_exception(self):
2382        # Check all the parameters are accepted.
2383        def foo():
2384            1/0
2385        try:
2386            foo()
2387        except Exception as e:
2388            exc_info = sys.exc_info()
2389            self.expected_stack = traceback.StackSummary.extract(
2390                traceback.walk_tb(exc_info[2]), limit=1, lookup_lines=False,
2391                capture_locals=True)
2392            self.exc = traceback.TracebackException.from_exception(
2393                e, limit=1, lookup_lines=False, capture_locals=True)
2394        expected_stack = self.expected_stack
2395        exc = self.exc
2396        self.assertEqual(None, exc.__cause__)
2397        self.assertEqual(None, exc.__context__)
2398        self.assertEqual(False, exc.__suppress_context__)
2399        self.assertEqual(expected_stack, exc.stack)
2400        self.assertEqual(exc_info[0], exc.exc_type)
2401        self.assertEqual(str(exc_info[1]), str(exc))
2402
2403    def test_cause(self):
2404        try:
2405            try:
2406                1/0
2407            finally:
2408                exc_info_context = sys.exc_info()
2409                exc_context = traceback.TracebackException(*exc_info_context)
2410                cause = Exception("cause")
2411                raise Exception("uh oh") from cause
2412        except Exception:
2413            exc_info = sys.exc_info()
2414            exc = traceback.TracebackException(*exc_info)
2415            expected_stack = traceback.StackSummary.extract(
2416                traceback.walk_tb(exc_info[2]))
2417        exc_cause = traceback.TracebackException(Exception, cause, None)
2418        self.assertEqual(exc_cause, exc.__cause__)
2419        self.assertEqual(exc_context, exc.__context__)
2420        self.assertEqual(True, exc.__suppress_context__)
2421        self.assertEqual(expected_stack, exc.stack)
2422        self.assertEqual(exc_info[0], exc.exc_type)
2423        self.assertEqual(str(exc_info[1]), str(exc))
2424
2425    def test_context(self):
2426        try:
2427            try:
2428                1/0
2429            finally:
2430                exc_info_context = sys.exc_info()
2431                exc_context = traceback.TracebackException(*exc_info_context)
2432                raise Exception("uh oh")
2433        except Exception:
2434            exc_info = sys.exc_info()
2435            exc = traceback.TracebackException(*exc_info)
2436            expected_stack = traceback.StackSummary.extract(
2437                traceback.walk_tb(exc_info[2]))
2438        self.assertEqual(None, exc.__cause__)
2439        self.assertEqual(exc_context, exc.__context__)
2440        self.assertEqual(False, exc.__suppress_context__)
2441        self.assertEqual(expected_stack, exc.stack)
2442        self.assertEqual(exc_info[0], exc.exc_type)
2443        self.assertEqual(str(exc_info[1]), str(exc))
2444
2445    def test_long_context_chain(self):
2446        def f():
2447            try:
2448                1/0
2449            except:
2450                f()
2451
2452        try:
2453            f()
2454        except RecursionError:
2455            exc_info = sys.exc_info()
2456        else:
2457            self.fail("Exception not raised")
2458
2459        te = traceback.TracebackException(*exc_info)
2460        res = list(te.format())
2461
2462        # many ZeroDiv errors followed by the RecursionError
2463        self.assertGreater(len(res), sys.getrecursionlimit())
2464        self.assertGreater(
2465            len([l for l in res if 'ZeroDivisionError:' in l]),
2466            sys.getrecursionlimit() * 0.5)
2467        self.assertIn(
2468            "RecursionError: maximum recursion depth exceeded", res[-1])
2469
2470    def test_compact_with_cause(self):
2471        try:
2472            try:
2473                1/0
2474            finally:
2475                cause = Exception("cause")
2476                raise Exception("uh oh") from cause
2477        except Exception:
2478            exc_info = sys.exc_info()
2479            exc = traceback.TracebackException(*exc_info, compact=True)
2480            expected_stack = traceback.StackSummary.extract(
2481                traceback.walk_tb(exc_info[2]))
2482        exc_cause = traceback.TracebackException(Exception, cause, None)
2483        self.assertEqual(exc_cause, exc.__cause__)
2484        self.assertEqual(None, exc.__context__)
2485        self.assertEqual(True, exc.__suppress_context__)
2486        self.assertEqual(expected_stack, exc.stack)
2487        self.assertEqual(exc_info[0], exc.exc_type)
2488        self.assertEqual(str(exc_info[1]), str(exc))
2489
2490    def test_compact_no_cause(self):
2491        try:
2492            try:
2493                1/0
2494            finally:
2495                exc_info_context = sys.exc_info()
2496                exc_context = traceback.TracebackException(*exc_info_context)
2497                raise Exception("uh oh")
2498        except Exception:
2499            exc_info = sys.exc_info()
2500            exc = traceback.TracebackException(*exc_info, compact=True)
2501            expected_stack = traceback.StackSummary.extract(
2502                traceback.walk_tb(exc_info[2]))
2503        self.assertEqual(None, exc.__cause__)
2504        self.assertEqual(exc_context, exc.__context__)
2505        self.assertEqual(False, exc.__suppress_context__)
2506        self.assertEqual(expected_stack, exc.stack)
2507        self.assertEqual(exc_info[0], exc.exc_type)
2508        self.assertEqual(str(exc_info[1]), str(exc))
2509
2510    def test_no_refs_to_exception_and_traceback_objects(self):
2511        try:
2512            1/0
2513        except Exception:
2514            exc_info = sys.exc_info()
2515
2516        refcnt1 = sys.getrefcount(exc_info[1])
2517        refcnt2 = sys.getrefcount(exc_info[2])
2518        exc = traceback.TracebackException(*exc_info)
2519        self.assertEqual(sys.getrefcount(exc_info[1]), refcnt1)
2520        self.assertEqual(sys.getrefcount(exc_info[2]), refcnt2)
2521
2522    def test_comparison_basic(self):
2523        try:
2524            1/0
2525        except Exception:
2526            exc_info = sys.exc_info()
2527            exc = traceback.TracebackException(*exc_info)
2528            exc2 = traceback.TracebackException(*exc_info)
2529        self.assertIsNot(exc, exc2)
2530        self.assertEqual(exc, exc2)
2531        self.assertNotEqual(exc, object())
2532        self.assertEqual(exc, ALWAYS_EQ)
2533
2534    def test_comparison_params_variations(self):
2535        def raise_exc():
2536            try:
2537                raise ValueError('bad value')
2538            except:
2539                raise
2540
2541        def raise_with_locals():
2542            x, y = 1, 2
2543            raise_exc()
2544
2545        try:
2546            raise_with_locals()
2547        except Exception:
2548            exc_info = sys.exc_info()
2549
2550        exc = traceback.TracebackException(*exc_info)
2551        exc1 = traceback.TracebackException(*exc_info, limit=10)
2552        exc2 = traceback.TracebackException(*exc_info, limit=2)
2553
2554        self.assertEqual(exc, exc1)      # limit=10 gets all frames
2555        self.assertNotEqual(exc, exc2)   # limit=2 truncates the output
2556
2557        # locals change the output
2558        exc3 = traceback.TracebackException(*exc_info, capture_locals=True)
2559        self.assertNotEqual(exc, exc3)
2560
2561        # there are no locals in the innermost frame
2562        exc4 = traceback.TracebackException(*exc_info, limit=-1)
2563        exc5 = traceback.TracebackException(*exc_info, limit=-1, capture_locals=True)
2564        self.assertEqual(exc4, exc5)
2565
2566        # there are locals in the next-to-innermost frame
2567        exc6 = traceback.TracebackException(*exc_info, limit=-2)
2568        exc7 = traceback.TracebackException(*exc_info, limit=-2, capture_locals=True)
2569        self.assertNotEqual(exc6, exc7)
2570
2571    def test_comparison_equivalent_exceptions_are_equal(self):
2572        excs = []
2573        for _ in range(2):
2574            try:
2575                1/0
2576            except:
2577                excs.append(traceback.TracebackException(*sys.exc_info()))
2578        self.assertEqual(excs[0], excs[1])
2579        self.assertEqual(list(excs[0].format()), list(excs[1].format()))
2580
2581    def test_unhashable(self):
2582        class UnhashableException(Exception):
2583            def __eq__(self, other):
2584                return True
2585
2586        ex1 = UnhashableException('ex1')
2587        ex2 = UnhashableException('ex2')
2588        try:
2589            raise ex2 from ex1
2590        except UnhashableException:
2591            try:
2592                raise ex1
2593            except UnhashableException:
2594                exc_info = sys.exc_info()
2595        exc = traceback.TracebackException(*exc_info)
2596        formatted = list(exc.format())
2597        self.assertIn('UnhashableException: ex2\n', formatted[2])
2598        self.assertIn('UnhashableException: ex1\n', formatted[6])
2599
2600    def test_limit(self):
2601        def recurse(n):
2602            if n:
2603                recurse(n-1)
2604            else:
2605                1/0
2606        try:
2607            recurse(10)
2608        except Exception:
2609            exc_info = sys.exc_info()
2610            exc = traceback.TracebackException(*exc_info, limit=5)
2611            expected_stack = traceback.StackSummary.extract(
2612                traceback.walk_tb(exc_info[2]), limit=5)
2613        self.assertEqual(expected_stack, exc.stack)
2614
2615    def test_lookup_lines(self):
2616        linecache.clearcache()
2617        e = Exception("uh oh")
2618        c = test_code('/foo.py', 'method')
2619        f = test_frame(c, None, None)
2620        tb = test_tb(f, 6, None, 0)
2621        exc = traceback.TracebackException(Exception, e, tb, lookup_lines=False)
2622        self.assertEqual(linecache.cache, {})
2623        linecache.updatecache('/foo.py', globals())
2624        self.assertEqual(exc.stack[0].line, "import sys")
2625
2626    def test_locals(self):
2627        linecache.updatecache('/foo.py', globals())
2628        e = Exception("uh oh")
2629        c = test_code('/foo.py', 'method')
2630        f = test_frame(c, globals(), {'something': 1, 'other': 'string'})
2631        tb = test_tb(f, 6, None, 0)
2632        exc = traceback.TracebackException(
2633            Exception, e, tb, capture_locals=True)
2634        self.assertEqual(
2635            exc.stack[0].locals, {'something': '1', 'other': "'string'"})
2636
2637    def test_no_locals(self):
2638        linecache.updatecache('/foo.py', globals())
2639        e = Exception("uh oh")
2640        c = test_code('/foo.py', 'method')
2641        f = test_frame(c, globals(), {'something': 1})
2642        tb = test_tb(f, 6, None, 0)
2643        exc = traceback.TracebackException(Exception, e, tb)
2644        self.assertEqual(exc.stack[0].locals, None)
2645
2646    def test_traceback_header(self):
2647        # do not print a traceback header if exc_traceback is None
2648        # see issue #24695
2649        exc = traceback.TracebackException(Exception, Exception("haven"), None)
2650        self.assertEqual(list(exc.format()), ["Exception: haven\n"])
2651
2652    @requires_debug_ranges()
2653    def test_print(self):
2654        def f():
2655            x = 12
2656            try:
2657                x/0
2658            except Exception:
2659                return sys.exc_info()
2660        exc = traceback.TracebackException(*f(), capture_locals=True)
2661        output = StringIO()
2662        exc.print(file=output)
2663        self.assertEqual(
2664            output.getvalue().split('\n')[-5:],
2665            ['    x/0',
2666             '    ~^~',
2667             '    x = 12',
2668             'ZeroDivisionError: division by zero',
2669             ''])
2670
2671
2672class TestTracebackException_ExceptionGroups(unittest.TestCase):
2673    def setUp(self):
2674        super().setUp()
2675        self.eg_info = self._get_exception_group()
2676
2677    def _get_exception_group(self):
2678        def f():
2679            1/0
2680
2681        def g(v):
2682            raise ValueError(v)
2683
2684        self.lno_f = f.__code__.co_firstlineno
2685        self.lno_g = g.__code__.co_firstlineno
2686
2687        try:
2688            try:
2689                try:
2690                    f()
2691                except Exception as e:
2692                    exc1 = e
2693                try:
2694                    g(42)
2695                except Exception as e:
2696                    exc2 = e
2697                raise ExceptionGroup("eg1", [exc1, exc2])
2698            except ExceptionGroup as e:
2699                exc3 = e
2700            try:
2701                g(24)
2702            except Exception as e:
2703                exc4 = e
2704            raise ExceptionGroup("eg2", [exc3, exc4])
2705        except ExceptionGroup:
2706            return sys.exc_info()
2707        self.fail('Exception Not Raised')
2708
2709    def test_exception_group_construction(self):
2710        eg_info = self.eg_info
2711        teg1 = traceback.TracebackException(*eg_info)
2712        teg2 = traceback.TracebackException.from_exception(eg_info[1])
2713        self.assertIsNot(teg1, teg2)
2714        self.assertEqual(teg1, teg2)
2715
2716    def test_exception_group_format_exception_only(self):
2717        teg = traceback.TracebackException(*self.eg_info)
2718        formatted = ''.join(teg.format_exception_only()).split('\n')
2719        expected = "ExceptionGroup: eg2 (2 sub-exceptions)\n".split('\n')
2720
2721        self.assertEqual(formatted, expected)
2722
2723    def test_exception_group_format(self):
2724        teg = traceback.TracebackException(*self.eg_info)
2725
2726        formatted = ''.join(teg.format()).split('\n')
2727        lno_f = self.lno_f
2728        lno_g = self.lno_g
2729
2730        expected = [
2731                    f'  + Exception Group Traceback (most recent call last):',
2732                    f'  |   File "{__file__}", line {lno_g+23}, in _get_exception_group',
2733                    f'  |     raise ExceptionGroup("eg2", [exc3, exc4])',
2734                    f'  | ExceptionGroup: eg2 (2 sub-exceptions)',
2735                    f'  +-+---------------- 1 ----------------',
2736                    f'    | Exception Group Traceback (most recent call last):',
2737                    f'    |   File "{__file__}", line {lno_g+16}, in _get_exception_group',
2738                    f'    |     raise ExceptionGroup("eg1", [exc1, exc2])',
2739                    f'    | ExceptionGroup: eg1 (2 sub-exceptions)',
2740                    f'    +-+---------------- 1 ----------------',
2741                    f'      | Traceback (most recent call last):',
2742                    f'      |   File "{__file__}", line {lno_g+9}, in _get_exception_group',
2743                    f'      |     f()',
2744                    f'      |   File "{__file__}", line {lno_f+1}, in f',
2745                    f'      |     1/0',
2746                    f'      |     ~^~',
2747                    f'      | ZeroDivisionError: division by zero',
2748                    f'      +---------------- 2 ----------------',
2749                    f'      | Traceback (most recent call last):',
2750                    f'      |   File "{__file__}", line {lno_g+13}, in _get_exception_group',
2751                    f'      |     g(42)',
2752                    f'      |   File "{__file__}", line {lno_g+1}, in g',
2753                    f'      |     raise ValueError(v)',
2754                    f'      | ValueError: 42',
2755                    f'      +------------------------------------',
2756                    f'    +---------------- 2 ----------------',
2757                    f'    | Traceback (most recent call last):',
2758                    f'    |   File "{__file__}", line {lno_g+20}, in _get_exception_group',
2759                    f'    |     g(24)',
2760                    f'    |   File "{__file__}", line {lno_g+1}, in g',
2761                    f'    |     raise ValueError(v)',
2762                    f'    | ValueError: 24',
2763                    f'    +------------------------------------',
2764                    f'']
2765
2766        self.assertEqual(formatted, expected)
2767
2768    def test_max_group_width(self):
2769        excs1 = []
2770        excs2 = []
2771        for i in range(3):
2772            excs1.append(ValueError(i))
2773        for i in range(10):
2774            excs2.append(TypeError(i))
2775
2776        EG = ExceptionGroup
2777        eg = EG('eg', [EG('eg1', excs1), EG('eg2', excs2)])
2778
2779        teg = traceback.TracebackException.from_exception(eg, max_group_width=2)
2780        formatted = ''.join(teg.format()).split('\n')
2781
2782        expected = [
2783                    f'  | ExceptionGroup: eg (2 sub-exceptions)',
2784                    f'  +-+---------------- 1 ----------------',
2785                    f'    | ExceptionGroup: eg1 (3 sub-exceptions)',
2786                    f'    +-+---------------- 1 ----------------',
2787                    f'      | ValueError: 0',
2788                    f'      +---------------- 2 ----------------',
2789                    f'      | ValueError: 1',
2790                    f'      +---------------- ... ----------------',
2791                    f'      | and 1 more exception',
2792                    f'      +------------------------------------',
2793                    f'    +---------------- 2 ----------------',
2794                    f'    | ExceptionGroup: eg2 (10 sub-exceptions)',
2795                    f'    +-+---------------- 1 ----------------',
2796                    f'      | TypeError: 0',
2797                    f'      +---------------- 2 ----------------',
2798                    f'      | TypeError: 1',
2799                    f'      +---------------- ... ----------------',
2800                    f'      | and 8 more exceptions',
2801                    f'      +------------------------------------',
2802                    f'']
2803
2804        self.assertEqual(formatted, expected)
2805
2806    def test_max_group_depth(self):
2807        exc = TypeError('bad type')
2808        for i in range(3):
2809            exc = ExceptionGroup('exc', [ValueError(-i), exc, ValueError(i)])
2810
2811        teg = traceback.TracebackException.from_exception(exc, max_group_depth=2)
2812        formatted = ''.join(teg.format()).split('\n')
2813
2814        expected = [
2815                    f'  | ExceptionGroup: exc (3 sub-exceptions)',
2816                    f'  +-+---------------- 1 ----------------',
2817                    f'    | ValueError: -2',
2818                    f'    +---------------- 2 ----------------',
2819                    f'    | ExceptionGroup: exc (3 sub-exceptions)',
2820                    f'    +-+---------------- 1 ----------------',
2821                    f'      | ValueError: -1',
2822                    f'      +---------------- 2 ----------------',
2823                    f'      | ... (max_group_depth is 2)',
2824                    f'      +---------------- 3 ----------------',
2825                    f'      | ValueError: 1',
2826                    f'      +------------------------------------',
2827                    f'    +---------------- 3 ----------------',
2828                    f'    | ValueError: 2',
2829                    f'    +------------------------------------',
2830                    f'']
2831
2832        self.assertEqual(formatted, expected)
2833
2834    def test_comparison(self):
2835        try:
2836            raise self.eg_info[1]
2837        except ExceptionGroup:
2838            exc_info = sys.exc_info()
2839        for _ in range(5):
2840            try:
2841                raise exc_info[1]
2842            except:
2843                exc_info = sys.exc_info()
2844        exc = traceback.TracebackException(*exc_info)
2845        exc2 = traceback.TracebackException(*exc_info)
2846        exc3 = traceback.TracebackException(*exc_info, limit=300)
2847        ne = traceback.TracebackException(*exc_info, limit=3)
2848        self.assertIsNot(exc, exc2)
2849        self.assertEqual(exc, exc2)
2850        self.assertEqual(exc, exc3)
2851        self.assertNotEqual(exc, ne)
2852        self.assertNotEqual(exc, object())
2853        self.assertEqual(exc, ALWAYS_EQ)
2854
2855
2856class MiscTest(unittest.TestCase):
2857
2858    def test_all(self):
2859        expected = set()
2860        denylist = {'print_list'}
2861        for name in dir(traceback):
2862            if name.startswith('_') or name in denylist:
2863                continue
2864            module_object = getattr(traceback, name)
2865            if getattr(module_object, '__module__', None) == 'traceback':
2866                expected.add(name)
2867        self.assertCountEqual(traceback.__all__, expected)
2868
2869
2870if __name__ == "__main__":
2871    unittest.main()
2872