Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requesting loop_scope="module" (or "session" etc.) returns incorrect event_loop if another test function requests a different loop. #950

Open
TimChild opened this issue Oct 4, 2024 · 1 comment
Labels
Milestone

Comments

@TimChild
Copy link

TimChild commented Oct 4, 2024

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"
@TimChild TimChild changed the title Requesting loop_scope="module" (or "session" etc.) returns incorrect event_loop if another fixture requests a different loop. Requesting loop_scope="module" (or "session" etc.) returns incorrect event_loop if another function requests a different loop. Oct 4, 2024
@TimChild TimChild changed the title Requesting loop_scope="module" (or "session" etc.) returns incorrect event_loop if another function requests a different loop. Requesting loop_scope="module" (or "session" etc.) returns incorrect event_loop if another test function requests a different loop. Oct 4, 2024
@seifertm seifertm added this to the v0.24 milestone Oct 4, 2024
@seifertm seifertm added the bug label Oct 4, 2024
@seifertm
Copy link
Contributor

seifertm commented Oct 4, 2024

Good catch and great reproducer! Thanks for the report.

The test that breaks the event loop is actually test_use_function_scope_loop_1. It uses the deprecated event_loop fixture which entails a bunch of invisible cleanups.

A fix for this should be included in a patch release for v0.24.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants