1"""Test suite for 2to3's parser and grammar files. 2 3This is the place to add tests for changes to 2to3's grammar, such as those 4merging the grammars for Python 2 and 3. In addition to specific tests for 5parts of the grammar we've changed, we also make sure we can parse the 6test_grammar.py files from both Python 2 and Python 3. 7""" 8 9# Testing imports 10from . import support 11from .support import driver, driver_no_print_statement 12 13# Python imports 14import difflib 15import importlib 16import operator 17import os 18import pickle 19import shutil 20import subprocess 21import sys 22import tempfile 23import test.support 24import unittest 25 26# Local imports 27from lib2to3.pgen2 import driver as pgen2_driver 28from lib2to3.pgen2 import tokenize 29from ..pgen2.parse import ParseError 30from lib2to3.pygram import python_symbols as syms 31 32 33class TestDriver(support.TestCase): 34 35 def test_formfeed(self): 36 s = """print 1\n\x0Cprint 2\n""" 37 t = driver.parse_string(s) 38 self.assertEqual(t.children[0].children[0].type, syms.print_stmt) 39 self.assertEqual(t.children[1].children[0].type, syms.print_stmt) 40 41 42class TestPgen2Caching(support.TestCase): 43 def test_load_grammar_from_txt_file(self): 44 pgen2_driver.load_grammar(support.grammar_path, save=False, force=True) 45 46 def test_load_grammar_from_pickle(self): 47 # Make a copy of the grammar file in a temp directory we are 48 # guaranteed to be able to write to. 49 tmpdir = tempfile.mkdtemp() 50 try: 51 grammar_copy = os.path.join( 52 tmpdir, os.path.basename(support.grammar_path)) 53 shutil.copy(support.grammar_path, grammar_copy) 54 pickle_name = pgen2_driver._generate_pickle_name(grammar_copy) 55 56 pgen2_driver.load_grammar(grammar_copy, save=True, force=True) 57 self.assertTrue(os.path.exists(pickle_name)) 58 59 os.unlink(grammar_copy) # Only the pickle remains... 60 pgen2_driver.load_grammar(grammar_copy, save=False, force=False) 61 finally: 62 shutil.rmtree(tmpdir) 63 64 @unittest.skipIf(sys.executable is None, 'sys.executable required') 65 def test_load_grammar_from_subprocess(self): 66 tmpdir = tempfile.mkdtemp() 67 tmpsubdir = os.path.join(tmpdir, 'subdir') 68 try: 69 os.mkdir(tmpsubdir) 70 grammar_base = os.path.basename(support.grammar_path) 71 grammar_copy = os.path.join(tmpdir, grammar_base) 72 grammar_sub_copy = os.path.join(tmpsubdir, grammar_base) 73 shutil.copy(support.grammar_path, grammar_copy) 74 shutil.copy(support.grammar_path, grammar_sub_copy) 75 pickle_name = pgen2_driver._generate_pickle_name(grammar_copy) 76 pickle_sub_name = pgen2_driver._generate_pickle_name( 77 grammar_sub_copy) 78 self.assertNotEqual(pickle_name, pickle_sub_name) 79 80 # Generate a pickle file from this process. 81 pgen2_driver.load_grammar(grammar_copy, save=True, force=True) 82 self.assertTrue(os.path.exists(pickle_name)) 83 84 # Generate a new pickle file in a subprocess with a most likely 85 # different hash randomization seed. 86 sub_env = dict(os.environ) 87 sub_env['PYTHONHASHSEED'] = 'random' 88 code = """ 89from lib2to3.pgen2 import driver as pgen2_driver 90pgen2_driver.load_grammar(%r, save=True, force=True) 91 """ % (grammar_sub_copy,) 92 msg = ("lib2to3 package is deprecated and may not be able " 93 "to parse Python 3.10+") 94 cmd = [sys.executable, 95 f'-Wignore:{msg}:PendingDeprecationWarning', 96 '-c', code] 97 subprocess.check_call( cmd, env=sub_env) 98 self.assertTrue(os.path.exists(pickle_sub_name)) 99 100 with open(pickle_name, 'rb') as pickle_f_1, \ 101 open(pickle_sub_name, 'rb') as pickle_f_2: 102 self.assertEqual( 103 pickle_f_1.read(), pickle_f_2.read(), 104 msg='Grammar caches generated using different hash seeds' 105 ' were not identical.') 106 finally: 107 shutil.rmtree(tmpdir) 108 109 def test_load_packaged_grammar(self): 110 modname = __name__ + '.load_test' 111 class MyLoader: 112 def get_data(self, where): 113 return pickle.dumps({'elephant': 19}) 114 class MyModule: 115 __file__ = 'parsertestmodule' 116 __spec__ = importlib.util.spec_from_loader(modname, MyLoader()) 117 sys.modules[modname] = MyModule() 118 self.addCleanup(operator.delitem, sys.modules, modname) 119 g = pgen2_driver.load_packaged_grammar(modname, 'Grammar.txt') 120 self.assertEqual(g.elephant, 19) 121 122 123class GrammarTest(support.TestCase): 124 def validate(self, code): 125 support.parse_string(code) 126 127 def invalid_syntax(self, code): 128 try: 129 self.validate(code) 130 except ParseError: 131 pass 132 else: 133 raise AssertionError("Syntax shouldn't have been valid") 134 135 136class TestMatrixMultiplication(GrammarTest): 137 def test_matrix_multiplication_operator(self): 138 self.validate("a @ b") 139 self.validate("a @= b") 140 141 142class TestYieldFrom(GrammarTest): 143 def test_yield_from(self): 144 self.validate("yield from x") 145 self.validate("(yield from x) + y") 146 self.invalid_syntax("yield from") 147 148 149class TestAsyncAwait(GrammarTest): 150 def test_await_expr(self): 151 self.validate("""async def foo(): 152 await x 153 """) 154 155 self.validate("""async def foo(): 156 [i async for i in b] 157 """) 158 159 self.validate("""async def foo(): 160 {i for i in b 161 async for i in a if await i 162 for b in i} 163 """) 164 165 self.validate("""async def foo(): 166 [await i for i in b if await c] 167 """) 168 169 self.validate("""async def foo(): 170 [ i for i in b if c] 171 """) 172 173 self.validate("""async def foo(): 174 175 def foo(): pass 176 177 def foo(): pass 178 179 await x 180 """) 181 182 self.validate("""async def foo(): return await a""") 183 184 self.validate("""def foo(): 185 def foo(): pass 186 async def foo(): await x 187 """) 188 189 self.invalid_syntax("await x") 190 self.invalid_syntax("""def foo(): 191 await x""") 192 193 self.invalid_syntax("""def foo(): 194 def foo(): pass 195 async def foo(): pass 196 await x 197 """) 198 199 def test_async_var(self): 200 self.validate("""async = 1""") 201 self.validate("""await = 1""") 202 self.validate("""def async(): pass""") 203 204 def test_async_for(self): 205 self.validate("""async def foo(): 206 async for a in b: pass""") 207 208 def test_async_with(self): 209 self.validate("""async def foo(): 210 async with a: pass""") 211 212 self.invalid_syntax("""def foo(): 213 async with a: pass""") 214 215 def test_async_generator(self): 216 self.validate( 217 """async def foo(): 218 return (i * 2 async for i in arange(42))""" 219 ) 220 self.validate( 221 """def foo(): 222 return (i * 2 async for i in arange(42))""" 223 ) 224 225 226class TestRaiseChanges(GrammarTest): 227 def test_2x_style_1(self): 228 self.validate("raise") 229 230 def test_2x_style_2(self): 231 self.validate("raise E, V") 232 233 def test_2x_style_3(self): 234 self.validate("raise E, V, T") 235 236 def test_2x_style_invalid_1(self): 237 self.invalid_syntax("raise E, V, T, Z") 238 239 def test_3x_style(self): 240 self.validate("raise E1 from E2") 241 242 def test_3x_style_invalid_1(self): 243 self.invalid_syntax("raise E, V from E1") 244 245 def test_3x_style_invalid_2(self): 246 self.invalid_syntax("raise E from E1, E2") 247 248 def test_3x_style_invalid_3(self): 249 self.invalid_syntax("raise from E1, E2") 250 251 def test_3x_style_invalid_4(self): 252 self.invalid_syntax("raise E from") 253 254 255# Modelled after Lib/test/test_grammar.py:TokenTests.test_funcdef issue2292 256# and Lib/test/text_parser.py test_list_displays, test_set_displays, 257# test_dict_displays, test_argument_unpacking, ... changes. 258class TestUnpackingGeneralizations(GrammarTest): 259 def test_mid_positional_star(self): 260 self.validate("""func(1, *(2, 3), 4)""") 261 262 def test_double_star_dict_literal(self): 263 self.validate("""func(**{'eggs':'scrambled', 'spam':'fried'})""") 264 265 def test_double_star_dict_literal_after_keywords(self): 266 self.validate("""func(spam='fried', **{'eggs':'scrambled'})""") 267 268 def test_double_star_expression(self): 269 self.validate("""func(**{'a':2} or {})""") 270 self.validate("""func(**() or {})""") 271 272 def test_star_expression(self): 273 self.validate("""func(*[] or [2])""") 274 275 def test_list_display(self): 276 self.validate("""[*{2}, 3, *[4]]""") 277 278 def test_set_display(self): 279 self.validate("""{*{2}, 3, *[4]}""") 280 281 def test_dict_display_1(self): 282 self.validate("""{**{}}""") 283 284 def test_dict_display_2(self): 285 self.validate("""{**{}, 3:4, **{5:6, 7:8}}""") 286 287 def test_complex_star_expression(self): 288 self.validate("func(* [] or [1])") 289 290 def test_complex_double_star_expression(self): 291 self.validate("func(**{1: 3} if False else {x: x for x in range(3)})") 292 293 def test_argument_unpacking_1(self): 294 self.validate("""f(a, *b, *c, d)""") 295 296 def test_argument_unpacking_2(self): 297 self.validate("""f(**a, **b)""") 298 299 def test_argument_unpacking_3(self): 300 self.validate("""f(2, *a, *b, **b, **c, **d)""") 301 302 def test_trailing_commas_1(self): 303 self.validate("def f(a, b): call(a, b)") 304 self.validate("def f(a, b,): call(a, b,)") 305 306 def test_trailing_commas_2(self): 307 self.validate("def f(a, *b): call(a, *b)") 308 self.validate("def f(a, *b,): call(a, *b,)") 309 310 def test_trailing_commas_3(self): 311 self.validate("def f(a, b=1): call(a, b=1)") 312 self.validate("def f(a, b=1,): call(a, b=1,)") 313 314 def test_trailing_commas_4(self): 315 self.validate("def f(a, **b): call(a, **b)") 316 self.validate("def f(a, **b,): call(a, **b,)") 317 318 def test_trailing_commas_5(self): 319 self.validate("def f(*a, b=1): call(*a, b=1)") 320 self.validate("def f(*a, b=1,): call(*a, b=1,)") 321 322 def test_trailing_commas_6(self): 323 self.validate("def f(*a, **b): call(*a, **b)") 324 self.validate("def f(*a, **b,): call(*a, **b,)") 325 326 def test_trailing_commas_7(self): 327 self.validate("def f(*, b=1): call(*b)") 328 self.validate("def f(*, b=1,): call(*b,)") 329 330 def test_trailing_commas_8(self): 331 self.validate("def f(a=1, b=2): call(a=1, b=2)") 332 self.validate("def f(a=1, b=2,): call(a=1, b=2,)") 333 334 def test_trailing_commas_9(self): 335 self.validate("def f(a=1, **b): call(a=1, **b)") 336 self.validate("def f(a=1, **b,): call(a=1, **b,)") 337 338 def test_trailing_commas_lambda_1(self): 339 self.validate("f = lambda a, b: call(a, b)") 340 self.validate("f = lambda a, b,: call(a, b,)") 341 342 def test_trailing_commas_lambda_2(self): 343 self.validate("f = lambda a, *b: call(a, *b)") 344 self.validate("f = lambda a, *b,: call(a, *b,)") 345 346 def test_trailing_commas_lambda_3(self): 347 self.validate("f = lambda a, b=1: call(a, b=1)") 348 self.validate("f = lambda a, b=1,: call(a, b=1,)") 349 350 def test_trailing_commas_lambda_4(self): 351 self.validate("f = lambda a, **b: call(a, **b)") 352 self.validate("f = lambda a, **b,: call(a, **b,)") 353 354 def test_trailing_commas_lambda_5(self): 355 self.validate("f = lambda *a, b=1: call(*a, b=1)") 356 self.validate("f = lambda *a, b=1,: call(*a, b=1,)") 357 358 def test_trailing_commas_lambda_6(self): 359 self.validate("f = lambda *a, **b: call(*a, **b)") 360 self.validate("f = lambda *a, **b,: call(*a, **b,)") 361 362 def test_trailing_commas_lambda_7(self): 363 self.validate("f = lambda *, b=1: call(*b)") 364 self.validate("f = lambda *, b=1,: call(*b,)") 365 366 def test_trailing_commas_lambda_8(self): 367 self.validate("f = lambda a=1, b=2: call(a=1, b=2)") 368 self.validate("f = lambda a=1, b=2,: call(a=1, b=2,)") 369 370 def test_trailing_commas_lambda_9(self): 371 self.validate("f = lambda a=1, **b: call(a=1, **b)") 372 self.validate("f = lambda a=1, **b,: call(a=1, **b,)") 373 374 375# Adapted from Python 3's Lib/test/test_grammar.py:GrammarTests.testFuncdef 376class TestFunctionAnnotations(GrammarTest): 377 def test_1(self): 378 self.validate("""def f(x) -> list: pass""") 379 380 def test_2(self): 381 self.validate("""def f(x:int): pass""") 382 383 def test_3(self): 384 self.validate("""def f(*x:str): pass""") 385 386 def test_4(self): 387 self.validate("""def f(**x:float): pass""") 388 389 def test_5(self): 390 self.validate("""def f(x, y:1+2): pass""") 391 392 def test_6(self): 393 self.validate("""def f(a, (b:1, c:2, d)): pass""") 394 395 def test_7(self): 396 self.validate("""def f(a, (b:1, c:2, d), e:3=4, f=5, *g:6): pass""") 397 398 def test_8(self): 399 s = """def f(a, (b:1, c:2, d), e:3=4, f=5, 400 *g:6, h:7, i=8, j:9=10, **k:11) -> 12: pass""" 401 self.validate(s) 402 403 def test_9(self): 404 s = """def f( 405 a: str, 406 b: int, 407 *, 408 c: bool = False, 409 **kwargs, 410 ) -> None: 411 call(c=c, **kwargs,)""" 412 self.validate(s) 413 414 def test_10(self): 415 s = """def f( 416 a: str, 417 ) -> None: 418 call(a,)""" 419 self.validate(s) 420 421 def test_11(self): 422 s = """def f( 423 a: str = '', 424 ) -> None: 425 call(a=a,)""" 426 self.validate(s) 427 428 def test_12(self): 429 s = """def f( 430 *args: str, 431 ) -> None: 432 call(*args,)""" 433 self.validate(s) 434 435 def test_13(self): 436 self.validate("def f(a: str, b: int) -> None: call(a, b)") 437 self.validate("def f(a: str, b: int,) -> None: call(a, b,)") 438 439 def test_14(self): 440 self.validate("def f(a: str, *b: int) -> None: call(a, *b)") 441 self.validate("def f(a: str, *b: int,) -> None: call(a, *b,)") 442 443 def test_15(self): 444 self.validate("def f(a: str, b: int=1) -> None: call(a, b=1)") 445 self.validate("def f(a: str, b: int=1,) -> None: call(a, b=1,)") 446 447 def test_16(self): 448 self.validate("def f(a: str, **b: int) -> None: call(a, **b)") 449 self.validate("def f(a: str, **b: int,) -> None: call(a, **b,)") 450 451 def test_17(self): 452 self.validate("def f(*a: str, b: int=1) -> None: call(*a, b=1)") 453 self.validate("def f(*a: str, b: int=1,) -> None: call(*a, b=1,)") 454 455 def test_18(self): 456 self.validate("def f(*a: str, **b: int) -> None: call(*a, **b)") 457 self.validate("def f(*a: str, **b: int,) -> None: call(*a, **b,)") 458 459 def test_19(self): 460 self.validate("def f(*, b: int=1) -> None: call(*b)") 461 self.validate("def f(*, b: int=1,) -> None: call(*b,)") 462 463 def test_20(self): 464 self.validate("def f(a: str='', b: int=2) -> None: call(a=a, b=2)") 465 self.validate("def f(a: str='', b: int=2,) -> None: call(a=a, b=2,)") 466 467 def test_21(self): 468 self.validate("def f(a: str='', **b: int) -> None: call(a=a, **b)") 469 self.validate("def f(a: str='', **b: int,) -> None: call(a=a, **b,)") 470 471 472# Adapted from Python 3's Lib/test/test_grammar.py:GrammarTests.test_var_annot 473class TestVarAnnotations(GrammarTest): 474 def test_1(self): 475 self.validate("var1: int = 5") 476 477 def test_2(self): 478 self.validate("var2: [int, str]") 479 480 def test_3(self): 481 self.validate("def f():\n" 482 " st: str = 'Hello'\n" 483 " a.b: int = (1, 2)\n" 484 " return st\n") 485 486 def test_4(self): 487 self.validate("def fbad():\n" 488 " x: int\n" 489 " print(x)\n") 490 491 def test_5(self): 492 self.validate("class C:\n" 493 " x: int\n" 494 " s: str = 'attr'\n" 495 " z = 2\n" 496 " def __init__(self, x):\n" 497 " self.x: int = x\n") 498 499 def test_6(self): 500 self.validate("lst: List[int] = []") 501 502 503class TestExcept(GrammarTest): 504 def test_new(self): 505 s = """ 506 try: 507 x 508 except E as N: 509 y""" 510 self.validate(s) 511 512 def test_old(self): 513 s = """ 514 try: 515 x 516 except E, N: 517 y""" 518 self.validate(s) 519 520 521class TestStringLiterals(GrammarTest): 522 prefixes = ("'", '"', 523 "r'", 'r"', "R'", 'R"', 524 "u'", 'u"', "U'", 'U"', 525 "b'", 'b"', "B'", 'B"', 526 "f'", 'f"', "F'", 'F"', 527 "ur'", 'ur"', "Ur'", 'Ur"', 528 "uR'", 'uR"', "UR'", 'UR"', 529 "br'", 'br"', "Br'", 'Br"', 530 "bR'", 'bR"', "BR'", 'BR"', 531 "rb'", 'rb"', "Rb'", 'Rb"', 532 "rB'", 'rB"', "RB'", 'RB"',) 533 534 def test_lit(self): 535 for pre in self.prefixes: 536 single = "{p}spamspamspam{s}".format(p=pre, s=pre[-1]) 537 self.validate(single) 538 triple = "{p}{s}{s}eggs{s}{s}{s}".format(p=pre, s=pre[-1]) 539 self.validate(triple) 540 541 542# Adapted from Python 3's Lib/test/test_grammar.py:GrammarTests.testAtoms 543class TestSetLiteral(GrammarTest): 544 def test_1(self): 545 self.validate("""x = {'one'}""") 546 547 def test_2(self): 548 self.validate("""x = {'one', 1,}""") 549 550 def test_3(self): 551 self.validate("""x = {'one', 'two', 'three'}""") 552 553 def test_4(self): 554 self.validate("""x = {2, 3, 4,}""") 555 556 557# Adapted from Python 3's Lib/test/test_unicode_identifiers.py and 558# Lib/test/test_tokenize.py:TokenizeTest.test_non_ascii_identifiers 559class TestIdentifier(GrammarTest): 560 def test_non_ascii_identifiers(self): 561 self.validate("Örter = 'places'\ngrün = 'green'") 562 self.validate("蟒 = a蟒 = 锦蛇 = 1") 563 self.validate("µ = aµ = µµ = 1") 564 self.validate(" = a_ = 1") 565 566 567class TestNumericLiterals(GrammarTest): 568 def test_new_octal_notation(self): 569 self.validate("""0o7777777777777""") 570 self.invalid_syntax("""0o7324528887""") 571 572 def test_new_binary_notation(self): 573 self.validate("""0b101010""") 574 self.invalid_syntax("""0b0101021""") 575 576 577class TestClassDef(GrammarTest): 578 def test_new_syntax(self): 579 self.validate("class B(t=7): pass") 580 self.validate("class B(t, *args): pass") 581 self.validate("class B(t, **kwargs): pass") 582 self.validate("class B(t, *args, **kwargs): pass") 583 self.validate("class B(t, y=9, *args, **kwargs,): pass") 584 585 586class TestParserIdempotency(support.TestCase): 587 588 """A cut-down version of pytree_idempotency.py.""" 589 590 def parse_file(self, filepath): 591 if test.support.verbose: 592 print(f"Parse file: {filepath}") 593 with open(filepath, "rb") as fp: 594 encoding = tokenize.detect_encoding(fp.readline)[0] 595 self.assertIsNotNone(encoding, 596 "can't detect encoding for %s" % filepath) 597 with open(filepath, "r", encoding=encoding) as fp: 598 source = fp.read() 599 try: 600 tree = driver.parse_string(source) 601 except ParseError: 602 try: 603 tree = driver_no_print_statement.parse_string(source) 604 except ParseError as err: 605 self.fail('ParseError on file %s (%s)' % (filepath, err)) 606 new = str(tree) 607 if new != source: 608 print(diff_texts(source, new, filepath)) 609 self.fail("Idempotency failed: %s" % filepath) 610 611 def test_all_project_files(self): 612 for filepath in support.all_project_files(): 613 with self.subTest(filepath=filepath): 614 self.parse_file(filepath) 615 616 def test_extended_unpacking(self): 617 driver.parse_string("a, *b, c = x\n") 618 driver.parse_string("[*a, b] = x\n") 619 driver.parse_string("(z, *y, w) = m\n") 620 driver.parse_string("for *z, m in d: pass\n") 621 622 623class TestLiterals(GrammarTest): 624 625 def validate(self, s): 626 driver.parse_string(support.dedent(s) + "\n\n") 627 628 def test_multiline_bytes_literals(self): 629 s = """ 630 md5test(b"\xaa" * 80, 631 (b"Test Using Larger Than Block-Size Key " 632 b"and Larger Than One Block-Size Data"), 633 "6f630fad67cda0ee1fb1f562db3aa53e") 634 """ 635 self.validate(s) 636 637 def test_multiline_bytes_tripquote_literals(self): 638 s = ''' 639 b""" 640 <?xml version="1.0" encoding="UTF-8"?> 641 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"> 642 """ 643 ''' 644 self.validate(s) 645 646 def test_multiline_str_literals(self): 647 s = """ 648 md5test("\xaa" * 80, 649 ("Test Using Larger Than Block-Size Key " 650 "and Larger Than One Block-Size Data"), 651 "6f630fad67cda0ee1fb1f562db3aa53e") 652 """ 653 self.validate(s) 654 655 656class TestNamedAssignments(GrammarTest): 657 """Also known as the walrus operator.""" 658 659 def test_named_assignment_if(self): 660 driver.parse_string("if f := x(): pass\n") 661 662 def test_named_assignment_while(self): 663 driver.parse_string("while f := x(): pass\n") 664 665 def test_named_assignment_generator(self): 666 driver.parse_string("any((lastNum := num) == 1 for num in [1, 2, 3])\n") 667 668 def test_named_assignment_listcomp(self): 669 driver.parse_string("[(lastNum := num) == 1 for num in [1, 2, 3]]\n") 670 671 672class TestPositionalOnlyArgs(GrammarTest): 673 674 def test_one_pos_only_arg(self): 675 driver.parse_string("def one_pos_only_arg(a, /): pass\n") 676 677 def test_all_markers(self): 678 driver.parse_string( 679 "def all_markers(a, b=2, /, c, d=4, *, e=5, f): pass\n") 680 681 def test_all_with_args_and_kwargs(self): 682 driver.parse_string( 683 """def all_markers_with_args_and_kwargs( 684 aa, b, /, _cc, d, *args, e, f_f, **kwargs, 685 ): 686 pass\n""") 687 688 def test_lambda_soup(self): 689 driver.parse_string( 690 "lambda a, b, /, c, d, *args, e, f, **kw: kw\n") 691 692 def test_only_positional_or_keyword(self): 693 driver.parse_string("def func(a,b,/,*,g,e=3): pass\n") 694 695 696class TestPickleableException(unittest.TestCase): 697 def test_ParseError(self): 698 err = ParseError('msg', 2, None, (1, 'context')) 699 for proto in range(pickle.HIGHEST_PROTOCOL + 1): 700 err2 = pickle.loads(pickle.dumps(err, protocol=proto)) 701 self.assertEqual(err.args, err2.args) 702 self.assertEqual(err.msg, err2.msg) 703 self.assertEqual(err.type, err2.type) 704 self.assertEqual(err.value, err2.value) 705 self.assertEqual(err.context, err2.context) 706 707 708def diff_texts(a, b, filename): 709 a = a.splitlines() 710 b = b.splitlines() 711 return difflib.unified_diff(a, b, filename, filename, 712 "(original)", "(reserialized)", 713 lineterm="") 714 715 716if __name__ == '__main__': 717 unittest.main() 718