• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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