mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	gh-131178: Add tests for ast command-line interface (#133329)
				
					
				
			Co-authored-by: sobolevn <mail@sobolevn.me>
This commit is contained in:
		
							parent
							
								
									40be123499
								
							
						
					
					
						commit
						6ce60f1574
					
				
					 2 changed files with 167 additions and 17 deletions
				
			
		|  | @ -626,7 +626,7 @@ def unparse(ast_obj): | ||||||
|     return unparser.visit(ast_obj) |     return unparser.visit(ast_obj) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def main(): | def main(args=None): | ||||||
|     import argparse |     import argparse | ||||||
|     import sys |     import sys | ||||||
| 
 | 
 | ||||||
|  | @ -643,7 +643,7 @@ def main(): | ||||||
|                              'column offsets') |                              'column offsets') | ||||||
|     parser.add_argument('-i', '--indent', type=int, default=3, |     parser.add_argument('-i', '--indent', type=int, default=3, | ||||||
|                         help='indentation of nodes (number of spaces)') |                         help='indentation of nodes (number of spaces)') | ||||||
|     args = parser.parse_args() |     args = parser.parse_args(args) | ||||||
| 
 | 
 | ||||||
|     if args.infile == '-': |     if args.infile == '-': | ||||||
|         name = '<stdin>' |         name = '<stdin>' | ||||||
|  |  | ||||||
|  | @ -1,16 +1,20 @@ | ||||||
| import _ast_unparse | import _ast_unparse | ||||||
| import ast | import ast | ||||||
| import builtins | import builtins | ||||||
|  | import contextlib | ||||||
| import copy | import copy | ||||||
| import dis | import dis | ||||||
| import enum | import enum | ||||||
|  | import itertools | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import sys | import sys | ||||||
|  | import tempfile | ||||||
| import textwrap | import textwrap | ||||||
| import types | import types | ||||||
| import unittest | import unittest | ||||||
| import weakref | import weakref | ||||||
|  | from io import StringIO | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from textwrap import dedent | from textwrap import dedent | ||||||
| try: | try: | ||||||
|  | @ -19,7 +23,7 @@ | ||||||
|     _testinternalcapi = None |     _testinternalcapi = None | ||||||
| 
 | 
 | ||||||
| from test import support | from test import support | ||||||
| from test.support import os_helper, script_helper | from test.support import os_helper | ||||||
| from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow | from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow | ||||||
| from test.support.ast_helper import ASTTestMixin | from test.support.ast_helper import ASTTestMixin | ||||||
| from test.test_ast.utils import to_tuple | from test.test_ast.utils import to_tuple | ||||||
|  | @ -3232,23 +3236,169 @@ def test_subinterpreter(self): | ||||||
|         self.assertEqual(res, 0) |         self.assertEqual(res, 0) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ASTMainTests(unittest.TestCase): | class CommandLineTests(unittest.TestCase): | ||||||
|     # Tests `ast.main()` function. |     def setUp(self): | ||||||
|  |         self.filename = tempfile.mktemp() | ||||||
|  |         self.addCleanup(os_helper.unlink, self.filename) | ||||||
| 
 | 
 | ||||||
|     def test_cli_file_input(self): |     @staticmethod | ||||||
|         code = "print(1, 2, 3)" |     def text_normalize(string): | ||||||
|         expected = ast.dump(ast.parse(code), indent=3) |         return textwrap.dedent(string).strip() | ||||||
| 
 | 
 | ||||||
|         with os_helper.temp_dir() as tmp_dir: |     def set_source(self, content): | ||||||
|             filename = os.path.join(tmp_dir, "test_module.py") |         Path(self.filename).write_text(self.text_normalize(content)) | ||||||
|             with open(filename, 'w', encoding='utf-8') as f: |  | ||||||
|                 f.write(code) |  | ||||||
|             res, _ = script_helper.run_python_until_end("-m", "ast", filename) |  | ||||||
| 
 | 
 | ||||||
|         self.assertEqual(res.err, b"") |     def invoke_ast(self, *flags): | ||||||
|         self.assertEqual(expected.splitlines(), |         stderr = StringIO() | ||||||
|                          res.out.decode("utf8").splitlines()) |         stdout = StringIO() | ||||||
|         self.assertEqual(res.rc, 0) |         with ( | ||||||
|  |             contextlib.redirect_stdout(stdout), | ||||||
|  |             contextlib.redirect_stderr(stderr), | ||||||
|  |         ): | ||||||
|  |             ast.main(args=[*flags, self.filename]) | ||||||
|  |         self.assertEqual(stderr.getvalue(), '') | ||||||
|  |         return stdout.getvalue().strip() | ||||||
|  | 
 | ||||||
|  |     def check_output(self, source, expect, *flags): | ||||||
|  |         self.set_source(source) | ||||||
|  |         res = self.invoke_ast(*flags) | ||||||
|  |         expect = self.text_normalize(expect) | ||||||
|  |         self.assertEqual(res, expect) | ||||||
|  | 
 | ||||||
|  |     def test_invocation(self): | ||||||
|  |         # test various combinations of parameters | ||||||
|  |         base_flags = ( | ||||||
|  |             ('-m=exec', '--mode=exec'), | ||||||
|  |             ('--no-type-comments', '--no-type-comments'), | ||||||
|  |             ('-a', '--include-attributes'), | ||||||
|  |             ('-i=4', '--indent=4'), | ||||||
|  |         ) | ||||||
|  |         self.set_source(''' | ||||||
|  |             print(1, 2, 3) | ||||||
|  |             def f(x: int) -> int: | ||||||
|  |                 x -= 1 | ||||||
|  |                 return x | ||||||
|  |         ''') | ||||||
|  | 
 | ||||||
|  |         for r in range(1, len(base_flags) + 1): | ||||||
|  |             for choices in itertools.combinations(base_flags, r=r): | ||||||
|  |                 for args in itertools.product(*choices): | ||||||
|  |                     with self.subTest(flags=args): | ||||||
|  |                         self.invoke_ast(*args) | ||||||
|  | 
 | ||||||
|  |     def test_help_message(self): | ||||||
|  |         for flag in ('-h', '--help', '--unknown'): | ||||||
|  |             with self.subTest(flag=flag): | ||||||
|  |                 output = StringIO() | ||||||
|  |                 with self.assertRaises(SystemExit): | ||||||
|  |                     with contextlib.redirect_stderr(output): | ||||||
|  |                         ast.main(args=flag) | ||||||
|  |                 self.assertStartsWith(output.getvalue(), 'usage: ') | ||||||
|  | 
 | ||||||
|  |     def test_exec_mode_flag(self): | ||||||
|  |         # test 'python -m ast -m/--mode exec' | ||||||
|  |         source = 'x: bool = 1 # type: ignore[assignment]' | ||||||
|  |         expect = ''' | ||||||
|  |             Module( | ||||||
|  |                body=[ | ||||||
|  |                   AnnAssign( | ||||||
|  |                      target=Name(id='x', ctx=Store()), | ||||||
|  |                      annotation=Name(id='bool', ctx=Load()), | ||||||
|  |                      value=Constant(value=1), | ||||||
|  |                      simple=1)], | ||||||
|  |                type_ignores=[ | ||||||
|  |                   TypeIgnore(lineno=1, tag='[assignment]')]) | ||||||
|  |         ''' | ||||||
|  |         for flag in ('-m=exec', '--mode=exec'): | ||||||
|  |             with self.subTest(flag=flag): | ||||||
|  |                 self.check_output(source, expect, flag) | ||||||
|  | 
 | ||||||
|  |     def test_single_mode_flag(self): | ||||||
|  |         # test 'python -m ast -m/--mode single' | ||||||
|  |         source = 'pass' | ||||||
|  |         expect = ''' | ||||||
|  |             Interactive( | ||||||
|  |                body=[ | ||||||
|  |                   Pass()]) | ||||||
|  |         ''' | ||||||
|  |         for flag in ('-m=single', '--mode=single'): | ||||||
|  |             with self.subTest(flag=flag): | ||||||
|  |                 self.check_output(source, expect, flag) | ||||||
|  | 
 | ||||||
|  |     def test_eval_mode_flag(self): | ||||||
|  |         # test 'python -m ast -m/--mode eval' | ||||||
|  |         source = 'print(1, 2, 3)' | ||||||
|  |         expect = ''' | ||||||
|  |             Expression( | ||||||
|  |                body=Call( | ||||||
|  |                   func=Name(id='print', ctx=Load()), | ||||||
|  |                   args=[ | ||||||
|  |                      Constant(value=1), | ||||||
|  |                      Constant(value=2), | ||||||
|  |                      Constant(value=3)])) | ||||||
|  |         ''' | ||||||
|  |         for flag in ('-m=eval', '--mode=eval'): | ||||||
|  |             with self.subTest(flag=flag): | ||||||
|  |                 self.check_output(source, expect, flag) | ||||||
|  | 
 | ||||||
|  |     def test_func_type_mode_flag(self): | ||||||
|  |         # test 'python -m ast -m/--mode func_type' | ||||||
|  |         source = '(int, str) -> list[int]' | ||||||
|  |         expect = ''' | ||||||
|  |             FunctionType( | ||||||
|  |                argtypes=[ | ||||||
|  |                   Name(id='int', ctx=Load()), | ||||||
|  |                   Name(id='str', ctx=Load())], | ||||||
|  |                returns=Subscript( | ||||||
|  |                   value=Name(id='list', ctx=Load()), | ||||||
|  |                   slice=Name(id='int', ctx=Load()), | ||||||
|  |                   ctx=Load())) | ||||||
|  |         ''' | ||||||
|  |         for flag in ('-m=func_type', '--mode=func_type'): | ||||||
|  |             with self.subTest(flag=flag): | ||||||
|  |                 self.check_output(source, expect, flag) | ||||||
|  | 
 | ||||||
|  |     def test_no_type_comments_flag(self): | ||||||
|  |         # test 'python -m ast --no-type-comments' | ||||||
|  |         source = 'x: bool = 1 # type: ignore[assignment]' | ||||||
|  |         expect = ''' | ||||||
|  |             Module( | ||||||
|  |                body=[ | ||||||
|  |                   AnnAssign( | ||||||
|  |                      target=Name(id='x', ctx=Store()), | ||||||
|  |                      annotation=Name(id='bool', ctx=Load()), | ||||||
|  |                      value=Constant(value=1), | ||||||
|  |                      simple=1)]) | ||||||
|  |         ''' | ||||||
|  |         self.check_output(source, expect, '--no-type-comments') | ||||||
|  | 
 | ||||||
|  |     def test_include_attributes_flag(self): | ||||||
|  |         # test 'python -m ast -a/--include-attributes' | ||||||
|  |         source = 'pass' | ||||||
|  |         expect = ''' | ||||||
|  |             Module( | ||||||
|  |                body=[ | ||||||
|  |                   Pass( | ||||||
|  |                      lineno=1, | ||||||
|  |                      col_offset=0, | ||||||
|  |                      end_lineno=1, | ||||||
|  |                      end_col_offset=4)]) | ||||||
|  |         ''' | ||||||
|  |         for flag in ('-a', '--include-attributes'): | ||||||
|  |             with self.subTest(flag=flag): | ||||||
|  |                 self.check_output(source, expect, flag) | ||||||
|  | 
 | ||||||
|  |     def test_indent_flag(self): | ||||||
|  |         # test 'python -m ast -i/--indent' | ||||||
|  |         source = 'pass' | ||||||
|  |         expect = ''' | ||||||
|  |             Module( | ||||||
|  |             body=[ | ||||||
|  |             Pass()]) | ||||||
|  |         ''' | ||||||
|  |         for flag in ('-i=0', '--indent=0'): | ||||||
|  |             with self.subTest(flag=flag): | ||||||
|  |                 self.check_output(source, expect, flag) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ASTOptimiziationTests(unittest.TestCase): | class ASTOptimiziationTests(unittest.TestCase): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Semyon Moroz
						Semyon Moroz