pytest-dev / pytest-asyncio

Asyncio support for pytest
https://pytest-asyncio.readthedocs.io
Apache License 2.0
1.36k stars 140 forks source link

`event_loop` not found for multiple test modules #862

Open TE-YongweiSun opened 3 weeks ago

TE-YongweiSun commented 3 weeks ago

I just want to use module scope fixture but I get an error:

[environment] python==3.10.14 pytest==8.2.2 pytest-asyncio==0.23.7

[Directory]

tests/conftest.py:

import pytest_asyncio
@pytest_asyncio.fixture(scope="module")
async def foo():
    yield "a value"

tests/test_1.py:

import pytest
@pytest.mark.asyncio
async def test_func(foo):
        print("test_1 test_func", foo)

tests/test_2.py:

import pytest
@pytest.mark.asyncio
async def test_func(foo):
    print("test_2 test_func", foo)

[error]

# pytest -s tests/
============================= test session starts ==============================
platform linux -- Python 3.10.14, pytest-8.2.2, pluggy-1.5.0
rootdir: /home/sunyw
plugins: asyncio-0.23.7
asyncio: mode=strict
collected 2 items                                                              

tests/test_1.py .                                                        [ 50%]
tests/test_2.py E                                                        [100%]

==================================== ERRORS ====================================
_________________________ ERROR at setup of test_func __________________________
file /home/xxx/tests/test_2.py, line 3
  @pytest.mark.asyncio
  async def test_func(foo):
      print("test_2 test_func", foo)
file /home/xxx/tests/conftest.py, line 13
  @pytest_asyncio.fixture(scope="module")
  async def foo():
      yield "a value"
E       fixture 'tests/test_1.py::<event_loop>' not found
>       available fixtures: _session_event_loop, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, event_loop, event_loop_policy, foo, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tests/test_2.py::<event_loop>, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, unused_tcp_port, unused_tcp_port_factory, unused_udp_port, unused_udp_port_factory
>       use 'pytest --fixtures [testpath]' for help on them.

/home/xxx/tests/conftest.py:13
=========================== short test summary info ============================
ERROR tests/test_2.py::test_func
========================== 1 passed, 1 error in 0.01s ==========================

according to the document Decorators:

All scopes are supported, but if you use a non-function scope you will need to redefine the event_loop fixture to have the same or broader scope. Async fixtures need the event loop, and so must have the same or narrower scope than the event_loop fixture.

I add event_loop fixture in conftest.py:

import pytest_asyncio
import asyncio
import pytest

@pytest.fixture(scope="module")
def event_loop(request):
    loop = asyncio.get_event_loop_policy().get_event_loop()
    yield loop
    loop.close()

@pytest_asyncio.fixture(scope="module")
async def foo():
    yield "a value"

But this error is still there.

erwanlfrt commented 3 weeks ago

Same issue for me, I have the same event_loop as @TE-YongweiSun in conftest.py and with pytest-asyncio v.0.21.1 no problem but with the latest version v0.23.6 I have the notorious attached to a different loop issue.

Logs:

E   RuntimeError: Task <Task pending name='Task-6' coro=<_wrap_asyncgen_fixture.<locals>._asyncgen_fixture_wrapper.<locals>.setup() running at /opt/acc-py/venvs/acc-py-venv/lib/python3.11/site-packages/pytest_asyncio/plugin.py:326> cb=[_run_until_complete_cb() at /opt/acc-py/base/2023.06/lib/python3.11/asyncio/base_events.py:180]> got Future <Future pending cb=[Protocol._on_waiter_completed()]> attached to a different loop

For somehow the fixture event_loop is not used anymore.

seifertm commented 16 hours ago

Starting from pytest-asyncio v0.23, every level of the pytest test collection hierarchy has its own asyncio event loop. By default, tests marked with @pytest.mark.asyncio are run in a function-scope event loop, i.e. the event loop is refreshed before every test and closed afterwards. A test marked with @pytest.mark.asyncio(scope="module") is run in an asyncio event loop that is valid for the scope of an entire module, for example.

Unfortunately, pytest-asyncio v0.23 falsely assumes that the caching scope and event loop scope for async fixtures are the same. In your example, this means that foo is run in a module-scoped loop, whereas tests/test_1.py::test_func and tests/test_2.py::test_func are each run in a different, function-scope event loop.

You'll want to adjust your tests to use

@pytest.mark.asyncio(scope="module")
async def test_func(foo):
    ...

for everything to run in the same event loop.

Is this something that you can do in your codebase (not the example)?