Skip to content

Commit

Permalink
Support coroutines with async and await syntax
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dirn committed Dec 17, 2015
1 parent 763576c commit 5648fb1
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 1 deletion.
7 changes: 6 additions & 1 deletion pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
import pytest


def _is_coroutine(obj):
"""Check to see if an object is really an asyncio coroutine."""
return asyncio.iscoroutinefunction(obj) or inspect.isgeneratorfunction(obj)


def pytest_configure(config):
config.addinivalue_line("markers",
"asyncio: "
Expand All @@ -20,7 +25,7 @@ def pytest_configure(config):

@pytest.mark.tryfirst
def pytest_pycollect_makeitem(collector, name, obj):
if collector.funcnamefilter(name) and inspect.isgeneratorfunction(obj):
if collector.funcnamefilter(name) and _is_coroutine(obj):
item = pytest.Function(name, parent=collector)
if ('asyncio' in item.keywords or
'asyncio_process_pool' in item.keywords):
Expand Down
99 changes: 99 additions & 0 deletions tests/test_simple_35.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Quick'n'dirty unit tests using async and await syntax."""

import asyncio
import sys

import pytest

pytestmark = pytest.mark.skipif(
sys.version_info[:2] < (3, 5),
reason='This syntax is only valid in 3.5 and newer')


@asyncio.coroutine
def async_coro(loop):
yield from asyncio.sleep(0, loop=loop)
return 'ok'


# PEP 492 added the syntax for these tests to Python 3.5. Older versions
# can't even parse the module to let skipif exclude the tests. To get
# around this, all tests are defined in a string which can be compiled
# and executed on appropriate versions of Python.
_possible_tests = '''
@pytest.mark.asyncio
async def test_asyncio_marker():
"""Test the asyncio pytest marker."""
@pytest.mark.asyncio
async def test_asyncio_marker_with_default_param(a_param=None):
"""Test the asyncio pytest marker."""
@pytest.mark.asyncio_process_pool
async def test_asyncio_process_pool_marker(event_loop):
ret = await async_coro(event_loop)
assert ret == 'ok'
@pytest.mark.asyncio
async def test_unused_port_fixture(unused_tcp_port, event_loop):
"""Test the unused TCP port fixture."""
async def closer(_, writer):
writer.close()
server1 = await asyncio.start_server(closer, host='localhost',
port=unused_tcp_port,
loop=event_loop)
server1.close()
await server1.wait_closed()
@pytest.mark.asyncio
async def test_unused_port_factory_fixture(unused_tcp_port_factory, event_loop):
"""Test the unused TCP port factory fixture."""
async def closer(_, writer):
writer.close()
port1, port2, port3 = (unused_tcp_port_factory(), unused_tcp_port_factory(),
unused_tcp_port_factory())
server1 = await asyncio.start_server(closer, host='localhost',
port=port1,
loop=event_loop)
server2 = await asyncio.start_server(closer, host='localhost',
port=port2,
loop=event_loop)
server3 = await asyncio.start_server(closer, host='localhost',
port=port3,
loop=event_loop)
for port in port1, port2, port3:
with pytest.raises(IOError):
await asyncio.start_server(closer, host='localhost',
port=port,
loop=event_loop)
server1.close()
await server1.wait_closed()
server2.close()
await server2.wait_closed()
server3.close()
await server3.wait_closed()
class Test:
"""Test that asyncio marked functions work in test methods."""
@pytest.mark.asyncio
async def test_asyncio_marker_method(self, event_loop):
"""Test the asyncio pytest marker in a Test class."""
ret = await async_coro(event_loop)
assert ret == 'ok'
'''

if sys.version_info[:2] >= (3, 5):
exec(compile(_possible_tests, __file__, 'exec'))

0 comments on commit 5648fb1

Please sign in to comment.