Skip to content

Commit 5648fb1

Browse files
committed
Support coroutines with async and await syntax
PEP 492 added support for defining coroutines using `async def` rather than having to decorate the function with `@asyncio.coroutine`. The new `_is_coroutine` function will match any coroutines created with the decorator using `inspect.isgeneratorfunction` as well as those created through the async and await syntax using `asyncio.iscoroutinefunction`. The tests for this need to be added in an unconventional way. `async` and `await` cause syntax errors in versions of Python prior to 3.5. Rather than causing the tests for 3.3 and 3.4 to fail, the tests are being defined as a string and then compiled and executed into the module. If a time ever comes that this library no longer supports versions prior to 3.5, this new module can be removed entirely.
1 parent 763576c commit 5648fb1

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

pytest_asyncio/plugin.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
import pytest
77

88

9+
def _is_coroutine(obj):
10+
"""Check to see if an object is really an asyncio coroutine."""
11+
return asyncio.iscoroutinefunction(obj) or inspect.isgeneratorfunction(obj)
12+
13+
914
def pytest_configure(config):
1015
config.addinivalue_line("markers",
1116
"asyncio: "
@@ -20,7 +25,7 @@ def pytest_configure(config):
2025

2126
@pytest.mark.tryfirst
2227
def pytest_pycollect_makeitem(collector, name, obj):
23-
if collector.funcnamefilter(name) and inspect.isgeneratorfunction(obj):
28+
if collector.funcnamefilter(name) and _is_coroutine(obj):
2429
item = pytest.Function(name, parent=collector)
2530
if ('asyncio' in item.keywords or
2631
'asyncio_process_pool' in item.keywords):

tests/test_simple_35.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Quick'n'dirty unit tests using async and await syntax."""
2+
3+
import asyncio
4+
import sys
5+
6+
import pytest
7+
8+
pytestmark = pytest.mark.skipif(
9+
sys.version_info[:2] < (3, 5),
10+
reason='This syntax is only valid in 3.5 and newer')
11+
12+
13+
@asyncio.coroutine
14+
def async_coro(loop):
15+
yield from asyncio.sleep(0, loop=loop)
16+
return 'ok'
17+
18+
19+
# PEP 492 added the syntax for these tests to Python 3.5. Older versions
20+
# can't even parse the module to let skipif exclude the tests. To get
21+
# around this, all tests are defined in a string which can be compiled
22+
# and executed on appropriate versions of Python.
23+
_possible_tests = '''
24+
@pytest.mark.asyncio
25+
async def test_asyncio_marker():
26+
"""Test the asyncio pytest marker."""
27+
28+
29+
@pytest.mark.asyncio
30+
async def test_asyncio_marker_with_default_param(a_param=None):
31+
"""Test the asyncio pytest marker."""
32+
33+
34+
@pytest.mark.asyncio_process_pool
35+
async def test_asyncio_process_pool_marker(event_loop):
36+
ret = await async_coro(event_loop)
37+
assert ret == 'ok'
38+
39+
40+
@pytest.mark.asyncio
41+
async def test_unused_port_fixture(unused_tcp_port, event_loop):
42+
"""Test the unused TCP port fixture."""
43+
async def closer(_, writer):
44+
writer.close()
45+
46+
server1 = await asyncio.start_server(closer, host='localhost',
47+
port=unused_tcp_port,
48+
loop=event_loop)
49+
50+
server1.close()
51+
await server1.wait_closed()
52+
53+
54+
@pytest.mark.asyncio
55+
async def test_unused_port_factory_fixture(unused_tcp_port_factory, event_loop):
56+
"""Test the unused TCP port factory fixture."""
57+
58+
async def closer(_, writer):
59+
writer.close()
60+
61+
port1, port2, port3 = (unused_tcp_port_factory(), unused_tcp_port_factory(),
62+
unused_tcp_port_factory())
63+
64+
server1 = await asyncio.start_server(closer, host='localhost',
65+
port=port1,
66+
loop=event_loop)
67+
server2 = await asyncio.start_server(closer, host='localhost',
68+
port=port2,
69+
loop=event_loop)
70+
server3 = await asyncio.start_server(closer, host='localhost',
71+
port=port3,
72+
loop=event_loop)
73+
74+
for port in port1, port2, port3:
75+
with pytest.raises(IOError):
76+
await asyncio.start_server(closer, host='localhost',
77+
port=port,
78+
loop=event_loop)
79+
80+
server1.close()
81+
await server1.wait_closed()
82+
server2.close()
83+
await server2.wait_closed()
84+
server3.close()
85+
await server3.wait_closed()
86+
87+
88+
class Test:
89+
"""Test that asyncio marked functions work in test methods."""
90+
91+
@pytest.mark.asyncio
92+
async def test_asyncio_marker_method(self, event_loop):
93+
"""Test the asyncio pytest marker in a Test class."""
94+
ret = await async_coro(event_loop)
95+
assert ret == 'ok'
96+
'''
97+
98+
if sys.version_info[:2] >= (3, 5):
99+
exec(compile(_possible_tests, __file__, 'exec'))

0 commit comments

Comments
 (0)