Description
Hi. First of all, thanks for the library — it is really a timesaver! ;-)
Now, to the point: I maintain a fancy library looptime that compacts the loop time into zero true (aka wall-clock) time, while simulating the time flow within the loop (be that seconds or years of loop time in 0.1s of true time). Previously (pytest-asyncio<1.0.0), every test had its own event loop and lived in its own universe from the event loop creation (say, time 0). Now (>=1.0.0), the event loop can be shared across many tests at different scopes, also separated by modules/classes/packages. I am trying to adapt my library to this behaviour.
I have found a somewhat confusing case which is easy to hit — see a repro here.
The key point is that a regular fixture fixt
is function-scoped by default, and is intended to be so. The rest of the code is basically an example from pytest-asyncio's docs. It fails in test B because the usage of the fixt
fixture overrides the current event loop to be function-scoped, despite the code declares the intention to use the session-scoped event loop.
No warnings are issued, no errors raised, it just silently falls to another event loop — it would be difficult to catch such issues in a big codebase if at least one of many fixtures is defined in the wrong scope.
When fixt
is removed from test B, the sample works. When fixt
is made session-scoped explicitly, it also works.
Can you please clarify what is the intended outcome in this code below? Should it work?
I also wonder why was it made so that every collector has its own event loop? The most straightforward way at the first glance seems to be to always provide the event_loop
fixture as before, always function-scoped, which internally either creates a new event loop or escalates to the higher-level _class_event_loop
, _module_event_loop
, … _session_event_loop
— if the test is marked with some higher scope. But maybe I miss some edge-cases or scenarios that prevent such usage?
I would really appreciate it, so that I could adjust my code properly long-term and without hacks.
import asyncio
import pytest
import pytest_asyncio
loop: asyncio.AbstractEventLoop
@pytest_asyncio.fixture # implies: scope=function # ❌FAILS
# @pytest_asyncio.fixture(scope='session') # ✅WORKS
async def fixt() -> None:
yield
@pytest.mark.asyncio(loop_scope="session") # uses `_session_event_loop`
async def test_a():
global loop
loop = asyncio.get_running_loop()
print(f"TST {id(asyncio.get_running_loop())=}")
@pytest.mark.asyncio(loop_scope="session") # uses `_session_event_loop` or `_function_event_loop`
async def test_b(fixt):
print(f"TST {id(asyncio.get_running_loop())=}")
assert asyncio.get_running_loop() is loop