• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Test cases for traceback module"""
2
3from collections import namedtuple
4from io import StringIO
5import linecache
6import sys
7import unittest
8import re
9from test import support
10from test.support import TESTFN, Error, captured_output, unlink, cpython_only, ALWAYS_EQ
11from test.support.script_helper import assert_python_ok
12import textwrap
13
14import traceback
15
16
17test_code = namedtuple('code', ['co_filename', 'co_name'])
18test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
19test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next'])
20
21
22class TracebackCases(unittest.TestCase):
23    # For now, a very minimal set of tests.  I want to be sure that
24    # formatting of SyntaxErrors works based on changes for 2.1.
25
26    def get_exception_format(self, func, exc):
27        try:
28            func()
29        except exc as value:
30            return traceback.format_exception_only(exc, value)
31        else:
32            raise ValueError("call did not raise exception")
33
34    def syntax_error_with_caret(self):
35        compile("def fact(x):\n\treturn x!\n", "?", "exec")
36
37    def syntax_error_with_caret_2(self):
38        compile("1 +\n", "?", "exec")
39
40    def syntax_error_bad_indentation(self):
41        compile("def spam():\n  print(1)\n print(2)", "?", "exec")
42
43    def syntax_error_with_caret_non_ascii(self):
44        compile('Python = "\u1e54\xfd\u0163\u0125\xf2\xf1" +', "?", "exec")
45
46    def syntax_error_bad_indentation2(self):
47        compile(" print(2)", "?", "exec")
48
49    def test_caret(self):
50        err = self.get_exception_format(self.syntax_error_with_caret,
51                                        SyntaxError)
52        self.assertEqual(len(err), 4)
53        self.assertTrue(err[1].strip() == "return x!")
54        self.assertIn("^", err[2]) # third line has caret
55        self.assertEqual(err[1].find("!"), err[2].find("^")) # in the right place
56
57        err = self.get_exception_format(self.syntax_error_with_caret_2,
58                                        SyntaxError)
59        self.assertIn("^", err[2]) # third line has caret
60        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
61        self.assertEqual(err[1].find("+") + 1, err[2].find("^"))  # in the right place
62
63        err = self.get_exception_format(self.syntax_error_with_caret_non_ascii,
64                                        SyntaxError)
65        self.assertIn("^", err[2]) # third line has caret
66        self.assertEqual(err[2].count('\n'), 1)   # and no additional newline
67        self.assertEqual(err[1].find("+") + 1, err[2].find("^"))  # in the right place
68
69    def test_nocaret(self):
70        exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
71        err = traceback.format_exception_only(SyntaxError, exc)
72        self.assertEqual(len(err), 3)
73        self.assertEqual(err[1].strip(), "bad syntax")
74
75    def test_bad_indentation(self):
76        err = self.get_exception_format(self.syntax_error_bad_indentation,
77                                        IndentationError)
78        self.assertEqual(len(err), 4)
79        self.assertEqual(err[1].strip(), "print(2)")
80        self.assertIn("^", err[2])
81        self.assertEqual(err[1].find(")") + 1, err[2].find("^"))
82
83        # No caret for "unexpected indent"
84        err = self.get_exception_format(self.syntax_error_bad_indentation2,
85                                        IndentationError)
86        self.assertEqual(len(err), 3)
87        self.assertEqual(err[1].strip(), "print(2)")
88
89    def test_base_exception(self):
90        # Test that exceptions derived from BaseException are formatted right
91        e = KeyboardInterrupt()
92        lst = traceback.format_exception_only(e.__class__, e)
93        self.assertEqual(lst, ['KeyboardInterrupt\n'])
94
95    def test_format_exception_only_bad__str__(self):
96        class X(Exception):
97            def __str__(self):
98                1/0
99        err = traceback.format_exception_only(X, X())
100        self.assertEqual(len(err), 1)
101        str_value = '<unprintable %s object>' % X.__name__
102        if X.__module__ in ('__main__', 'builtins'):
103            str_name = X.__qualname__
104        else:
105            str_name = '.'.join([X.__module__, X.__qualname__])
106        self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
107
108    def test_encoded_file(self):
109        # Test that tracebacks are correctly printed for encoded source files:
110        # - correct line number (Issue2384)
111        # - respect file encoding (Issue3975)
112        import sys, subprocess
113
114        # The spawned subprocess has its stdout redirected to a PIPE, and its
115        # encoding may be different from the current interpreter, on Windows
116        # at least.
117        process = subprocess.Popen([sys.executable, "-c",
118                                    "import sys; print(sys.stdout.encoding)"],
119                                   stdout=subprocess.PIPE,
120                                   stderr=subprocess.STDOUT)
121        stdout, stderr = process.communicate()
122        output_encoding = str(stdout, 'ascii').splitlines()[0]
123
124        def do_test(firstlines, message, charset, lineno):
125            # Raise the message in a subprocess, and catch the output
126            try:
127                with open(TESTFN, "w", encoding=charset) as output:
128                    output.write("""{0}if 1:
129                        import traceback;
130                        raise RuntimeError('{1}')
131                        """.format(firstlines, message))
132
133                process = subprocess.Popen([sys.executable, TESTFN],
134                    stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
135                stdout, stderr = process.communicate()
136                stdout = stdout.decode(output_encoding).splitlines()
137            finally:
138                unlink(TESTFN)
139
140            # The source lines are encoded with the 'backslashreplace' handler
141            encoded_message = message.encode(output_encoding,
142                                             'backslashreplace')
143            # and we just decoded them with the output_encoding.
144            message_ascii = encoded_message.decode(output_encoding)
145
146            err_line = "raise RuntimeError('{0}')".format(message_ascii)
147            err_msg = "RuntimeError: {0}".format(message_ascii)
148
149            self.assertIn(("line %s" % lineno), stdout[1],
150                "Invalid line number: {0!r} instead of {1}".format(
151                    stdout[1], lineno))
152            self.assertTrue(stdout[2].endswith(err_line),
153                "Invalid traceback line: {0!r} instead of {1!r}".format(
154                    stdout[2], err_line))
155            self.assertTrue(stdout[3] == err_msg,
156                "Invalid error message: {0!r} instead of {1!r}".format(
157                    stdout[3], err_msg))
158
159        do_test("", "foo", "ascii", 3)
160        for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
161            if charset == "ascii":
162                text = "foo"
163            elif charset == "GBK":
164                text = "\u4E02\u5100"
165            else:
166                text = "h\xe9 ho"
167            do_test("# coding: {0}\n".format(charset),
168                    text, charset, 4)
169            do_test("#!shebang\n# coding: {0}\n".format(charset),
170                    text, charset, 5)
171            do_test(" \t\f\n# coding: {0}\n".format(charset),
172                    text, charset, 5)
173        # Issue #18960: coding spec should have no effect
174        do_test("x=0\n# coding: GBK\n", "h\xe9 ho", 'utf-8', 5)
175
176    def test_print_traceback_at_exit(self):
177        # Issue #22599: Ensure that it is possible to use the traceback module
178        # to display an exception at Python exit
179        code = textwrap.dedent("""
180            import sys
181            import traceback
182
183            class PrintExceptionAtExit(object):
184                def __init__(self):
185                    try:
186                        x = 1 / 0
187                    except Exception:
188                        self.exc_info = sys.exc_info()
189                        # self.exc_info[1] (traceback) contains frames:
190                        # explicitly clear the reference to self in the current
191                        # frame to break a reference cycle
192                        self = None
193
194                def __del__(self):
195                    traceback.print_exception(*self.exc_info)
196
197            # Keep a reference in the module namespace to call the destructor
198            # when the module is unloaded
199            obj = PrintExceptionAtExit()
200        """)
201        rc, stdout, stderr = assert_python_ok('-c', code)
202        expected = [b'Traceback (most recent call last):',
203                    b'  File "<string>", line 8, in __init__',
204                    b'ZeroDivisionError: division by zero']
205        self.assertEqual(stderr.splitlines(), expected)
206
207    def test_print_exception(self):
208        output = StringIO()
209        traceback.print_exception(
210            Exception, Exception("projector"), None, file=output
211        )
212        self.assertEqual(output.getvalue(), "Exception: projector\n")
213
214
215class TracebackFormatTests(unittest.TestCase):
216
217    def some_exception(self):
218        raise KeyError('blah')
219
220    @cpython_only
221    def check_traceback_format(self, cleanup_func=None):
222        from _testcapi import traceback_print
223        try:
224            self.some_exception()
225        except KeyError:
226            type_, value, tb = sys.exc_info()
227            if cleanup_func is not None:
228                # Clear the inner frames, not this one
229                cleanup_func(tb.tb_next)
230            traceback_fmt = 'Traceback (most recent call last):\n' + \
231                            ''.join(traceback.format_tb(tb))
232            file_ = StringIO()
233            traceback_print(tb, file_)
234            python_fmt  = file_.getvalue()
235            # Call all _tb and _exc functions
236            with captured_output("stderr") as tbstderr:
237                traceback.print_tb(tb)
238            tbfile = StringIO()
239            traceback.print_tb(tb, file=tbfile)
240            with captured_output("stderr") as excstderr:
241                traceback.print_exc()
242            excfmt = traceback.format_exc()
243            excfile = StringIO()
244            traceback.print_exc(file=excfile)
245        else:
246            raise Error("unable to create test traceback string")
247
248        # Make sure that Python and the traceback module format the same thing
249        self.assertEqual(traceback_fmt, python_fmt)
250        # Now verify the _tb func output
251        self.assertEqual(tbstderr.getvalue(), tbfile.getvalue())
252        # Now verify the _exc func output
253        self.assertEqual(excstderr.getvalue(), excfile.getvalue())
254        self.assertEqual(excfmt, excfile.getvalue())
255
256        # Make sure that the traceback is properly indented.
257        tb_lines = python_fmt.splitlines()
258        self.assertEqual(len(tb_lines), 5)
259        banner = tb_lines[0]
260        location, source_line = tb_lines[-2:]
261        self.assertTrue(banner.startswith('Traceback'))
262        self.assertTrue(location.startswith('  File'))
263        self.assertTrue(source_line.startswith('    raise'))
264
265    def test_traceback_format(self):
266        self.check_traceback_format()
267
268    def test_traceback_format_with_cleared_frames(self):
269        # Check that traceback formatting also works with a clear()ed frame
270        def cleanup_tb(tb):
271            tb.tb_frame.clear()
272        self.check_traceback_format(cleanup_tb)
273
274    def test_stack_format(self):
275        # Verify _stack functions. Note we have to use _getframe(1) to
276        # compare them without this frame appearing in the output
277        with captured_output("stderr") as ststderr:
278            traceback.print_stack(sys._getframe(1))
279        stfile = StringIO()
280        traceback.print_stack(sys._getframe(1), file=stfile)
281        self.assertEqual(ststderr.getvalue(), stfile.getvalue())
282
283        stfmt = traceback.format_stack(sys._getframe(1))
284
285        self.assertEqual(ststderr.getvalue(), "".join(stfmt))
286
287    def test_print_stack(self):
288        def prn():
289            traceback.print_stack()
290        with captured_output("stderr") as stderr:
291            prn()
292        lineno = prn.__code__.co_firstlineno
293        self.assertEqual(stderr.getvalue().splitlines()[-4:], [
294            '  File "%s", line %d, in test_print_stack' % (__file__, lineno+3),
295            '    prn()',
296            '  File "%s", line %d, in prn' % (__file__, lineno+1),
297            '    traceback.print_stack()',
298        ])
299
300    # issue 26823 - Shrink recursive tracebacks
301    def _check_recursive_traceback_display(self, render_exc):
302        # Always show full diffs when this test fails
303        # Note that rearranging things may require adjusting
304        # the relative line numbers in the expected tracebacks
305        self.maxDiff = None
306
307        # Check hitting the recursion limit
308        def f():
309            f()
310
311        with captured_output("stderr") as stderr_f:
312            try:
313                f()
314            except RecursionError:
315                render_exc()
316            else:
317                self.fail("no recursion occurred")
318
319        lineno_f = f.__code__.co_firstlineno
320        result_f = (
321            'Traceback (most recent call last):\n'
322            f'  File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
323            '    f()\n'
324            f'  File "{__file__}", line {lineno_f+1}, in f\n'
325            '    f()\n'
326            f'  File "{__file__}", line {lineno_f+1}, in f\n'
327            '    f()\n'
328            f'  File "{__file__}", line {lineno_f+1}, in f\n'
329            '    f()\n'
330            # XXX: The following line changes depending on whether the tests
331            # are run through the interactive interpreter or with -m
332            # It also varies depending on the platform (stack size)
333            # Fortunately, we don't care about exactness here, so we use regex
334            r'  \[Previous line repeated (\d+) more times\]' '\n'
335            'RecursionError: maximum recursion depth exceeded\n'
336        )
337
338        expected = result_f.splitlines()
339        actual = stderr_f.getvalue().splitlines()
340
341        # Check the output text matches expectations
342        # 2nd last line contains the repetition count
343        self.assertEqual(actual[:-2], expected[:-2])
344        self.assertRegex(actual[-2], expected[-2])
345        # last line can have additional text appended
346        self.assertIn(expected[-1], actual[-1])
347
348        # Check the recursion count is roughly as expected
349        rec_limit = sys.getrecursionlimit()
350        self.assertIn(int(re.search(r"\d+", actual[-2]).group()), range(rec_limit-60, rec_limit))
351
352        # Check a known (limited) number of recursive invocations
353        def g(count=10):
354            if count:
355                return g(count-1)
356            raise ValueError
357
358        with captured_output("stderr") as stderr_g:
359            try:
360                g()
361            except ValueError:
362                render_exc()
363            else:
364                self.fail("no value error was raised")
365
366        lineno_g = g.__code__.co_firstlineno
367        result_g = (
368            f'  File "{__file__}", line {lineno_g+2}, in g\n'
369            '    return g(count-1)\n'
370            f'  File "{__file__}", line {lineno_g+2}, in g\n'
371            '    return g(count-1)\n'
372            f'  File "{__file__}", line {lineno_g+2}, in g\n'
373            '    return g(count-1)\n'
374            '  [Previous line repeated 7 more times]\n'
375            f'  File "{__file__}", line {lineno_g+3}, in g\n'
376            '    raise ValueError\n'
377            'ValueError\n'
378        )
379        tb_line = (
380            'Traceback (most recent call last):\n'
381            f'  File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
382            '    g()\n'
383        )
384        expected = (tb_line + result_g).splitlines()
385        actual = stderr_g.getvalue().splitlines()
386        self.assertEqual(actual, expected)
387
388        # Check 2 different repetitive sections
389        def h(count=10):
390            if count:
391                return h(count-1)
392            g()
393
394        with captured_output("stderr") as stderr_h:
395            try:
396                h()
397            except ValueError:
398                render_exc()
399            else:
400                self.fail("no value error was raised")
401
402        lineno_h = h.__code__.co_firstlineno
403        result_h = (
404            'Traceback (most recent call last):\n'
405            f'  File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
406            '    h()\n'
407            f'  File "{__file__}", line {lineno_h+2}, in h\n'
408            '    return h(count-1)\n'
409            f'  File "{__file__}", line {lineno_h+2}, in h\n'
410            '    return h(count-1)\n'
411            f'  File "{__file__}", line {lineno_h+2}, in h\n'
412            '    return h(count-1)\n'
413            '  [Previous line repeated 7 more times]\n'
414            f'  File "{__file__}", line {lineno_h+3}, in h\n'
415            '    g()\n'
416        )
417        expected = (result_h + result_g).splitlines()
418        actual = stderr_h.getvalue().splitlines()
419        self.assertEqual(actual, expected)
420
421        # Check the boundary conditions. First, test just below the cutoff.
422        with captured_output("stderr") as stderr_g:
423            try:
424                g(traceback._RECURSIVE_CUTOFF)
425            except ValueError:
426                render_exc()
427            else:
428                self.fail("no error raised")
429        result_g = (
430            f'  File "{__file__}", line {lineno_g+2}, in g\n'
431            '    return g(count-1)\n'
432            f'  File "{__file__}", line {lineno_g+2}, in g\n'
433            '    return g(count-1)\n'
434            f'  File "{__file__}", line {lineno_g+2}, in g\n'
435            '    return g(count-1)\n'
436            f'  File "{__file__}", line {lineno_g+3}, in g\n'
437            '    raise ValueError\n'
438            'ValueError\n'
439        )
440        tb_line = (
441            'Traceback (most recent call last):\n'
442            f'  File "{__file__}", line {lineno_g+71}, in _check_recursive_traceback_display\n'
443            '    g(traceback._RECURSIVE_CUTOFF)\n'
444        )
445        expected = (tb_line + result_g).splitlines()
446        actual = stderr_g.getvalue().splitlines()
447        self.assertEqual(actual, expected)
448
449        # Second, test just above the cutoff.
450        with captured_output("stderr") as stderr_g:
451            try:
452                g(traceback._RECURSIVE_CUTOFF + 1)
453            except ValueError:
454                render_exc()
455            else:
456                self.fail("no error raised")
457        result_g = (
458            f'  File "{__file__}", line {lineno_g+2}, in g\n'
459            '    return g(count-1)\n'
460            f'  File "{__file__}", line {lineno_g+2}, in g\n'
461            '    return g(count-1)\n'
462            f'  File "{__file__}", line {lineno_g+2}, in g\n'
463            '    return g(count-1)\n'
464            '  [Previous line repeated 1 more time]\n'
465            f'  File "{__file__}", line {lineno_g+3}, in g\n'
466            '    raise ValueError\n'
467            'ValueError\n'
468        )
469        tb_line = (
470            'Traceback (most recent call last):\n'
471            f'  File "{__file__}", line {lineno_g+99}, in _check_recursive_traceback_display\n'
472            '    g(traceback._RECURSIVE_CUTOFF + 1)\n'
473        )
474        expected = (tb_line + result_g).splitlines()
475        actual = stderr_g.getvalue().splitlines()
476        self.assertEqual(actual, expected)
477
478    def test_recursive_traceback_python(self):
479        self._check_recursive_traceback_display(traceback.print_exc)
480
481    @cpython_only
482    def test_recursive_traceback_cpython_internal(self):
483        from _testcapi import exception_print
484        def render_exc():
485            exc_type, exc_value, exc_tb = sys.exc_info()
486            exception_print(exc_value)
487        self._check_recursive_traceback_display(render_exc)
488
489    def test_format_stack(self):
490        def fmt():
491            return traceback.format_stack()
492        result = fmt()
493        lineno = fmt.__code__.co_firstlineno
494        self.assertEqual(result[-2:], [
495            '  File "%s", line %d, in test_format_stack\n'
496            '    result = fmt()\n' % (__file__, lineno+2),
497            '  File "%s", line %d, in fmt\n'
498            '    return traceback.format_stack()\n' % (__file__, lineno+1),
499        ])
500
501    @cpython_only
502    def test_unhashable(self):
503        from _testcapi import exception_print
504
505        class UnhashableException(Exception):
506            def __eq__(self, other):
507                return True
508
509        ex1 = UnhashableException('ex1')
510        ex2 = UnhashableException('ex2')
511        try:
512            raise ex2 from ex1
513        except UnhashableException:
514            try:
515                raise ex1
516            except UnhashableException:
517                exc_type, exc_val, exc_tb = sys.exc_info()
518
519        with captured_output("stderr") as stderr_f:
520            exception_print(exc_val)
521
522        tb = stderr_f.getvalue().strip().splitlines()
523        self.assertEqual(11, len(tb))
524        self.assertEqual(context_message.strip(), tb[5])
525        self.assertIn('UnhashableException: ex2', tb[3])
526        self.assertIn('UnhashableException: ex1', tb[10])
527
528
529cause_message = (
530    "\nThe above exception was the direct cause "
531    "of the following exception:\n\n")
532
533context_message = (
534    "\nDuring handling of the above exception, "
535    "another exception occurred:\n\n")
536
537boundaries = re.compile(
538    '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
539
540
541class BaseExceptionReportingTests:
542
543    def get_exception(self, exception_or_callable):
544        if isinstance(exception_or_callable, Exception):
545            return exception_or_callable
546        try:
547            exception_or_callable()
548        except Exception as e:
549            return e
550
551    def zero_div(self):
552        1/0 # In zero_div
553
554    def check_zero_div(self, msg):
555        lines = msg.splitlines()
556        self.assertTrue(lines[-3].startswith('  File'))
557        self.assertIn('1/0 # In zero_div', lines[-2])
558        self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
559
560    def test_simple(self):
561        try:
562            1/0 # Marker
563        except ZeroDivisionError as _:
564            e = _
565        lines = self.get_report(e).splitlines()
566        self.assertEqual(len(lines), 4)
567        self.assertTrue(lines[0].startswith('Traceback'))
568        self.assertTrue(lines[1].startswith('  File'))
569        self.assertIn('1/0 # Marker', lines[2])
570        self.assertTrue(lines[3].startswith('ZeroDivisionError'))
571
572    def test_cause(self):
573        def inner_raise():
574            try:
575                self.zero_div()
576            except ZeroDivisionError as e:
577                raise KeyError from e
578        def outer_raise():
579            inner_raise() # Marker
580        blocks = boundaries.split(self.get_report(outer_raise))
581        self.assertEqual(len(blocks), 3)
582        self.assertEqual(blocks[1], cause_message)
583        self.check_zero_div(blocks[0])
584        self.assertIn('inner_raise() # Marker', blocks[2])
585
586    def test_context(self):
587        def inner_raise():
588            try:
589                self.zero_div()
590            except ZeroDivisionError:
591                raise KeyError
592        def outer_raise():
593            inner_raise() # Marker
594        blocks = boundaries.split(self.get_report(outer_raise))
595        self.assertEqual(len(blocks), 3)
596        self.assertEqual(blocks[1], context_message)
597        self.check_zero_div(blocks[0])
598        self.assertIn('inner_raise() # Marker', blocks[2])
599
600    def test_context_suppression(self):
601        try:
602            try:
603                raise Exception
604            except:
605                raise ZeroDivisionError from None
606        except ZeroDivisionError as _:
607            e = _
608        lines = self.get_report(e).splitlines()
609        self.assertEqual(len(lines), 4)
610        self.assertTrue(lines[0].startswith('Traceback'))
611        self.assertTrue(lines[1].startswith('  File'))
612        self.assertIn('ZeroDivisionError from None', lines[2])
613        self.assertTrue(lines[3].startswith('ZeroDivisionError'))
614
615    def test_cause_and_context(self):
616        # When both a cause and a context are set, only the cause should be
617        # displayed and the context should be muted.
618        def inner_raise():
619            try:
620                self.zero_div()
621            except ZeroDivisionError as _e:
622                e = _e
623            try:
624                xyzzy
625            except NameError:
626                raise KeyError from e
627        def outer_raise():
628            inner_raise() # Marker
629        blocks = boundaries.split(self.get_report(outer_raise))
630        self.assertEqual(len(blocks), 3)
631        self.assertEqual(blocks[1], cause_message)
632        self.check_zero_div(blocks[0])
633        self.assertIn('inner_raise() # Marker', blocks[2])
634
635    def test_cause_recursive(self):
636        def inner_raise():
637            try:
638                try:
639                    self.zero_div()
640                except ZeroDivisionError as e:
641                    z = e
642                    raise KeyError from e
643            except KeyError as e:
644                raise z from e
645        def outer_raise():
646            inner_raise() # Marker
647        blocks = boundaries.split(self.get_report(outer_raise))
648        self.assertEqual(len(blocks), 3)
649        self.assertEqual(blocks[1], cause_message)
650        # The first block is the KeyError raised from the ZeroDivisionError
651        self.assertIn('raise KeyError from e', blocks[0])
652        self.assertNotIn('1/0', blocks[0])
653        # The second block (apart from the boundary) is the ZeroDivisionError
654        # re-raised from the KeyError
655        self.assertIn('inner_raise() # Marker', blocks[2])
656        self.check_zero_div(blocks[2])
657
658    @unittest.skipIf(support.use_old_parser(), "Pegen is arguably better here, so no need to fix this")
659    def test_syntax_error_offset_at_eol(self):
660        # See #10186.
661        def e():
662            raise SyntaxError('', ('', 0, 5, 'hello'))
663        msg = self.get_report(e).splitlines()
664        self.assertEqual(msg[-2], "        ^")
665        def e():
666            exec("x = 5 | 4 |")
667        msg = self.get_report(e).splitlines()
668        self.assertEqual(msg[-2], '               ^')
669
670    def test_message_none(self):
671        # A message that looks like "None" should not be treated specially
672        err = self.get_report(Exception(None))
673        self.assertIn('Exception: None\n', err)
674        err = self.get_report(Exception('None'))
675        self.assertIn('Exception: None\n', err)
676        err = self.get_report(Exception())
677        self.assertIn('Exception\n', err)
678        err = self.get_report(Exception(''))
679        self.assertIn('Exception\n', err)
680
681    def test_syntax_error_various_offsets(self):
682        for offset in range(-5, 10):
683            for add in [0, 2]:
684                text = " "*add + "text%d" % offset
685                expected = ['  File "file.py", line 1']
686                if offset < 1:
687                    expected.append("    %s" % text.lstrip())
688                elif offset <= 6:
689                    expected.append("    %s" % text.lstrip())
690                    expected.append("    %s^" % (" "*(offset-1)))
691                else:
692                    expected.append("    %s" % text.lstrip())
693                    expected.append("    %s^" % (" "*5))
694                expected.append("SyntaxError: msg")
695                expected.append("")
696                err = self.get_report(SyntaxError("msg", ("file.py", 1, offset+add, text)))
697                exp = "\n".join(expected)
698                self.assertEqual(exp, err)
699
700
701class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
702    #
703    # This checks reporting through the 'traceback' module, with both
704    # format_exception() and print_exception().
705    #
706
707    def get_report(self, e):
708        e = self.get_exception(e)
709        s = ''.join(
710            traceback.format_exception(type(e), e, e.__traceback__))
711        with captured_output("stderr") as sio:
712            traceback.print_exception(type(e), e, e.__traceback__)
713        self.assertEqual(sio.getvalue(), s)
714        return s
715
716
717class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
718    #
719    # This checks built-in reporting by the interpreter.
720    #
721
722    @cpython_only
723    def get_report(self, e):
724        from _testcapi import exception_print
725        e = self.get_exception(e)
726        with captured_output("stderr") as s:
727            exception_print(e)
728        return s.getvalue()
729
730
731class LimitTests(unittest.TestCase):
732
733    ''' Tests for limit argument.
734        It's enough to test extact_tb, extract_stack and format_exception '''
735
736    def last_raises1(self):
737        raise Exception('Last raised')
738
739    def last_raises2(self):
740        self.last_raises1()
741
742    def last_raises3(self):
743        self.last_raises2()
744
745    def last_raises4(self):
746        self.last_raises3()
747
748    def last_raises5(self):
749        self.last_raises4()
750
751    def last_returns_frame1(self):
752        return sys._getframe()
753
754    def last_returns_frame2(self):
755        return self.last_returns_frame1()
756
757    def last_returns_frame3(self):
758        return self.last_returns_frame2()
759
760    def last_returns_frame4(self):
761        return self.last_returns_frame3()
762
763    def last_returns_frame5(self):
764        return self.last_returns_frame4()
765
766    def test_extract_stack(self):
767        frame = self.last_returns_frame5()
768        def extract(**kwargs):
769            return traceback.extract_stack(frame, **kwargs)
770        def assertEqualExcept(actual, expected, ignore):
771            self.assertEqual(actual[:ignore], expected[:ignore])
772            self.assertEqual(actual[ignore+1:], expected[ignore+1:])
773            self.assertEqual(len(actual), len(expected))
774
775        with support.swap_attr(sys, 'tracebacklimit', 1000):
776            nolim = extract()
777            self.assertGreater(len(nolim), 5)
778            self.assertEqual(extract(limit=2), nolim[-2:])
779            assertEqualExcept(extract(limit=100), nolim[-100:], -5-1)
780            self.assertEqual(extract(limit=-2), nolim[:2])
781            assertEqualExcept(extract(limit=-100), nolim[:100], len(nolim)-5-1)
782            self.assertEqual(extract(limit=0), [])
783            del sys.tracebacklimit
784            assertEqualExcept(extract(), nolim, -5-1)
785            sys.tracebacklimit = 2
786            self.assertEqual(extract(), nolim[-2:])
787            self.assertEqual(extract(limit=3), nolim[-3:])
788            self.assertEqual(extract(limit=-3), nolim[:3])
789            sys.tracebacklimit = 0
790            self.assertEqual(extract(), [])
791            sys.tracebacklimit = -1
792            self.assertEqual(extract(), [])
793
794    def test_extract_tb(self):
795        try:
796            self.last_raises5()
797        except Exception:
798            exc_type, exc_value, tb = sys.exc_info()
799        def extract(**kwargs):
800            return traceback.extract_tb(tb, **kwargs)
801
802        with support.swap_attr(sys, 'tracebacklimit', 1000):
803            nolim = extract()
804            self.assertEqual(len(nolim), 5+1)
805            self.assertEqual(extract(limit=2), nolim[:2])
806            self.assertEqual(extract(limit=10), nolim)
807            self.assertEqual(extract(limit=-2), nolim[-2:])
808            self.assertEqual(extract(limit=-10), nolim)
809            self.assertEqual(extract(limit=0), [])
810            del sys.tracebacklimit
811            self.assertEqual(extract(), nolim)
812            sys.tracebacklimit = 2
813            self.assertEqual(extract(), nolim[:2])
814            self.assertEqual(extract(limit=3), nolim[:3])
815            self.assertEqual(extract(limit=-3), nolim[-3:])
816            sys.tracebacklimit = 0
817            self.assertEqual(extract(), [])
818            sys.tracebacklimit = -1
819            self.assertEqual(extract(), [])
820
821    def test_format_exception(self):
822        try:
823            self.last_raises5()
824        except Exception:
825            exc_type, exc_value, tb = sys.exc_info()
826        # [1:-1] to exclude "Traceback (...)" header and
827        # exception type and value
828        def extract(**kwargs):
829            return traceback.format_exception(exc_type, exc_value, tb, **kwargs)[1:-1]
830
831        with support.swap_attr(sys, 'tracebacklimit', 1000):
832            nolim = extract()
833            self.assertEqual(len(nolim), 5+1)
834            self.assertEqual(extract(limit=2), nolim[:2])
835            self.assertEqual(extract(limit=10), nolim)
836            self.assertEqual(extract(limit=-2), nolim[-2:])
837            self.assertEqual(extract(limit=-10), nolim)
838            self.assertEqual(extract(limit=0), [])
839            del sys.tracebacklimit
840            self.assertEqual(extract(), nolim)
841            sys.tracebacklimit = 2
842            self.assertEqual(extract(), nolim[:2])
843            self.assertEqual(extract(limit=3), nolim[:3])
844            self.assertEqual(extract(limit=-3), nolim[-3:])
845            sys.tracebacklimit = 0
846            self.assertEqual(extract(), [])
847            sys.tracebacklimit = -1
848            self.assertEqual(extract(), [])
849
850
851class MiscTracebackCases(unittest.TestCase):
852    #
853    # Check non-printing functions in traceback module
854    #
855
856    def test_clear(self):
857        def outer():
858            middle()
859        def middle():
860            inner()
861        def inner():
862            i = 1
863            1/0
864
865        try:
866            outer()
867        except:
868            type_, value, tb = sys.exc_info()
869
870        # Initial assertion: there's one local in the inner frame.
871        inner_frame = tb.tb_next.tb_next.tb_next.tb_frame
872        self.assertEqual(len(inner_frame.f_locals), 1)
873
874        # Clear traceback frames
875        traceback.clear_frames(tb)
876
877        # Local variable dict should now be empty.
878        self.assertEqual(len(inner_frame.f_locals), 0)
879
880    def test_extract_stack(self):
881        def extract():
882            return traceback.extract_stack()
883        result = extract()
884        lineno = extract.__code__.co_firstlineno
885        self.assertEqual(result[-2:], [
886            (__file__, lineno+2, 'test_extract_stack', 'result = extract()'),
887            (__file__, lineno+1, 'extract', 'return traceback.extract_stack()'),
888            ])
889        self.assertEqual(len(result[0]), 4)
890
891
892class TestFrame(unittest.TestCase):
893
894    def test_basics(self):
895        linecache.clearcache()
896        linecache.lazycache("f", globals())
897        f = traceback.FrameSummary("f", 1, "dummy")
898        self.assertEqual(f,
899            ("f", 1, "dummy", '"""Test cases for traceback module"""'))
900        self.assertEqual(tuple(f),
901            ("f", 1, "dummy", '"""Test cases for traceback module"""'))
902        self.assertEqual(f, traceback.FrameSummary("f", 1, "dummy"))
903        self.assertEqual(f, tuple(f))
904        # Since tuple.__eq__ doesn't support FrameSummary, the equality
905        # operator fallbacks to FrameSummary.__eq__.
906        self.assertEqual(tuple(f), f)
907        self.assertIsNone(f.locals)
908        self.assertNotEqual(f, object())
909        self.assertEqual(f, ALWAYS_EQ)
910
911    def test_lazy_lines(self):
912        linecache.clearcache()
913        f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
914        self.assertEqual(None, f._line)
915        linecache.lazycache("f", globals())
916        self.assertEqual(
917            '"""Test cases for traceback module"""',
918            f.line)
919
920    def test_explicit_line(self):
921        f = traceback.FrameSummary("f", 1, "dummy", line="line")
922        self.assertEqual("line", f.line)
923
924    def test_len(self):
925        f = traceback.FrameSummary("f", 1, "dummy", line="line")
926        self.assertEqual(len(f), 4)
927
928
929class TestStack(unittest.TestCase):
930
931    def test_walk_stack(self):
932        def deeper():
933            return list(traceback.walk_stack(None))
934        s1 = list(traceback.walk_stack(None))
935        s2 = deeper()
936        self.assertEqual(len(s2) - len(s1), 1)
937        self.assertEqual(s2[1:], s1)
938
939    def test_walk_tb(self):
940        try:
941            1/0
942        except Exception:
943            _, _, tb = sys.exc_info()
944        s = list(traceback.walk_tb(tb))
945        self.assertEqual(len(s), 1)
946
947    def test_extract_stack(self):
948        s = traceback.StackSummary.extract(traceback.walk_stack(None))
949        self.assertIsInstance(s, traceback.StackSummary)
950
951    def test_extract_stack_limit(self):
952        s = traceback.StackSummary.extract(traceback.walk_stack(None), limit=5)
953        self.assertEqual(len(s), 5)
954
955    def test_extract_stack_lookup_lines(self):
956        linecache.clearcache()
957        linecache.updatecache('/foo.py', globals())
958        c = test_code('/foo.py', 'method')
959        f = test_frame(c, None, None)
960        s = traceback.StackSummary.extract(iter([(f, 6)]), lookup_lines=True)
961        linecache.clearcache()
962        self.assertEqual(s[0].line, "import sys")
963
964    def test_extract_stackup_deferred_lookup_lines(self):
965        linecache.clearcache()
966        c = test_code('/foo.py', 'method')
967        f = test_frame(c, None, None)
968        s = traceback.StackSummary.extract(iter([(f, 6)]), lookup_lines=False)
969        self.assertEqual({}, linecache.cache)
970        linecache.updatecache('/foo.py', globals())
971        self.assertEqual(s[0].line, "import sys")
972
973    def test_from_list(self):
974        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
975        self.assertEqual(
976            ['  File "foo.py", line 1, in fred\n    line\n'],
977            s.format())
978
979    def test_from_list_edited_stack(self):
980        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
981        s[0] = ('foo.py', 2, 'fred', 'line')
982        s2 = traceback.StackSummary.from_list(s)
983        self.assertEqual(
984            ['  File "foo.py", line 2, in fred\n    line\n'],
985            s2.format())
986
987    def test_format_smoke(self):
988        # For detailed tests see the format_list tests, which consume the same
989        # code.
990        s = traceback.StackSummary.from_list([('foo.py', 1, 'fred', 'line')])
991        self.assertEqual(
992            ['  File "foo.py", line 1, in fred\n    line\n'],
993            s.format())
994
995    def test_locals(self):
996        linecache.updatecache('/foo.py', globals())
997        c = test_code('/foo.py', 'method')
998        f = test_frame(c, globals(), {'something': 1})
999        s = traceback.StackSummary.extract(iter([(f, 6)]), capture_locals=True)
1000        self.assertEqual(s[0].locals, {'something': '1'})
1001
1002    def test_no_locals(self):
1003        linecache.updatecache('/foo.py', globals())
1004        c = test_code('/foo.py', 'method')
1005        f = test_frame(c, globals(), {'something': 1})
1006        s = traceback.StackSummary.extract(iter([(f, 6)]))
1007        self.assertEqual(s[0].locals, None)
1008
1009    def test_format_locals(self):
1010        def some_inner(k, v):
1011            a = 1
1012            b = 2
1013            return traceback.StackSummary.extract(
1014                traceback.walk_stack(None), capture_locals=True, limit=1)
1015        s = some_inner(3, 4)
1016        self.assertEqual(
1017            ['  File "%s", line %d, in some_inner\n'
1018             '    return traceback.StackSummary.extract(\n'
1019             '    a = 1\n'
1020             '    b = 2\n'
1021             '    k = 3\n'
1022             '    v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
1023            ], s.format())
1024
1025class TestTracebackException(unittest.TestCase):
1026
1027    def test_smoke(self):
1028        try:
1029            1/0
1030        except Exception:
1031            exc_info = sys.exc_info()
1032            exc = traceback.TracebackException(*exc_info)
1033            expected_stack = traceback.StackSummary.extract(
1034                traceback.walk_tb(exc_info[2]))
1035        self.assertEqual(None, exc.__cause__)
1036        self.assertEqual(None, exc.__context__)
1037        self.assertEqual(False, exc.__suppress_context__)
1038        self.assertEqual(expected_stack, exc.stack)
1039        self.assertEqual(exc_info[0], exc.exc_type)
1040        self.assertEqual(str(exc_info[1]), str(exc))
1041
1042    def test_from_exception(self):
1043        # Check all the parameters are accepted.
1044        def foo():
1045            1/0
1046        try:
1047            foo()
1048        except Exception as e:
1049            exc_info = sys.exc_info()
1050            self.expected_stack = traceback.StackSummary.extract(
1051                traceback.walk_tb(exc_info[2]), limit=1, lookup_lines=False,
1052                capture_locals=True)
1053            self.exc = traceback.TracebackException.from_exception(
1054                e, limit=1, lookup_lines=False, capture_locals=True)
1055        expected_stack = self.expected_stack
1056        exc = self.exc
1057        self.assertEqual(None, exc.__cause__)
1058        self.assertEqual(None, exc.__context__)
1059        self.assertEqual(False, exc.__suppress_context__)
1060        self.assertEqual(expected_stack, exc.stack)
1061        self.assertEqual(exc_info[0], exc.exc_type)
1062        self.assertEqual(str(exc_info[1]), str(exc))
1063
1064    def test_cause(self):
1065        try:
1066            try:
1067                1/0
1068            finally:
1069                exc_info_context = sys.exc_info()
1070                exc_context = traceback.TracebackException(*exc_info_context)
1071                cause = Exception("cause")
1072                raise Exception("uh oh") from cause
1073        except Exception:
1074            exc_info = sys.exc_info()
1075            exc = traceback.TracebackException(*exc_info)
1076            expected_stack = traceback.StackSummary.extract(
1077                traceback.walk_tb(exc_info[2]))
1078        exc_cause = traceback.TracebackException(Exception, cause, None)
1079        self.assertEqual(exc_cause, exc.__cause__)
1080        self.assertEqual(exc_context, exc.__context__)
1081        self.assertEqual(True, exc.__suppress_context__)
1082        self.assertEqual(expected_stack, exc.stack)
1083        self.assertEqual(exc_info[0], exc.exc_type)
1084        self.assertEqual(str(exc_info[1]), str(exc))
1085
1086    def test_context(self):
1087        try:
1088            try:
1089                1/0
1090            finally:
1091                exc_info_context = sys.exc_info()
1092                exc_context = traceback.TracebackException(*exc_info_context)
1093                raise Exception("uh oh")
1094        except Exception:
1095            exc_info = sys.exc_info()
1096            exc = traceback.TracebackException(*exc_info)
1097            expected_stack = traceback.StackSummary.extract(
1098                traceback.walk_tb(exc_info[2]))
1099        self.assertEqual(None, exc.__cause__)
1100        self.assertEqual(exc_context, exc.__context__)
1101        self.assertEqual(False, exc.__suppress_context__)
1102        self.assertEqual(expected_stack, exc.stack)
1103        self.assertEqual(exc_info[0], exc.exc_type)
1104        self.assertEqual(str(exc_info[1]), str(exc))
1105
1106    def test_no_refs_to_exception_and_traceback_objects(self):
1107        try:
1108            1/0
1109        except Exception:
1110            exc_info = sys.exc_info()
1111
1112        refcnt1 = sys.getrefcount(exc_info[1])
1113        refcnt2 = sys.getrefcount(exc_info[2])
1114        exc = traceback.TracebackException(*exc_info)
1115        self.assertEqual(sys.getrefcount(exc_info[1]), refcnt1)
1116        self.assertEqual(sys.getrefcount(exc_info[2]), refcnt2)
1117
1118    def test_comparison_basic(self):
1119        try:
1120            1/0
1121        except Exception:
1122            exc_info = sys.exc_info()
1123            exc = traceback.TracebackException(*exc_info)
1124            exc2 = traceback.TracebackException(*exc_info)
1125        self.assertIsNot(exc, exc2)
1126        self.assertEqual(exc, exc2)
1127        self.assertNotEqual(exc, object())
1128        self.assertEqual(exc, ALWAYS_EQ)
1129
1130    def test_comparison_params_variations(self):
1131        def raise_exc():
1132            try:
1133                raise ValueError('bad value')
1134            except:
1135                raise
1136
1137        def raise_with_locals():
1138            x, y = 1, 2
1139            raise_exc()
1140
1141        try:
1142            raise_with_locals()
1143        except Exception:
1144            exc_info = sys.exc_info()
1145
1146        exc = traceback.TracebackException(*exc_info)
1147        exc1 = traceback.TracebackException(*exc_info, limit=10)
1148        exc2 = traceback.TracebackException(*exc_info, limit=2)
1149
1150        self.assertEqual(exc, exc1)      # limit=10 gets all frames
1151        self.assertNotEqual(exc, exc2)   # limit=2 truncates the output
1152
1153        # locals change the output
1154        exc3 = traceback.TracebackException(*exc_info, capture_locals=True)
1155        self.assertNotEqual(exc, exc3)
1156
1157        # there are no locals in the innermost frame
1158        exc4 = traceback.TracebackException(*exc_info, limit=-1)
1159        exc5 = traceback.TracebackException(*exc_info, limit=-1, capture_locals=True)
1160        self.assertEqual(exc4, exc5)
1161
1162        # there are locals in the next-to-innermost frame
1163        exc6 = traceback.TracebackException(*exc_info, limit=-2)
1164        exc7 = traceback.TracebackException(*exc_info, limit=-2, capture_locals=True)
1165        self.assertNotEqual(exc6, exc7)
1166
1167    def test_comparison_equivalent_exceptions_are_equal(self):
1168        excs = []
1169        for _ in range(2):
1170            try:
1171                1/0
1172            except:
1173                excs.append(traceback.TracebackException(*sys.exc_info()))
1174        self.assertEqual(excs[0], excs[1])
1175        self.assertEqual(list(excs[0].format()), list(excs[1].format()))
1176
1177    def test_unhashable(self):
1178        class UnhashableException(Exception):
1179            def __eq__(self, other):
1180                return True
1181
1182        ex1 = UnhashableException('ex1')
1183        ex2 = UnhashableException('ex2')
1184        try:
1185            raise ex2 from ex1
1186        except UnhashableException:
1187            try:
1188                raise ex1
1189            except UnhashableException:
1190                exc_info = sys.exc_info()
1191        exc = traceback.TracebackException(*exc_info)
1192        formatted = list(exc.format())
1193        self.assertIn('UnhashableException: ex2\n', formatted[2])
1194        self.assertIn('UnhashableException: ex1\n', formatted[6])
1195
1196    def test_limit(self):
1197        def recurse(n):
1198            if n:
1199                recurse(n-1)
1200            else:
1201                1/0
1202        try:
1203            recurse(10)
1204        except Exception:
1205            exc_info = sys.exc_info()
1206            exc = traceback.TracebackException(*exc_info, limit=5)
1207            expected_stack = traceback.StackSummary.extract(
1208                traceback.walk_tb(exc_info[2]), limit=5)
1209        self.assertEqual(expected_stack, exc.stack)
1210
1211    def test_lookup_lines(self):
1212        linecache.clearcache()
1213        e = Exception("uh oh")
1214        c = test_code('/foo.py', 'method')
1215        f = test_frame(c, None, None)
1216        tb = test_tb(f, 6, None)
1217        exc = traceback.TracebackException(Exception, e, tb, lookup_lines=False)
1218        self.assertEqual(linecache.cache, {})
1219        linecache.updatecache('/foo.py', globals())
1220        self.assertEqual(exc.stack[0].line, "import sys")
1221
1222    def test_locals(self):
1223        linecache.updatecache('/foo.py', globals())
1224        e = Exception("uh oh")
1225        c = test_code('/foo.py', 'method')
1226        f = test_frame(c, globals(), {'something': 1, 'other': 'string'})
1227        tb = test_tb(f, 6, None)
1228        exc = traceback.TracebackException(
1229            Exception, e, tb, capture_locals=True)
1230        self.assertEqual(
1231            exc.stack[0].locals, {'something': '1', 'other': "'string'"})
1232
1233    def test_no_locals(self):
1234        linecache.updatecache('/foo.py', globals())
1235        e = Exception("uh oh")
1236        c = test_code('/foo.py', 'method')
1237        f = test_frame(c, globals(), {'something': 1})
1238        tb = test_tb(f, 6, None)
1239        exc = traceback.TracebackException(Exception, e, tb)
1240        self.assertEqual(exc.stack[0].locals, None)
1241
1242    def test_traceback_header(self):
1243        # do not print a traceback header if exc_traceback is None
1244        # see issue #24695
1245        exc = traceback.TracebackException(Exception, Exception("haven"), None)
1246        self.assertEqual(list(exc.format()), ["Exception: haven\n"])
1247
1248
1249class MiscTest(unittest.TestCase):
1250
1251    def test_all(self):
1252        expected = set()
1253        blacklist = {'print_list'}
1254        for name in dir(traceback):
1255            if name.startswith('_') or name in blacklist:
1256                continue
1257            module_object = getattr(traceback, name)
1258            if getattr(module_object, '__module__', None) == 'traceback':
1259                expected.add(name)
1260        self.assertCountEqual(traceback.__all__, expected)
1261
1262
1263if __name__ == "__main__":
1264    unittest.main()
1265