diff --git a/CHANGES/2565.bugfix b/CHANGES/2565.bugfix new file mode 100644 index 00000000000..4fe65a7d1c6 --- /dev/null +++ b/CHANGES/2565.bugfix @@ -0,0 +1 @@ +Fix compatibility with `pytest` 3.3+ diff --git a/aiohttp/pytest_plugin.py b/aiohttp/pytest_plugin.py index 9c8679562fb..d60ef03c64a 100644 --- a/aiohttp/pytest_plugin.py +++ b/aiohttp/pytest_plugin.py @@ -96,8 +96,14 @@ def finalizer(): @pytest.fixture def fast(request): - """ --fast config option """ - return request.config.getoption('--fast') # pragma: no cover + """--fast config option""" + return request.config.getoption('--fast') + + +@pytest.fixture +def loop_debug(request): + """--enable-loop-debug config option""" + return request.config.getoption('--enable-loop-debug') @contextlib.contextmanager @@ -162,50 +168,47 @@ def pytest_pyfunc_call(pyfuncitem): return True -def pytest_configure(config): - loops = config.getoption('--loop') +def pytest_generate_tests(metafunc): + if 'loop_factory' not in metafunc.fixturenames: + return - factories = {'pyloop': asyncio.new_event_loop} + loops = metafunc.config.option.loop + avail_factories = {'pyloop': asyncio.new_event_loop} if uvloop is not None: # pragma: no cover - factories['uvloop'] = uvloop.new_event_loop + avail_factories['uvloop'] = uvloop.new_event_loop if tokio is not None: # pragma: no cover - factories['tokio'] = tokio.new_event_loop - - LOOP_FACTORIES.clear() - LOOP_FACTORY_IDS.clear() + avail_factories['tokio'] = tokio.new_event_loop if loops == 'all': loops = 'pyloop,uvloop?,tokio?' + factories = {} for name in loops.split(','): required = not name.endswith('?') name = name.strip(' ?') - if name in factories: - LOOP_FACTORIES.append(factories[name]) - LOOP_FACTORY_IDS.append(name) - elif required: - raise ValueError( - "Unknown loop '%s', available loops: %s" % ( - name, list(factories.keys()))) - asyncio.set_event_loop(None) + if name not in avail_factories: # pragma: no cover + if required: + raise ValueError( + "Unknown loop '%s', available loops: %s" % ( + name, list(factories.keys()))) + else: + continue + factories[name] = avail_factories[name] + metafunc.parametrize("loop_factory", + list(factories.values()), + ids=list(factories.keys())) -LOOP_FACTORIES = [] -LOOP_FACTORY_IDS = [] - - -@pytest.fixture(params=LOOP_FACTORIES, ids=LOOP_FACTORY_IDS) -def loop(request): +@pytest.fixture +def loop(loop_factory, fast, loop_debug): """Return an instance of the event loop.""" - fast = request.config.getoption('--fast') - debug = request.config.getoption('--enable-loop-debug') - - with loop_context(request.param, fast=fast) as _loop: - if debug: + with loop_context(loop_factory, fast=fast) as _loop: + if loop_debug: _loop.set_debug(True) # pragma: no cover yield _loop + asyncio.set_event_loop(None) @pytest.fixture diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index a62d5ab3b38..ef696ee5928 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -3,11 +3,13 @@ import pytest -from aiohttp.pytest_plugin import LOOP_FACTORIES - pytest_plugins = 'pytester' +CONFTEST = ''' +pytest_plugins = 'aiohttp.pytest_plugin' +''' + def test_aiohttp_plugin(testdir): testdir.makepyfile("""\ @@ -17,10 +19,6 @@ def test_aiohttp_plugin(testdir): from aiohttp import web - -pytest_plugins = 'aiohttp.pytest_plugin' - - @asyncio.coroutine def hello(request): return web.Response(body=b'Hello, world') @@ -72,7 +70,7 @@ def test_hello_fails(test_client): @asyncio.coroutine def test_hello_with_fake_loop(test_client): - with pytest.raises(AssertionError): + with pytest.raises(RuntimeError): fake_loop = mock.Mock() yield from test_client(web.Application(loop=fake_loop)) @@ -153,11 +151,9 @@ def make_app(loop): yield from test_client(make_app) """) - testdir.runpytest('-p', 'no:sugar') - - # i dont know how to fix this - # result = testdir.runpytest('-p', 'no:sugar') - # result.assert_outcomes(passed=11, failed=1) + testdir.makeconftest(CONFTEST) + result = testdir.runpytest('-p', 'no:sugar', '--loop=pyloop') + result.assert_outcomes(passed=11, failed=1) @pytest.mark.skipif(sys.version_info < (3, 5), reason='old python') @@ -165,8 +161,6 @@ def test_warning_checks(testdir, capsys): testdir.makepyfile("""\ import asyncio -pytest_plugins = 'aiohttp.pytest_plugin' - async def foobar(): return 123 @@ -177,7 +171,8 @@ async def test_good(): async def test_bad(): foobar() """) - result = testdir.runpytest('-p', 'no:sugar', '-s') + testdir.makeconftest(CONFTEST) + result = testdir.runpytest('-p', 'no:sugar', '-s', '--loop=pyloop') result.assert_outcomes(passed=1, failed=1) stdout, _ = capsys.readouterr() assert ("test_warning_checks.py:__LINE__:coroutine 'foobar' was " @@ -192,9 +187,6 @@ def test_aiohttp_plugin_async_fixture(testdir, capsys): from aiohttp import web -pytest_plugins = 'aiohttp.pytest_plugin' - - @asyncio.coroutine def hello(request): return web.Response(body=b'Hello, world') @@ -244,9 +236,9 @@ def test_foo_without_loop(foo): def test_bar(loop, bar): assert bar is test_bar """) - nb_loops = len(LOOP_FACTORIES) - result = testdir.runpytest('-p', 'no:sugar') - result.assert_outcomes(passed=3 * nb_loops, error=1) + testdir.makeconftest(CONFTEST) + result = testdir.runpytest('-p', 'no:sugar', '--loop=pyloop') + result.assert_outcomes(passed=3, error=1) result.stdout.fnmatch_lines( "*Asynchronous fixtures must depend on the 'loop' fixture " "or be used in tests depending from it." @@ -263,8 +255,6 @@ def test_aiohttp_plugin_async_gen_fixture(testdir): from aiohttp import web -pytest_plugins = 'aiohttp.pytest_plugin' - canary = mock.Mock() @@ -292,6 +282,6 @@ async def test_hello(cli): def test_finalized(): assert canary.called is True """) - nb_loops = len(LOOP_FACTORIES) - result = testdir.runpytest('-p', 'no:sugar') - result.assert_outcomes(passed=1 * nb_loops + 1) + testdir.makeconftest(CONFTEST) + result = testdir.runpytest('-p', 'no:sugar', '--loop=pyloop') + result.assert_outcomes(passed=2)