Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6ce60f1

Browse filesBrowse files
donBarbossobolevn
andauthored
gh-131178: Add tests for ast command-line interface (#133329)
Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent 40be123 commit 6ce60f1
Copy full SHA for 6ce60f1

File tree

2 files changed

+168
-18
lines changed
Filter options

2 files changed

+168
-18
lines changed

‎Lib/ast.py

Copy file name to clipboardExpand all lines: Lib/ast.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ def unparse(ast_obj):
626626
return unparser.visit(ast_obj)
627627

628628

629-
def main():
629+
def main(args=None):
630630
import argparse
631631
import sys
632632

@@ -643,7 +643,7 @@ def main():
643643
'column offsets')
644644
parser.add_argument('-i', '--indent', type=int, default=3,
645645
help='indentation of nodes (number of spaces)')
646-
args = parser.parse_args()
646+
args = parser.parse_args(args)
647647

648648
if args.infile == '-':
649649
name = '<stdin>'

‎Lib/test/test_ast/test_ast.py

Copy file name to clipboardExpand all lines: Lib/test/test_ast/test_ast.py
+166-16Lines changed: 166 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import _ast_unparse
22
import ast
33
import builtins
4+
import contextlib
45
import copy
56
import dis
67
import enum
8+
import itertools
79
import os
810
import re
911
import sys
12+
import tempfile
1013
import textwrap
1114
import types
1215
import unittest
1316
import weakref
17+
from io import StringIO
1418
from pathlib import Path
1519
from textwrap import dedent
1620
try:
@@ -19,7 +23,7 @@
1923
_testinternalcapi = None
2024

2125
from test import support
22-
from test.support import os_helper, script_helper
26+
from test.support import os_helper
2327
from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow
2428
from test.support.ast_helper import ASTTestMixin
2529
from test.test_ast.utils import to_tuple
@@ -3232,23 +3236,169 @@ def test_subinterpreter(self):
32323236
self.assertEqual(res, 0)
32333237

32343238

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)
32373243

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+
''')
32473282

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)
32523402

32533403

32543404
class ASTOptimiziationTests(unittest.TestCase):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.