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