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