Skip to content

Adapt to pytest-asyncio 1.0.0 with multi-scoped event loops co-existing at the same time [1st attempt]#7

Closed
nolar wants to merge 1 commit intomainfrom
pytest-asyncio-1.0.0-compat
Closed

Adapt to pytest-asyncio 1.0.0 with multi-scoped event loops co-existing at the same time [1st attempt]#7
nolar wants to merge 1 commit intomainfrom
pytest-asyncio-1.0.0-compat

Conversation

@nolar
Copy link
Copy Markdown
Owner

@nolar nolar commented Jun 18, 2025

Still work in progress! But generally works on a sample snippet.

TODOs:

  • Evaluate callable start=…, end=… (now: assumed to be scalar).
  • Adjust existing tests.
  • Add new tests for possibly new scenarios
  • Setup CI to test pytest-asyncio <1.0.0 and >=1.0.0 separately.
  • Read and adjust the docs accordingly.
  • Unrelated: Upgrade to Python 3.9+ syntax.
  • Unrelated: Upgrade to new pytest/pluggy notations (new-style hook wrappers, maybe more).

Pytest-asyncio 1.0.0 removed the event_loop and is now based on event_loop_policy with multi-scoped fixtures. It was deprecated since 0.23.0, but finally removed in 1.0.0.

This breaks several conceptual(!) assumptions for looptime:

Looptime assumed that every test lives in its own tiny universe that starts with the big bang at time 0 by default (looptime(start=…) mark) and does not share the event loop with other tests.

Now, the event loop is shared with tests in the scope (session, module, etc). Which means, the time in every test cannot be guaranteed to start with 0.

If it is forced to 0, then the event loop's time is non-monotonic, i.e. it goes forward in a test A, then resets to 0, goes forwards again in test B, so on. This can lead to undesired effects, such as events/callbacks happening "before" they were scheduled, loop-clock-wise.

In order to resolve this problem, looptime has no choice but to introduce a few minor but breaking changes:

  • loop.time() is not guaranteed to start with 0 for every test. It can be any value. Still 0 for function-scoped event loops.
  • looptime (a numeric-like fixture) must be used for assertions of the test time.
  • They are not synonymous anymore, as they were before.

The more verbose version of this explanation is in the plugin.py docstring — for future read.


Solves #5

The sample snippet used for manual testing:

import asyncio

import pytest


@pytest.mark.asyncio(loop_scope="session")  # uses `_session_event_loop`
async def test_a_in_true_time():
    loop = asyncio.get_running_loop()
    print(f"TST-A {id(loop)=} {loop=}")
    await asyncio.sleep(1)
    assert asyncio.get_running_loop().time() == 1  # ???


@pytest.mark.asyncio(loop_scope="session")  # uses `_session_event_loop`
@pytest.mark.looptime(start=1000)
async def test_b_in_loop_time(looptime):
    loop = asyncio.get_running_loop()
    print(f"TST-B {id(loop)=} {loop=}")

    await asyncio.sleep(1)
    assert looptime == 1001
    assert asyncio.get_running_loop().time() == 2


@pytest.mark.asyncio(loop_scope="session")  # uses `_session_event_loop`
@pytest.mark.looptime(start=0)
async def test_c_in_loop_time(looptime):
    loop = asyncio.get_running_loop()
    print(f"TST-C {id(loop)=} {loop=}")
    await asyncio.sleep(1)
    assert looptime == 1
    assert asyncio.get_running_loop().time() == 3

Signed-off-by: Sergey Vasilyev <nolar@nolar.info>
@nolar nolar changed the title [WIP] Adapt to pytest-asyncio 1.0.0 with multi-scoped event loops co-existing at the same time Adapt to pytest-asyncio 1.0.0 with multi-scoped event loops co-existing at the same time [1st attempt] Jun 22, 2025
@nolar
Copy link
Copy Markdown
Owner Author

nolar commented Jun 22, 2025

Closing in favour of a more simple and seemingly feature-aligned (with pytest-asyncio):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant