From 864a434f1dc3ac89ab6461fd709b5543509e14f7 Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Thu, 17 Dec 2015 13:31:58 -0500 Subject: [PATCH 1/2] Include Python 3.5 as a supported version --- .travis.yml | 3 ++- setup.py | 1 + tox.ini | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8ba5b681..cc1ed984 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: python -python: 3.3 +python: 3.5 env: - TOX_ENV=py33 - TOX_ENV=py34 + - TOX_ENV=py35 install: - pip install tox diff --git a/setup.py b/setup.py index 2454224f..d60862b1 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ def find_version(*file_paths): "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Testing", ], install_requires=[ diff --git a/tox.ini b/tox.ini index 6fb5a1ca..1307c2eb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py33, py34 +envlist = py33, py34, py35 [testenv] deps = From a4afe3652b77518457e887013ef417c730d41656 Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Thu, 17 Dec 2015 12:11:52 -0500 Subject: [PATCH 2/2] 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. --- pytest_asyncio/plugin.py | 7 ++- tests/test_simple_35.py | 99 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 tests/test_simple_35.py diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 185a6d32..4c690466 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -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: " @@ -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): diff --git a/tests/test_simple_35.py b/tests/test_simple_35.py new file mode 100644 index 00000000..745bba60 --- /dev/null +++ b/tests/test_simple_35.py @@ -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'))