Skip to content

Commit 24d9f76

Browse files
committed
Improved test coverage for codeop.py.
* Ported existing tests to cover both the CommandCompiler and Compile classes * Added tests that cover the case when PyCF_ONLY_AST is set * Added tests that cover the case when the compile method returns a code object with flags
1 parent 16562f1 commit 24d9f76

1 file changed

Lines changed: 104 additions & 44 deletions

File tree

Lib/test/test_codeop.py

Lines changed: 104 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,63 @@
77
from test.support import warnings_helper
88
from textwrap import dedent
99

10-
from codeop import compile_command, PyCF_DONT_IMPLY_DEDENT
10+
from codeop import compile_command, CommandCompiler, Compile
11+
from codeop import PyCF_DONT_IMPLY_DEDENT, PyCF_ONLY_AST
12+
import ast
13+
1114

1215
class CodeopTests(unittest.TestCase):
1316

17+
def setUp(self):
18+
self.wrapping_compilers = [compile_command, CommandCompiler()]
19+
self.raw_compilers = [Compile()]
20+
self.compilers = self.wrapping_compilers + self.raw_compilers
21+
1422
def assertValid(self, str, symbol='single'):
1523
'''succeed iff str is a valid piece of code'''
16-
expected = compile(str, "<input>", symbol, PyCF_DONT_IMPLY_DEDENT)
17-
self.assertEqual(compile_command(str, "<input>", symbol), expected)
24+
for compiler in self.compilers:
25+
expected = compile(str, "<input>", symbol, PyCF_DONT_IMPLY_DEDENT)
26+
self.assertEqual(compiler(str, "<input>", symbol), expected)
1827

1928
def assertIncomplete(self, str, symbol='single'):
2029
'''succeed iff str is the start of a valid piece of code'''
21-
self.assertEqual(compile_command(str, symbol=symbol), None)
30+
for compiler in self.wrapping_compilers:
31+
self.assertEqual(compiler(str, "<input>", symbol=symbol), None)
32+
33+
for compiler in self.raw_compilers:
34+
# Compile has should raise like built-in compile
35+
with self.assertRaises(SyntaxError) as cm_original_error:
36+
compile(str, "<input>", symbol, compiler.flags)
37+
expected_error = cm_original_error.exception
38+
with self.assertRaises(type(expected_error)) as cm_wrapped_error:
39+
compiler(str, "<input>", symbol=symbol)
40+
self.assertEqual(
41+
expected_error.args,
42+
cm_wrapped_error.exception.args
43+
)
2244

2345
def assertInvalid(self, str, symbol='single', is_syntax=1):
2446
'''succeed iff str is the start of an invalid piece of code'''
25-
try:
26-
compile_command(str,symbol=symbol)
27-
self.fail("No exception raised for invalid code")
28-
except SyntaxError:
29-
self.assertTrue(is_syntax)
30-
except OverflowError:
31-
self.assertTrue(not is_syntax)
47+
for compiler in self.compilers:
48+
try:
49+
compiler(str,"<input>", symbol=symbol)
50+
self.fail("No exception raised for invalid code")
51+
except SyntaxError:
52+
self.assertTrue(is_syntax)
53+
except OverflowError:
54+
self.assertTrue(not is_syntax)
3255

3356
def test_valid(self):
3457
av = self.assertValid
3558

3659
# special case
37-
self.assertEqual(compile_command(""),
38-
compile("pass", "<input>", 'single',
39-
PyCF_DONT_IMPLY_DEDENT))
40-
self.assertEqual(compile_command("\n"),
41-
compile("pass", "<input>", 'single',
42-
PyCF_DONT_IMPLY_DEDENT))
60+
for compiler in self.wrapping_compilers:
61+
self.assertEqual(
62+
compiler("", "<input>", 'single'),
63+
compile("pass", "<input>", 'single', PyCF_DONT_IMPLY_DEDENT))
64+
self.assertEqual(
65+
compiler("\n", "<input>", 'single'),
66+
compile("pass", "<input>", 'single', PyCF_DONT_IMPLY_DEDENT))
4367

4468
av("a = 1")
4569
av("\na = 1")
@@ -273,49 +297,80 @@ def test_invalid_exec(self):
273297
ai('a await raise b?+1', symbol='exec')
274298

275299
def test_filename(self):
276-
self.assertEqual(compile_command("a = 1\n", "abc").co_filename,
277-
compile("a = 1\n", "abc", 'single').co_filename)
278-
self.assertNotEqual(compile_command("a = 1\n", "abc").co_filename,
279-
compile("a = 1\n", "def", 'single').co_filename)
300+
for compiler in self.compilers:
301+
self.assertEqual(
302+
compiler("a = 1\n", "abc", "single").co_filename,
303+
compile("a = 1\n", "abc", 'single').co_filename
304+
)
305+
self.assertNotEqual(
306+
compiler("a = 1\n", "abc", "single").co_filename,
307+
compile("a = 1\n", "def", 'single').co_filename
308+
)
309+
310+
def assertReturnsModule(self, code, compiler):
311+
retval = compiler(code, "<input>", 'exec', PyCF_ONLY_AST)
312+
self.assertIsInstance(retval, ast.Module)
313+
314+
def test_ast_return_value(self):
315+
for compiler in self.raw_compilers:
316+
validate_ast = self.assertReturnsModule
317+
validate_ast("x = 5", compiler)
318+
validate_ast("\nx = 5", compiler)
319+
validate_ast("x = 5\n", compiler)
320+
validate_ast("x = 5\n\n", compiler)
321+
validate_ast("\n\nx = 5\n\n", compiler)
280322

281323
def test_warning(self):
282324
# Test that the warning is only returned once.
283-
with warnings_helper.check_warnings(
284-
('"is" with \'str\' literal', SyntaxWarning),
285-
('"\\\\e" is an invalid escape sequence', SyntaxWarning),
286-
) as w:
287-
compile_command(r"'\e' is 0")
325+
for compiler in self.compilers:
326+
with warnings_helper.check_warnings(
327+
('"is" with \'str\' literal', SyntaxWarning),
328+
('"\\\\e" is an invalid escape sequence', SyntaxWarning),
329+
) as w:
330+
compiler(r"'\e' is 0", "<input>", "single")
288331
self.assertEqual(len(w.warnings), 2)
289332

290-
# bpo-41520: check SyntaxWarning treated as an SyntaxError
291-
with warnings.catch_warnings(), self.assertRaises(SyntaxError):
292-
warnings.simplefilter('error', SyntaxWarning)
293-
compile_command('1 is 1', symbol='exec')
333+
# bpo-41520: check SyntaxWarning treated as an SyntaxError
334+
with warnings.catch_warnings(), self.assertRaises(SyntaxError):
335+
warnings.simplefilter('error', SyntaxWarning)
336+
compiler('1 is 1', "<input>", 'exec')
294337

295-
# Check SyntaxWarning treated as an SyntaxError
296-
with warnings.catch_warnings(), self.assertRaises(SyntaxError):
297-
warnings.simplefilter('error', SyntaxWarning)
298-
compile_command(r"'\e'", symbol='exec')
338+
# Check SyntaxWarning treated as an SyntaxError
339+
with warnings.catch_warnings(), self.assertRaises(SyntaxError):
340+
warnings.simplefilter('error', SyntaxWarning)
341+
compiler(r"'\e'", "<input>", 'exec')
299342

300343
def test_incomplete_warning(self):
301-
with warnings.catch_warnings(record=True) as w:
302-
warnings.simplefilter('always')
303-
self.assertIncomplete("'\\e' + (")
304-
self.assertEqual(w, [])
344+
code = "'\\e' + ("
345+
for compiler in self.wrapping_compilers:
346+
with warnings.catch_warnings(record=True) as w:
347+
warnings.simplefilter('always')
348+
compiler(code)
349+
self.assertEqual(w, [])
350+
for compiler in self.raw_compilers:
351+
warnings_cm = warnings_helper.check_warnings(
352+
('"\\\\e" is an invalid esceape sequence', SyntaxWarning)
353+
)
354+
with self.assertRaises(SyntaxError), warnings_cm as w:
355+
compiler = self.compilers[-1]
356+
compiler(code, "<input>", 'single')
357+
self.assertEqual(len(w.warnings), 1)
305358

306359
def test_invalid_warning(self):
307360
with warnings.catch_warnings(record=True) as w:
308361
warnings.simplefilter('always')
309362
self.assertInvalid("'\\e' 1")
310-
self.assertEqual(len(w), 1)
311-
self.assertEqual(w[0].category, SyntaxWarning)
312-
self.assertRegex(str(w[0].message), 'invalid escape sequence')
313-
self.assertEqual(w[0].filename, '<input>')
363+
self.assertEqual(len(w), len(self.compilers))
364+
for warning in w:
365+
self.assertEqual(warning.category, SyntaxWarning)
366+
self.assertRegex(str(warning), 'invalid escape sequence')
367+
self.assertEqual(warning.filename, '<input>')
314368

315369
def assertSyntaxErrorMatches(self, code, message):
316370
with self.subTest(code):
317-
with self.assertRaisesRegex(SyntaxError, message):
318-
compile_command(code, symbol='exec')
371+
for compiler in self.compilers:
372+
with self.assertRaisesRegex(SyntaxError, message):
373+
compiler(code, "<input>", 'exec')
319374

320375
def test_syntax_errors(self):
321376
self.assertSyntaxErrorMatches(
@@ -324,6 +379,11 @@ def foo(x,x):
324379
pass
325380
"""), "duplicate parameter 'x' in function definition")
326381

382+
def test_future_imports(self):
383+
for compiler in self.raw_compilers:
384+
original_flags = compiler.flags
385+
compiler('from __future__ import annotations', "<input>", 'single')
386+
self.assertGreater(compiler.flags, original_flags)
327387

328388

329389
if __name__ == "__main__":

0 commit comments

Comments
 (0)