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