Description
First of all, thanks for the great pytest plugin!
Also, I like the idea of being able to specify event_loop scopes explicitly and separately to the fixture scopes.
While playing with this, I kept running into issues that my "session" scoped event_loop seemed to change between tests that were running in different files, but only if certain other tests were run...
I think this example reproduces the issues in a single file. I recognize that the docs explicitly advise against doing this, but I hope it makes the problem easier to reason about.
Here's the example.
import pytest_asyncio
import asyncio
import pytest
class FakeAsyncConnection:
def __init__(self, loop):
self.loop = loop
async def do_something(self):
# Check if the current loop is the same as the one with which the
# connection was created
if asyncio.get_event_loop() is not self.loop:
raise RuntimeError(
"This connection is being used with a different event loop!")
return "Success"
@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def async_connection():
"""Set up a async connection object with module scope."""
event_loop = asyncio.get_event_loop()
print(f"Setting up fixture: event_loop_id {id(event_loop)}")
connection = FakeAsyncConnection(event_loop)
yield connection
@pytest.mark.asyncio(loop_scope="module")
async def test_use_module_scope_loop_1(async_connection):
"""Use module loop"""
print(f"Test using loop with id: {id(asyncio.get_event_loop())}")
result = await async_connection.do_something()
assert result == "Success"
@pytest.mark.asyncio(loop_scope="module")
async def test_use_module_scope_loop_2(async_connection):
"""Use module loop again"""
print(f"Test using loop with id: {id(asyncio.get_event_loop())}")
result = await async_connection.do_something()
assert result == "Success"
@pytest.mark.asyncio(loop_scope="function")
async def test_use_function_scope_loop_1(async_connection):
"""Use function loop"""
print(f"Test using loop with id: {id(asyncio.get_event_loop())}")
with pytest.raises(RuntimeError, match="This connection is being used with a different event loop!"):
# This should raise an error because the connection is being used with a different loop
await async_connection.do_something()
@pytest.mark.asyncio(loop_scope="module")
async def test_use_module_scope_loop_3(async_connection):
"""Unexpectedly fail to use module scope again"""
print(f"Test using loop with id: {id(asyncio.get_event_loop())}")
result = await async_connection.do_something()
assert result == "Success"
I would expect all tests to pass, however, the final test test_use_module_scope_loop_3
fails only if the test_use_function_scope_loop_1
is present. If the function scope one is commented out, the final test does pass (as expected).
The fixtures aren't obviously set up incorrectly (running with --setup-show
):
SETUP S event_loop_policy
SETUP M tests/test_a.py::<event_loop> (fixtures used: event_loop_policy)
SETUP M async_connection
tests/test_a.py::test_use_module_scope_loop_1 (fixtures used: async_connection, event_loop_policy, request, tests/test_a.py::<event_loop>).
tests/test_a.py::test_use_module_scope_loop_2 (fixtures used: async_connection, event_loop_policy, request, tests/test_a.py::<event_loop>).
SETUP F event_loop
tests/test_a.py::test_use_function_scope_loop_1 (fixtures used: async_connection, event_loop, event_loop_policy, request).
TEARDOWN F event_loop
tests/test_a.py::test_use_module_scope_loop_3 (fixtures used: async_connection, event_loop_policy, request, tests/test_a.py::<event_loop>)F
TEARDOWN M async_connection
TEARDOWN M tests/test_a.py::<event_loop>
TEARDOWN S event_loop_policy
But for some reason I don't understand, the module scoped event loop changes for the last test.
The printed loop ids tell the same story... The fixture and first two tests all get the the same loop_id as expected. The function scope test gets a new one as expected. Then the final module scope test also gets a new loop_id (different from both previous loop_ids) unexpectedly.
Versions:
python: 3.12.7
pytest: 8.3.3
pytest-asyncio: 0.24.0
Also, my pytest settings are:
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope="function"