pytest-asyncio
pytest-asyncio copied to clipboard
documentation on how to force all tests in one session to use the same event loop appears to not force fixtures into the same event loop
I checked in all the files referenced below into this repo: https://github.com/danking/pytest-asyncio-confusion
pytest==7.4.3
pytest-asyncio==0.23.3
Simple test file:
import pytest
import asyncio
@pytest.fixture(scope='session')
async def foo():
yield asyncio.get_running_loop()
async def test_foobar(foo):
assert foo == asyncio.get_running_loop()
conftest.py taken directly from the docs:
import pytest
from pytest_asyncio import is_async_test
def pytest_collection_modifyitems(items):
pytest_asyncio_tests = (item for item in items if is_async_test(item))
session_scope_marker = pytest.mark.asyncio(scope="session")
for async_test in pytest_asyncio_tests:
async_test.add_marker(session_scope_marker)
pytest.ini:
[pytest]
asyncio_mode = auto
I expected this to pass but instead it fails:
# pytest foo.py
========================================================================================= test session starts =========================================================================================
platform darwin -- Python 3.10.9, pytest-7.4.3, pluggy-1.3.0
rootdir: /private/tmp/bar
configfile: pytest.ini
plugins: xdist-2.5.0, timeout-2.2.0, instafail-0.5.0, asyncio-0.23.3, devtools-0.12.2, timestamper-0.0.9, metadata-3.0.0, anyio-4.1.0, html-1.22.1, forked-1.6.0, accept-0.1.9, image-diff-0.0.11
asyncio: mode=auto
collected 1 item
foo.py F [100%]
============================================================================================== FAILURES ===============================================================================================
_____________________________________________________________________________________________ test_foobar _____________________________________________________________________________________________
foo = <_UnixSelectorEventLoop running=False closed=False debug=False>
async def test_foobar(foo):
> assert foo == asyncio.get_running_loop()
E assert <_UnixSelectorEventLoop running=False closed=False debug=False> == <_UnixSelectorEventLoop running=True closed=False debug=False>
E + where <_UnixSelectorEventLoop running=True closed=False debug=False> = <built-in function get_running_loop>()
E + where <built-in function get_running_loop> = asyncio.get_running_loop
foo.py:14: AssertionError
======================================================================================= short test summary info =======================================================================================
FAILED foo.py::test_foobar - assert <_UnixSelectorEventLoop running=False closed=False debug=False> == <_UnixSelectorEventLoop running=True closed=False debug=False>
========================================================================================== 1 failed in 0.05s ==========================================================================================
Am I being dense? I feel like I must misunderstand something very simple. If I explicitly request session scope it works:
# g diff
diff --git a/foo.py b/foo.py
index 2f7efc0..608da8a 100644
--- a/foo.py
+++ b/foo.py
@@ -8,6 +8,7 @@ async def foo():
yield asyncio.get_running_loop()
[email protected](scope='session')
async def test_foobar(foo):
assert foo == asyncio.get_running_loop()
# pytest foo.py
========================================================================================= test session starts =========================================================================================
platform darwin -- Python 3.10.9, pytest-7.4.3, pluggy-1.3.0
rootdir: /private/tmp/bar
configfile: pytest.ini
plugins: xdist-2.5.0, timeout-2.2.0, instafail-0.5.0, asyncio-0.23.3, devtools-0.12.2, timestamper-0.0.9, metadata-3.0.0, anyio-4.1.0, html-1.22.1, forked-1.6.0, accept-0.1.9, image-diff-0.0.11
asyncio: mode=auto
collected 1 item
foo.py . [100%]
========================================================================================== 1 passed in 0.01s ==========================================================================================
This is definitely related to https://github.com/pytest-dev/pytest-asyncio/issues/706, https://github.com/pytest-dev/pytest-asyncio/issues/705, and https://github.com/pytest-dev/pytest-asyncio/issues/718, but I think my essential ask here is for documentation on how to ensure a particular test (or scope of tests, or group of tests) uses the same event loop as a particular fixture (or scope of fixtures, or group of fixtures).
The example I gave above may seem contrived, but it's essential to the correct functioning of aiohttp. The aiohttp.ClientSession saves a copy of the event loop at allocation time and assumes the same event loop is in use at HTTP request time.
If I have any non-default scoped fixture that creates a client session (possibly multiple layers down from the object I'm actually allocating), that fixture will yield a (nondeterministically) broken client session.
https://github.com/pytest-dev/pytest-asyncio/issues/706#issuecomment-1838969204
You'd better move to prior version. -> 0.21.1. I also wasted a day, figuring out why contextvar got spawend on another event_loop.
You'd better move to prior version. -> 0.21.1. I also wasted a day, figuring out why contextvar got spawend on another event_loop.
I agree. If pytest-asyncio v0.23 is currently causing issues for you, I suggest to continue using v0.21 until the issues are resolved.
@danking The v0.23 release allows tests to be run in different event loops. There's essentially one loop for each hierarchy level of the test suite (session, package, module, class, function). Unfortunately, pytest-asyncio also assumes that the scope of a fixture is coupled with the scope of the event loop. This means that a session-scoped fixture always runs in the session-scope loop. There's currently no way to have a session-scoped fixture running in the same loop as a function-scoped test. This is the core issue of #706.
@rumbarum I'm sorry that this caused you spending so much time on this issue.
@seifertm I appreciate your works. And thanks for your words.