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_strings(self): 143 self.check_roundtrip("u'foo'") 144 self.check_roundtrip("r'foo'") 145 self.check_roundtrip("b'foo'") 146 147 def test_del_statement(self): 148 self.check_roundtrip("del x, y, z") 149 150 def test_shifts(self): 151 self.check_roundtrip("45 << 2") 152 self.check_roundtrip("13 >> 7") 153 154 def test_for_else(self): 155 self.check_roundtrip(for_else) 156 157 def test_while_else(self): 158 self.check_roundtrip(while_else) 159 160 def test_unary_parens(self): 161 self.check_roundtrip("(-1)**7") 162 self.check_roundtrip("(-1.)**8") 163 self.check_roundtrip("(-1j)**6") 164 self.check_roundtrip("not True or False") 165 self.check_roundtrip("True or not False") 166 167 def test_integer_parens(self): 168 self.check_roundtrip("3 .__abs__()") 169 170 def test_huge_float(self): 171 self.check_roundtrip("1e1000") 172 self.check_roundtrip("-1e1000") 173 self.check_roundtrip("1e1000j") 174 self.check_roundtrip("-1e1000j") 175 176 def test_min_int(self): 177 self.check_roundtrip(str(-2**31)) 178 self.check_roundtrip(str(-2**63)) 179 180 def test_imaginary_literals(self): 181 self.check_roundtrip("7j") 182 self.check_roundtrip("-7j") 183 self.check_roundtrip("0j") 184 self.check_roundtrip("-0j") 185 186 def test_lambda_parentheses(self): 187 self.check_roundtrip("(lambda: int)()") 188 189 def test_chained_comparisons(self): 190 self.check_roundtrip("1 < 4 <= 5") 191 self.check_roundtrip("a is b is c is not d") 192 193 def test_function_arguments(self): 194 self.check_roundtrip("def f(): pass") 195 self.check_roundtrip("def f(a): pass") 196 self.check_roundtrip("def f(b = 2): pass") 197 self.check_roundtrip("def f(a, b): pass") 198 self.check_roundtrip("def f(a, b = 2): pass") 199 self.check_roundtrip("def f(a = 5, b = 2): pass") 200 self.check_roundtrip("def f(*, a = 1, b = 2): pass") 201 self.check_roundtrip("def f(*, a = 1, b): pass") 202 self.check_roundtrip("def f(*, a, b = 2): pass") 203 self.check_roundtrip("def f(a, b = None, *, c, **kwds): pass") 204 self.check_roundtrip("def f(a=2, *args, c=5, d, **kwds): pass") 205 self.check_roundtrip("def f(*args, **kwargs): pass") 206 207 def test_relative_import(self): 208 self.check_roundtrip(relative_import) 209 210 def test_nonlocal(self): 211 self.check_roundtrip(nonlocal_ex) 212 213 def test_raise_from(self): 214 self.check_roundtrip(raise_from) 215 216 def test_bytes(self): 217 self.check_roundtrip("b'123'") 218 219 def test_annotations(self): 220 self.check_roundtrip("def f(a : int): pass") 221 self.check_roundtrip("def f(a: int = 5): pass") 222 self.check_roundtrip("def f(*args: [int]): pass") 223 self.check_roundtrip("def f(**kwargs: dict): pass") 224 self.check_roundtrip("def f() -> None: pass") 225 226 def test_set_literal(self): 227 self.check_roundtrip("{'a', 'b', 'c'}") 228 229 def test_set_comprehension(self): 230 self.check_roundtrip("{x for x in range(5)}") 231 232 def test_dict_comprehension(self): 233 self.check_roundtrip("{x: x*x for x in range(10)}") 234 235 def test_class_decorators(self): 236 self.check_roundtrip(class_decorator) 237 238 def test_class_definition(self): 239 self.check_roundtrip("class A(metaclass=type, *[], **{}): pass") 240 241 def test_elifs(self): 242 self.check_roundtrip(elif1) 243 self.check_roundtrip(elif2) 244 245 def test_try_except_finally(self): 246 self.check_roundtrip(try_except_finally) 247 248 def test_starred_assignment(self): 249 self.check_roundtrip("a, *b, c = seq") 250 self.check_roundtrip("a, (*b, c) = seq") 251 self.check_roundtrip("a, *b[0], c = seq") 252 self.check_roundtrip("a, *(b, c) = seq") 253 254 def test_with_simple(self): 255 self.check_roundtrip(with_simple) 256 257 def test_with_as(self): 258 self.check_roundtrip(with_as) 259 260 def test_with_two_items(self): 261 self.check_roundtrip(with_two_items) 262 263 def test_dict_unpacking_in_dict(self): 264 # See issue 26489 265 self.check_roundtrip(r"""{**{'y': 2}, 'x': 1}""") 266 self.check_roundtrip(r"""{**{'y': 2}, **{'x': 1}}""") 267 268 def test_subscript(self): 269 self.check_roundtrip("a[i]") 270 self.check_roundtrip("a[i,]") 271 self.check_roundtrip("a[i, j]") 272 self.check_roundtrip("a[()]") 273 self.check_roundtrip("a[i:j]") 274 self.check_roundtrip("a[:j]") 275 self.check_roundtrip("a[i:]") 276 self.check_roundtrip("a[i:j:k]") 277 self.check_roundtrip("a[:j:k]") 278 self.check_roundtrip("a[i::k]") 279 self.check_roundtrip("a[i:j,]") 280 self.check_roundtrip("a[i:j, k]") 281 282 283class DirectoryTestCase(ASTTestCase): 284 """Test roundtrip behaviour on all files in Lib and Lib/test.""" 285 NAMES = None 286 287 # test directories, relative to the root of the distribution 288 test_directories = 'Lib', os.path.join('Lib', 'test') 289 290 @classmethod 291 def get_names(cls): 292 if cls.NAMES is not None: 293 return cls.NAMES 294 295 names = [] 296 for d in cls.test_directories: 297 test_dir = os.path.join(basepath, d) 298 for n in os.listdir(test_dir): 299 if n.endswith('.py') and not n.startswith('bad'): 300 names.append(os.path.join(test_dir, n)) 301 302 # Test limited subset of files unless the 'cpu' resource is specified. 303 if not test.support.is_resource_enabled("cpu"): 304 names = random.sample(names, 10) 305 # bpo-31174: Store the names sample to always test the same files. 306 # It prevents false alarms when hunting reference leaks. 307 cls.NAMES = names 308 return names 309 310 def test_files(self): 311 # get names of files to test 312 names = self.get_names() 313 314 for filename in names: 315 if test.support.verbose: 316 print('Testing %s' % filename) 317 318 # Some f-strings are not correctly round-tripped by 319 # Tools/parser/unparse.py. See issue 28002 for details. 320 # We need to skip files that contain such f-strings. 321 if os.path.basename(filename) in ('test_fstring.py', ): 322 if test.support.verbose: 323 print(f'Skipping {filename}: see issue 28002') 324 continue 325 326 with self.subTest(filename=filename): 327 source = read_pyfile(filename) 328 self.check_roundtrip(source) 329 330 331if __name__ == '__main__': 332 unittest.main() 333