|
1 | 1 | # Python test set -- built-in functions |
2 | 2 |
|
3 | 3 | import ast |
| 4 | +import asyncio |
4 | 5 | import builtins |
5 | 6 | import collections |
6 | 7 | import decimal |
|
18 | 19 | import unittest |
19 | 20 | import warnings |
20 | 21 | from contextlib import ExitStack |
| 22 | +from inspect import CO_COROUTINE |
| 23 | +from itertools import product |
| 24 | +from textwrap import dedent |
| 25 | +from types import AsyncGeneratorType, FunctionType |
21 | 26 | from operator import neg |
22 | 27 | from test.support import ( |
23 | | - EnvironmentVarGuard, TESTFN, check_warnings, swap_attr, unlink) |
| 28 | + EnvironmentVarGuard, TESTFN, check_warnings, swap_attr, unlink, |
| 29 | + maybe_get_event_loop_policy) |
24 | 30 | from test.support.script_helper import assert_python_ok |
25 | 31 | from unittest.mock import MagicMock, patch |
26 | 32 | try: |
@@ -358,6 +364,71 @@ def f(): """doc""" |
358 | 364 | rv = ns['f']() |
359 | 365 | self.assertEqual(rv, tuple(expected)) |
360 | 366 |
|
| 367 | + def test_compile_top_level_await(self): |
| 368 | + """Test whether code some top level await can be compiled. |
| 369 | +
|
| 370 | + Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag set, |
| 371 | + and make sure the generated code object has the CO_COROUTINE flag set in |
| 372 | + order to execute it with `await eval(.....)` instead of exec, or via a |
| 373 | + FunctionType. |
| 374 | + """ |
| 375 | + |
| 376 | + # helper function just to check we can run top=level async-for |
| 377 | + async def arange(n): |
| 378 | + for i in range(n): |
| 379 | + yield i |
| 380 | + |
| 381 | + modes = ('single', 'exec') |
| 382 | + code_samples = ['''a = await asyncio.sleep(0, result=1)''', |
| 383 | + '''async for i in arange(1): |
| 384 | + a = 1''', |
| 385 | + '''async with asyncio.Lock() as l: |
| 386 | + a = 1'''] |
| 387 | + policy = maybe_get_event_loop_policy() |
| 388 | + try: |
| 389 | + for mode, code_sample in product(modes,code_samples): |
| 390 | + source = dedent(code_sample) |
| 391 | + with self.assertRaises(SyntaxError, msg=f"{source=} {mode=}"): |
| 392 | + compile(source, '?' , mode) |
| 393 | + |
| 394 | + co = compile(source, |
| 395 | + '?', |
| 396 | + mode, |
| 397 | + flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) |
| 398 | + |
| 399 | + self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE, |
| 400 | + msg=f"{source=} {mode=}") |
| 401 | + |
| 402 | + |
| 403 | + # test we can create and advance a function type |
| 404 | + globals_ = {'asyncio': asyncio, 'a':0, 'arange': arange} |
| 405 | + async_f = FunctionType(co, globals_) |
| 406 | + asyncio.run(async_f()) |
| 407 | + self.assertEqual(globals_['a'], 1) |
| 408 | + |
| 409 | + # test we can await-eval, |
| 410 | + globals_ = {'asyncio': asyncio, 'a':0, 'arange': arange} |
| 411 | + asyncio.run(eval(co, globals_)) |
| 412 | + self.assertEqual(globals_['a'], 1) |
| 413 | + finally: |
| 414 | + asyncio.set_event_loop_policy(policy) |
| 415 | + |
| 416 | + def test_compile_async_generator(self): |
| 417 | + """ |
| 418 | + With the PyCF_ALLOW_TOP_LEVEL_AWAIT flag added in 3.8, we want to |
| 419 | + make sure AsyncGenerators are still properly not marked with CO_COROUTINE |
| 420 | + """ |
| 421 | + code = dedent("""async def ticker(): |
| 422 | + for i in range(10): |
| 423 | + yield i |
| 424 | + await asyncio.sleep(0)""") |
| 425 | + |
| 426 | + co = compile(code, '?', 'exec', flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) |
| 427 | + glob = {} |
| 428 | + exec(co, glob) |
| 429 | + self.assertEqual(type(glob['ticker']()), AsyncGeneratorType) |
| 430 | + |
| 431 | + |
361 | 432 | def test_delattr(self): |
362 | 433 | sys.spam = 1 |
363 | 434 | delattr(sys, 'spam') |
|
0 commit comments