Open ykuzma1 opened 5 years ago
I'd be happy to put in a pull request. I mostly want to use this space for ideas, because I've tried everything I can think of. Ideas welcome! I'll put my couple attempts below.
My thinking is that it fails because plugin.py calls loop.run_until_complete(setup())
during the middle of async tests being run on the event loop already. So calling run_until_complete
tries adding the async fixture onto the already running loop - causing the error. It sounds like iPython had the same issue. So I tried some workarounds they suggested to replace that line:
Causes infinite loop:
return asyncio.run_coroutine_threadsafe(setup(), loop).result()
Causes asyncio.base_futures.InvalidStateError: Result is not ready.
error:
return asyncio.ensure_future(setup()).result()
Another infinite loop:
from concurrent.futures import ThreadPoolExecutor
loop = asyncio.new_event_loop()
ThreadPoolExecutor().submit(loop.run_forever)
return asyncio.run_coroutine_threadsafe(setup(), loop).result()
Funny enough I got one workaround to work while I was going back through the iPython thread:
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(1)
loop = asyncio.new_event_loop()
pool.submit(asyncio.set_event_loop, loop).result()
return pool.submit(loop.run_until_complete, setup()).result()
Will start working on a proper pull request.
Any news? :cry:
any updates on this?
Sorry this doesn't help solve this for pytest-asyncio, but I struggled with this for a while, including trying custom fixtures with module scope and nest_asyncio
, but eventually a solution was to drop all the pytest-asyncio decorators and async def
tests to go back to vanilla non-async test functions with a non-async entry point that runs the rest of the async coroutines. The only trick to executing the integration tests for the async coroutines was to create a main
like entry point that all the tests pass through and that function gets a new event loop every time, e.g.
def run_aync_main(*args, **kwargs):
main_loop = asyncio.new_event_loop()
try:
main_loop.run_until_complete(any_async_entry_point(*args, *kwargs))
finally:
main_loop.stop()
main_loop.close()
Any non-async function that calls a run_until_complete
does not need to be run by pytest-asyncio decorators and it should not conflict across tests when it gets a new loop. Other unit tests on async coroutines with regular await
statements work OK with pytest-asyncio.
In our case it worked by simply making the dynamic fixture sync:
def test_async_fixture_dynamic(request, event_loop):
async_fixture = request.getfixturevalue('async_fixture')
assert async_fixture == 'Hi from async_fixture()!'
Got this error when I tried to upgrade to 0.14.0 from 0.10.0. Rolled back for now.
Got this error in 0.15.1 also, any update ?
got this error in 0.18.3
As of v0.18.3 this error could be caused by an unexpected interaction with other pytest plugins that manipulate the event loop.
@ReznikovRoman Can you provide a reproducible example?
I got the same issue today.
@pytest.mark.parametrize('client_fixture,expectation', [
('client', do_not_raise()),
('client_without_apikey', pytest.raises(errors.ApikeyNotSetError)),
])
async def test_get_user_settings(client_fixture, expectation, request: pytest.FixtureRequest):
> client: HavenClient = request.getfixturevalue(client_fixture)
self = <_UnixSelectorEventLoop running=False closed=False debug=False>
def _check_running(self):
if self.is_running():
> raise RuntimeError('This event loop is already running')
E RuntimeError: This event loop is already running
/usr/local/lib/python3.10/asyncio/base_events.py:582: RuntimeError
I'll try to get a clean example little bit later.
I made a simple example
plugins: anyio-3.6.1, asyncio-0.18.3
asyncio: mode=auto
@seifertm
Thanks for the example @suharnikov. I managed to reproduce the error.
When a fixture is requested dynamically, it is looked up in pytest's fixture cache first. If it cannot be found in the cache pytest evaluates the fixture function. Since the async fixture coroutine has a synchronous wrapper around it that calls loop.run_until_complete
that wrapper will fail to execute, because the event loop from the async test function's wrapper is already running.
A fix would require that the fixture wrapper can decide dynamically whether it is run asynchronously in an event loop or synchronously. However, the fixture wrapper itself needs to be synchronous, because that's what pytest expects. If an event loop is already running, the fixture wrapper needs to submit a task to the event loop and block execution until that task has finished.
I'm not aware of a way to await a task from a synchronous function. With the current state of pytest-asyncio, I don't see how this bug can be solved. Suggestions are welcome.
I solved this problem by adding nest_asyncio.apply() on the top on contest.py:
import nest_asyncio
nest_asyncio.apply()
There seems to be a bug with how
request.getfixturevalue(argname)
interacts with pytest-asyncio. Calling the function leads to a runtime error saying the event loop is already running. If you change it from being dynamically called to being fixed in the function definition, it works as expected.Platform Info:
Test fixture that can be used in both dynamic/fixed cases:
Successful fixed function argument fixture test:
Failed dynamic fixture test:
Failed test trace-back: