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