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