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