Repository: aio-libs/aiohttp

Issue:
pytest-aiohttp: compatibility with pytest v3.3.0
## Long story short

https://github.com/pytest-dev/pytest/issues/2959
Fixture parameters are now immutable, [pytest_plugin.py](https://github.com/aio-libs/aiohttp/blob/master/aiohttp/pytest_plugin.py#L195-L200) need to be updated

pytest-aiohttp: compatibility with pytest v3.3.0
## Long story short

https://github.com/pytest-dev/pytest/issues/2959
Fixture parameters are now immutable, [pytest_plugin.py](https://github.com/aio-libs/aiohttp/blob/master/aiohttp/pytest_plugin.py#L195-L200) need to be updated



Discussion:

Resolving patch:
diff --git a/aiohttp/pytest_plugin.py b/aiohttp/pytest_plugin.py
--- 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


Test patch:
diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py
--- 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)