|
1 | 1 | import _ast_unparse
|
2 | 2 | import ast
|
3 | 3 | import builtins
|
| 4 | +import contextlib |
4 | 5 | import copy
|
5 | 6 | import dis
|
6 | 7 | import enum
|
| 8 | +import itertools |
7 | 9 | import os
|
8 | 10 | import re
|
9 | 11 | import sys
|
| 12 | +import tempfile |
10 | 13 | import textwrap
|
11 | 14 | import types
|
12 | 15 | import unittest
|
13 | 16 | import weakref
|
| 17 | +from io import StringIO |
14 | 18 | from pathlib import Path
|
15 | 19 | from textwrap import dedent
|
16 | 20 | try:
|
|
19 | 23 | _testinternalcapi = None
|
20 | 24 |
|
21 | 25 | from test import support
|
22 |
| -from test.support import os_helper, script_helper |
| 26 | +from test.support import os_helper |
23 | 27 | from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow
|
24 | 28 | from test.support.ast_helper import ASTTestMixin
|
25 | 29 | from test.test_ast.utils import to_tuple
|
@@ -3232,23 +3236,169 @@ def test_subinterpreter(self):
|
3232 | 3236 | self.assertEqual(res, 0)
|
3233 | 3237 |
|
3234 | 3238 |
|
3235 |
| -class ASTMainTests(unittest.TestCase): |
3236 |
| - # Tests `ast.main()` function. |
| 3239 | +class CommandLineTests(unittest.TestCase): |
| 3240 | + def setUp(self): |
| 3241 | + self.filename = tempfile.mktemp() |
| 3242 | + self.addCleanup(os_helper.unlink, self.filename) |
3237 | 3243 |
|
3238 |
| - def test_cli_file_input(self): |
3239 |
| - code = "print(1, 2, 3)" |
3240 |
| - expected = ast.dump(ast.parse(code), indent=3) |
3241 |
| - |
3242 |
| - with os_helper.temp_dir() as tmp_dir: |
3243 |
| - filename = os.path.join(tmp_dir, "test_module.py") |
3244 |
| - with open(filename, 'w', encoding='utf-8') as f: |
3245 |
| - f.write(code) |
3246 |
| - res, _ = script_helper.run_python_until_end("-m", "ast", filename) |
| 3244 | + @staticmethod |
| 3245 | + def text_normalize(string): |
| 3246 | + return textwrap.dedent(string).strip() |
| 3247 | + |
| 3248 | + def set_source(self, content): |
| 3249 | + Path(self.filename).write_text(self.text_normalize(content)) |
| 3250 | + |
| 3251 | + def invoke_ast(self, *flags): |
| 3252 | + stderr = StringIO() |
| 3253 | + stdout = StringIO() |
| 3254 | + with ( |
| 3255 | + contextlib.redirect_stdout(stdout), |
| 3256 | + contextlib.redirect_stderr(stderr), |
| 3257 | + ): |
| 3258 | + ast.main(args=[*flags, self.filename]) |
| 3259 | + self.assertEqual(stderr.getvalue(), '') |
| 3260 | + return stdout.getvalue().strip() |
| 3261 | + |
| 3262 | + def check_output(self, source, expect, *flags): |
| 3263 | + self.set_source(source) |
| 3264 | + res = self.invoke_ast(*flags) |
| 3265 | + expect = self.text_normalize(expect) |
| 3266 | + self.assertEqual(res, expect) |
| 3267 | + |
| 3268 | + def test_invocation(self): |
| 3269 | + # test various combinations of parameters |
| 3270 | + base_flags = ( |
| 3271 | + ('-m=exec', '--mode=exec'), |
| 3272 | + ('--no-type-comments', '--no-type-comments'), |
| 3273 | + ('-a', '--include-attributes'), |
| 3274 | + ('-i=4', '--indent=4'), |
| 3275 | + ) |
| 3276 | + self.set_source(''' |
| 3277 | + print(1, 2, 3) |
| 3278 | + def f(x: int) -> int: |
| 3279 | + x -= 1 |
| 3280 | + return x |
| 3281 | + ''') |
3247 | 3282 |
|
3248 |
| - self.assertEqual(res.err, b"") |
3249 |
| - self.assertEqual(expected.splitlines(), |
3250 |
| - res.out.decode("utf8").splitlines()) |
3251 |
| - self.assertEqual(res.rc, 0) |
| 3283 | + for r in range(1, len(base_flags) + 1): |
| 3284 | + for choices in itertools.combinations(base_flags, r=r): |
| 3285 | + for args in itertools.product(*choices): |
| 3286 | + with self.subTest(flags=args): |
| 3287 | + self.invoke_ast(*args) |
| 3288 | + |
| 3289 | + def test_help_message(self): |
| 3290 | + for flag in ('-h', '--help', '--unknown'): |
| 3291 | + with self.subTest(flag=flag): |
| 3292 | + output = StringIO() |
| 3293 | + with self.assertRaises(SystemExit): |
| 3294 | + with contextlib.redirect_stderr(output): |
| 3295 | + ast.main(args=flag) |
| 3296 | + self.assertStartsWith(output.getvalue(), 'usage: ') |
| 3297 | + |
| 3298 | + def test_exec_mode_flag(self): |
| 3299 | + # test 'python -m ast -m/--mode exec' |
| 3300 | + source = 'x: bool = 1 # type: ignore[assignment]' |
| 3301 | + expect = ''' |
| 3302 | + Module( |
| 3303 | + body=[ |
| 3304 | + AnnAssign( |
| 3305 | + target=Name(id='x', ctx=Store()), |
| 3306 | + annotation=Name(id='bool', ctx=Load()), |
| 3307 | + value=Constant(value=1), |
| 3308 | + simple=1)], |
| 3309 | + type_ignores=[ |
| 3310 | + TypeIgnore(lineno=1, tag='[assignment]')]) |
| 3311 | + ''' |
| 3312 | + for flag in ('-m=exec', '--mode=exec'): |
| 3313 | + with self.subTest(flag=flag): |
| 3314 | + self.check_output(source, expect, flag) |
| 3315 | + |
| 3316 | + def test_single_mode_flag(self): |
| 3317 | + # test 'python -m ast -m/--mode single' |
| 3318 | + source = 'pass' |
| 3319 | + expect = ''' |
| 3320 | + Interactive( |
| 3321 | + body=[ |
| 3322 | + Pass()]) |
| 3323 | + ''' |
| 3324 | + for flag in ('-m=single', '--mode=single'): |
| 3325 | + with self.subTest(flag=flag): |
| 3326 | + self.check_output(source, expect, flag) |
| 3327 | + |
| 3328 | + def test_eval_mode_flag(self): |
| 3329 | + # test 'python -m ast -m/--mode eval' |
| 3330 | + source = 'print(1, 2, 3)' |
| 3331 | + expect = ''' |
| 3332 | + Expression( |
| 3333 | + body=Call( |
| 3334 | + func=Name(id='print', ctx=Load()), |
| 3335 | + args=[ |
| 3336 | + Constant(value=1), |
| 3337 | + Constant(value=2), |
| 3338 | + Constant(value=3)])) |
| 3339 | + ''' |
| 3340 | + for flag in ('-m=eval', '--mode=eval'): |
| 3341 | + with self.subTest(flag=flag): |
| 3342 | + self.check_output(source, expect, flag) |
| 3343 | + |
| 3344 | + def test_func_type_mode_flag(self): |
| 3345 | + # test 'python -m ast -m/--mode func_type' |
| 3346 | + source = '(int, str) -> list[int]' |
| 3347 | + expect = ''' |
| 3348 | + FunctionType( |
| 3349 | + argtypes=[ |
| 3350 | + Name(id='int', ctx=Load()), |
| 3351 | + Name(id='str', ctx=Load())], |
| 3352 | + returns=Subscript( |
| 3353 | + value=Name(id='list', ctx=Load()), |
| 3354 | + slice=Name(id='int', ctx=Load()), |
| 3355 | + ctx=Load())) |
| 3356 | + ''' |
| 3357 | + for flag in ('-m=func_type', '--mode=func_type'): |
| 3358 | + with self.subTest(flag=flag): |
| 3359 | + self.check_output(source, expect, flag) |
| 3360 | + |
| 3361 | + def test_no_type_comments_flag(self): |
| 3362 | + # test 'python -m ast --no-type-comments' |
| 3363 | + source = 'x: bool = 1 # type: ignore[assignment]' |
| 3364 | + expect = ''' |
| 3365 | + Module( |
| 3366 | + body=[ |
| 3367 | + AnnAssign( |
| 3368 | + target=Name(id='x', ctx=Store()), |
| 3369 | + annotation=Name(id='bool', ctx=Load()), |
| 3370 | + value=Constant(value=1), |
| 3371 | + simple=1)]) |
| 3372 | + ''' |
| 3373 | + self.check_output(source, expect, '--no-type-comments') |
| 3374 | + |
| 3375 | + def test_include_attributes_flag(self): |
| 3376 | + # test 'python -m ast -a/--include-attributes' |
| 3377 | + source = 'pass' |
| 3378 | + expect = ''' |
| 3379 | + Module( |
| 3380 | + body=[ |
| 3381 | + Pass( |
| 3382 | + lineno=1, |
| 3383 | + col_offset=0, |
| 3384 | + end_lineno=1, |
| 3385 | + end_col_offset=4)]) |
| 3386 | + ''' |
| 3387 | + for flag in ('-a', '--include-attributes'): |
| 3388 | + with self.subTest(flag=flag): |
| 3389 | + self.check_output(source, expect, flag) |
| 3390 | + |
| 3391 | + def test_indent_flag(self): |
| 3392 | + # test 'python -m ast -i/--indent' |
| 3393 | + source = 'pass' |
| 3394 | + expect = ''' |
| 3395 | + Module( |
| 3396 | + body=[ |
| 3397 | + Pass()]) |
| 3398 | + ''' |
| 3399 | + for flag in ('-i=0', '--indent=0'): |
| 3400 | + with self.subTest(flag=flag): |
| 3401 | + self.check_output(source, expect, flag) |
3252 | 3402 |
|
3253 | 3403 |
|
3254 | 3404 | class ASTOptimiziationTests(unittest.TestCase):
|
|
0 commit comments