Fix event_loop is run after other fixtures#156
Fix event_loop is run after other fixtures#156Tinche merged 3 commits intopytest-dev:masterfrom simonfagerholm:fix_issue_154
Conversation
| I did something that seems to fix the test, however I'm really not sure the behaviour is wanted: diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 9860e04..c0b65da 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -125,14 +125,18 @@ def pytest_pyfunc_call(pyfuncitem): if 'asyncio' in pyfuncitem.keywords: if getattr(pyfuncitem.obj, 'is_hypothesis_test', False): pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync( - pyfuncitem.obj.hypothesis.inner_test + pyfuncitem.obj.hypothesis.inner_test, + _loop=pyfuncitem.funcargs['event_loop'] ) else: - pyfuncitem.obj = wrap_in_sync(pyfuncitem.obj) + pyfuncitem.obj = wrap_in_sync( + pyfuncitem.obj, + _loop=pyfuncitem.funcargs['event_loop'] + ) yield -def wrap_in_sync(func): +def wrap_in_sync(func, _loop): """Return a sync wrapper around an async function executing it in the current event loop.""" @@ -140,9 +144,15 @@ def wrap_in_sync(func): def inner(**kwargs): coro = func(**kwargs) if coro is not None: - task = asyncio.ensure_future(coro) try: - asyncio.get_event_loop().run_until_complete(task) + loop = asyncio.get_event_loop() + except RuntimeError as exc: + if 'no current event loop' not in str(exc): + raise + loop = _loop + task = asyncio.ensure_future(coro, loop=loop) + try: + loop.run_until_complete(task) except BaseException: # run_until_complete doesn't get the result from exceptions # that are not subclasses of `Exception`. Consume allBasically the wrapper will attach the loop from the |
| Hi, I think the behavior is fine (second time that I look to the source of pytest-asyncio s don't take my word for it :D), but I would take it up a notch to "An asynchronous test function should run on the event loop that was used for its fixtures". So I tried the following changes, and tests continue to pass but can't keep thinking that is probably too easy :). diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 9860e04..08cf9d2 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -125,14 +125,18 @@ def pytest_pyfunc_call(pyfuncitem): if 'asyncio' in pyfuncitem.keywords: if getattr(pyfuncitem.obj, 'is_hypothesis_test', False): pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync( - pyfuncitem.obj.hypothesis.inner_test + pyfuncitem.obj.hypothesis.inner_test, + _loop=pyfuncitem.funcargs['event_loop'], ) else: - pyfuncitem.obj = wrap_in_sync(pyfuncitem.obj) + pyfuncitem.obj = wrap_in_sync( + pyfuncitem.obj, + _loop=pyfuncitem.funcargs['event_loop'], + ) yield -def wrap_in_sync(func): +def wrap_in_sync(func, _loop): """Return a sync wrapper around an async function executing it in the current event loop.""" @@ -140,9 +144,9 @@ def wrap_in_sync(func): def inner(**kwargs): coro = func(**kwargs) if coro is not None: - task = asyncio.ensure_future(coro) + task = asyncio.ensure_future(coro, loop=_loop) try: - asyncio.get_event_loop().run_until_complete(task) + _loop.run_until_complete(task) except BaseException: # run_until_complete doesn't get the result from exceptions # that are not subclasses of `Exception`. Consume all |
| @rsebille The behavior and your comment differ I think: |
| @rsebille I forgot to add that I appreciate that you took time to look into this and any future support :) |
| I totally assume (slaps ensues) that asynchronous fixtures were explicitly run in the @simonfagerholm No problem, thank to you for taking the time on this. |
| Thanks for this, I will look at this over the weekend! |
| @Tinche Thats great! In my digging I found out that the test is supposed to test for problems with pytest-aiohttp.
As it fails on pytest-asyncio==0.11 and passes on this PR, it seems the unittest did not protect against that problem. import pytest from aiohttp import web async def hello(request): return web.Response(body=b'Hello, world') def create_app(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/', hello) return app async def test_hello(aiohttp_client): client = await aiohttp_client(create_app) resp = await client.get('/') assert resp.status == 200 text = await resp.text() assert 'Hello, world' in text @pytest.fixture async def async_fixture(): yield 'Hi from async_fixture()!' @pytest.mark.asyncio async def test_async_fixture_fixed(async_fixture): assert async_fixture == 'Hi from async_fixture()!' |
| Yeah, I guess that unittest from #64 was useless. On @simonfagerholm's branch, and applied the linked diff (which I'm ok with). So I tried installing If you could add a changelog entry, I'll release this, with thanks :) |
| @Tinche Pushed a commit of my diff above. Thanks for your help in the investigation, do you have any ideas on how to test the compatibility with aiohttp? I somewhat dislike the inclusion of aiohttp in the tests, but it might not be possible in other ways. |
| Actually I don't really have a good idea. It would be ideal if they started depending on us, since I feel they are a higher level component... |
| Thanks a lot @simonfagerholm ! |
Resolves #154, resolves #157, resolves #158
Also adds unittests for the use case.
Not ready for merge, breaks
test_asyncio_marker_without_loop, which was added in #64.pytest-asyncio/tests/test_simple.py
Lines 116 to 128 in a7e5795
Discussion for possible fix of broken test below