• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Tests for the unparse.py script in the Tools/parser directory."""
2
3import unittest
4import test.support
5import io
6import os
7import random
8import tokenize
9import ast
10
11from test.test_tools import basepath, toolsdir, skip_if_missing
12
13skip_if_missing()
14
15parser_path = os.path.join(toolsdir, "parser")
16
17with test.support.DirsOnSysPath(parser_path):
18    import unparse
19
20def read_pyfile(filename):
21    """Read and return the contents of a Python source file (as a
22    string), taking into account the file encoding."""
23    with open(filename, "rb") as pyfile:
24        encoding = tokenize.detect_encoding(pyfile.readline)[0]
25    with open(filename, "r", encoding=encoding) as pyfile:
26        source = pyfile.read()
27    return source
28
29for_else = """\
30def f():
31    for x in range(10):
32        break
33    else:
34        y = 2
35    z = 3
36"""
37
38while_else = """\
39def g():
40    while True:
41        break
42    else:
43        y = 2
44    z = 3
45"""
46
47relative_import = """\
48from . import fred
49from .. import barney
50from .australia import shrimp as prawns
51"""
52
53nonlocal_ex = """\
54def f():
55    x = 1
56    def g():
57        nonlocal x
58        x = 2
59        y = 7
60        def h():
61            nonlocal x, y
62"""
63
64# also acts as test for 'except ... as ...'
65raise_from = """\
66try:
67    1 / 0
68except ZeroDivisionError as e:
69    raise ArithmeticError from e
70"""
71
72class_decorator = """\
73@f1(arg)
74@f2
75class Foo: pass
76"""
77
78elif1 = """\
79if cond1:
80    suite1
81elif cond2:
82    suite2
83else:
84    suite3
85"""
86
87elif2 = """\
88if cond1:
89    suite1
90elif cond2:
91    suite2
92"""
93
94try_except_finally = """\
95try:
96    suite1
97except ex1:
98    suite2
99except ex2:
100    suite3
101else:
102    suite4
103finally:
104    suite5
105"""
106
107with_simple = """\
108with f():
109    suite1
110"""
111
112with_as = """\
113with f() as x:
114    suite1
115"""
116
117with_two_items = """\
118with f() as x, g() as y:
119    suite1
120"""
121
122class ASTTestCase(unittest.TestCase):
123    def assertASTEqual(self, ast1, ast2):
124        self.assertEqual(ast.dump(ast1), ast.dump(ast2))
125
126    def check_roundtrip(self, code1, filename="internal"):
127        ast1 = compile(code1, filename, "exec", ast.PyCF_ONLY_AST)
128        unparse_buffer = io.StringIO()
129        unparse.Unparser(ast1, unparse_buffer)
130        code2 = unparse_buffer.getvalue()
131        ast2 = compile(code2, filename, "exec", ast.PyCF_ONLY_AST)
132        self.assertASTEqual(ast1, ast2)
133
134class UnparseTestCase(ASTTestCase):
135    # Tests for specific bugs found in earlier versions of unparse
136
137    def test_fstrings(self):
138        # See issue 25180
139        self.check_roundtrip(r"""f'{f"{0}"*3}'""")
140        self.check_roundtrip(r"""f'{f"{y}"*3}'""")
141
142    def test_del_statement(self):
143        self.check_roundtrip("del x, y, z")
144
145    def test_shifts(self):
146        self.check_roundtrip("45 << 2")
147        self.check_roundtrip("13 >> 7")
148
149    def test_for_else(self):
150        self.check_roundtrip(for_else)
151
152    def test_while_else(self):
153        self.check_roundtrip(while_else)
154
155    def test_unary_parens(self):
156        self.check_roundtrip("(-1)**7")
157        self.check_roundtrip("(-1.)**8")
158        self.check_roundtrip("(-1j)**6")
159        self.check_roundtrip("not True or False")
160        self.check_roundtrip("True or not False")
161
162    def test_integer_parens(self):
163        self.check_roundtrip("3 .__abs__()")
164
165    def test_huge_float(self):
166        self.check_roundtrip("1e1000")
167        self.check_roundtrip("-1e1000")
168        self.check_roundtrip("1e1000j")
169        self.check_roundtrip("-1e1000j")
170
171    def test_min_int(self):
172        self.check_roundtrip(str(-2**31))
173        self.check_roundtrip(str(-2**63))
174
175    def test_imaginary_literals(self):
176        self.check_roundtrip("7j")
177        self.check_roundtrip("-7j")
178        self.check_roundtrip("0j")
179        self.check_roundtrip("-0j")
180
181    def test_lambda_parentheses(self):
182        self.check_roundtrip("(lambda: int)()")
183
184    def test_chained_comparisons(self):
185        self.check_roundtrip("1 < 4 <= 5")
186        self.check_roundtrip("a is b is c is not d")
187
188    def test_function_arguments(self):
189        self.check_roundtrip("def f(): pass")
190        self.check_roundtrip("def f(a): pass")
191        self.check_roundtrip("def f(b = 2): pass")
192        self.check_roundtrip("def f(a, b): pass")
193        self.check_roundtrip("def f(a, b = 2): pass")
194        self.check_roundtrip("def f(a = 5, b = 2): pass")
195        self.check_roundtrip("def f(*, a = 1, b = 2): pass")
196        self.check_roundtrip("def f(*, a = 1, b): pass")
197        self.check_roundtrip("def f(*, a, b = 2): pass")
198        self.check_roundtrip("def f(a, b = None, *, c, **kwds): pass")
199        self.check_roundtrip("def f(a=2, *args, c=5, d, **kwds): pass")
200        self.check_roundtrip("def f(*args, **kwargs): pass")
201
202    def test_relative_import(self):
203        self.check_roundtrip(relative_import)
204
205    def test_nonlocal(self):
206        self.check_roundtrip(nonlocal_ex)
207
208    def test_raise_from(self):
209        self.check_roundtrip(raise_from)
210
211    def test_bytes(self):
212        self.check_roundtrip("b'123'")
213
214    def test_annotations(self):
215        self.check_roundtrip("def f(a : int): pass")
216        self.check_roundtrip("def f(a: int = 5): pass")
217        self.check_roundtrip("def f(*args: [int]): pass")
218        self.check_roundtrip("def f(**kwargs: dict): pass")
219        self.check_roundtrip("def f() -> None: pass")
220
221    def test_set_literal(self):
222        self.check_roundtrip("{'a', 'b', 'c'}")
223
224    def test_set_comprehension(self):
225        self.check_roundtrip("{x for x in range(5)}")
226
227    def test_dict_comprehension(self):
228        self.check_roundtrip("{x: x*x for x in range(10)}")
229
230    def test_class_decorators(self):
231        self.check_roundtrip(class_decorator)
232
233    def test_class_definition(self):
234        self.check_roundtrip("class A(metaclass=type, *[], **{}): pass")
235
236    def test_elifs(self):
237        self.check_roundtrip(elif1)
238        self.check_roundtrip(elif2)
239
240    def test_try_except_finally(self):
241        self.check_roundtrip(try_except_finally)
242
243    def test_starred_assignment(self):
244        self.check_roundtrip("a, *b, c = seq")
245        self.check_roundtrip("a, (*b, c) = seq")
246        self.check_roundtrip("a, *b[0], c = seq")
247        self.check_roundtrip("a, *(b, c) = seq")
248
249    def test_with_simple(self):
250        self.check_roundtrip(with_simple)
251
252    def test_with_as(self):
253        self.check_roundtrip(with_as)
254
255    def test_with_two_items(self):
256        self.check_roundtrip(with_two_items)
257
258    def test_dict_unpacking_in_dict(self):
259        # See issue 26489
260        self.check_roundtrip(r"""{**{'y': 2}, 'x': 1}""")
261        self.check_roundtrip(r"""{**{'y': 2}, **{'x': 1}}""")
262
263
264class DirectoryTestCase(ASTTestCase):
265    """Test roundtrip behaviour on all files in Lib and Lib/test."""
266
267    # test directories, relative to the root of the distribution
268    test_directories = 'Lib', os.path.join('Lib', 'test')
269
270    def test_files(self):
271        # get names of files to test
272
273        names = []
274        for d in self.test_directories:
275            test_dir = os.path.join(basepath, d)
276            for n in os.listdir(test_dir):
277                if n.endswith('.py') and not n.startswith('bad'):
278                    names.append(os.path.join(test_dir, n))
279
280        # Test limited subset of files unless the 'cpu' resource is specified.
281        if not test.support.is_resource_enabled("cpu"):
282            names = random.sample(names, 10)
283
284        for filename in names:
285            if test.support.verbose:
286                print('Testing %s' % filename)
287
288            # Some f-strings are not correctly round-tripped by
289            #  Tools/parser/unparse.py.  See issue 28002 for details.
290            #  We need to skip files that contain such f-strings.
291            if os.path.basename(filename) in ('test_fstring.py', ):
292                if test.support.verbose:
293                    print(f'Skipping {filename}: see issue 28002')
294                continue
295
296            with self.subTest(filename=filename):
297                source = read_pyfile(filename)
298                self.check_roundtrip(source)
299
300
301if __name__ == '__main__':
302    unittest.main()
303