Configuration

Markers

@pytest.mark.looptime configures the test’s options if and when it is executed with the timeline replaced to fast-forwarding time. In normal mode with no configs/CLI options specified, it marks the test to be executed with the time replaced.

@pytest.mark.looptime(False) (with the positional argument) excludes the test from the time fast-forwarding under any circumstances. The test will be executed with the loop time aligned with the real-world time. Use it only for the tests that are designed to be true-time-based.

Note that markers can be applied not only to individual tests, but also to whole test suites (classes, modules, packages):

import asyncio
import pytest

pytestmark = [
  pytest.mark.asyncio,
  pytest.mark.looptime(end=60),
]


async def test_me():
    await asyncio.sleep(100)

The markers can also be artificially injected by plugins/hooks if needed:

import inspect
import pytest

@pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj):
    if collector.funcnamefilter(name) and inspect.iscoroutinefunction(obj):
        pytest.mark.asyncio(obj)
        pytest.mark.looptime(end=60)(obj)
    yield

All in all, the looptime plugin uses the most specific (the “closest”) value for each setting separately (i.e., not the closest marker as a whole).

Options

--looptime enables time fast-forwarding for all tests that are not explicitly marked as using the fake loop time—including those not marked at all— as if all tests were implicitly marked.

--no-looptime runs all tests, both marked and unmarked, with real time. This flag effectively disables the plugin.

Settings

The marker accepts several settings for the test. The closest to the test function applies. This lets you define the test-suite defaults and override them on the directory, module, class, function, or test level:

import asyncio
import pytest

pytestmark = pytest.mark.looptime(end=10, idle_timeout=1)

@pytest.mark.asyncio
@pytest.mark.looptime(end=101)
async def test_me():
    await asyncio.sleep(100)
    assert asyncio.get_running_loop().time() == 100

The time zero

start (float or None, or a no-argument callable that returns the same) is the initial time of the event loop.

If it is a callable, it is invoked once per test to get the value: e.g., start=time.monotonic to align with the true time, or start=lambda: random.random() * 100 to add some unpredictability.

None is treated the same as 0.0.

The default is 0.0. For reusable event loops, the default is to keep the time untouched, which means 0.0 or the explicit value for the first test, but then an ever-increasing value for the second, third, and subsequent tests.

Note

pytest-asyncio 1.0.0+ introduced event loops with higher scopes, e.g., class-, module-, package-, session-scoped event loops used in tests. Such event loops are reused, so their time continues growing through many tests. However, if the test is explicitly configured with a start time, that time is enforced on the event loop when the test function starts— to satisfy the clearly declared intentions—even if the time moves backwards, which goes against the nature of time itself (monotonically growing). This might lead to surprises in time measurements outside of the test, e.g., in fixtures: code durations can become negative, or events can happen (falsely) before they are scheduled (loop-clock-wise). Be careful.

The end of time

end (float or None, or a no-argument callable that returns the same) is the final time in the event loop (the internal fake time). If it is reached, all tasks get terminated and the test is supposed to fail. The injected exception is looptime.LoopTimeoutError, a subclass of asyncio.TimeoutError.

All test-/fixture-finalizing routines will have their fair chance to execute as long as they do not move the loop time forward, i.e., they take zero time: e.g., with asyncio.sleep(0), simple await statements, etc.

If set to None, there is no end of time, and the event loop runs as long as needed. Note: 0 means ending the time immediately on start. Be careful with the explicit ending time in higher-scoped event loops of pytest-asyncio>=1.0.0, since their time increases through many tests.

If it is a callable, it is called once per event loop to get the value: e.g., end=lambda: time.monotonic() + 10.

The end of time is not the same as timeouts — see Nuances on differences with async-timeout.

Advanced settings

A few more settings are considered advanced and documented in Nuances. They cover very nuanced aspects of the time flow, mainly synchronising across two timelines: fake time & real time, sync & async. You normally should not use them and the defaults should be fine.

  • noop_cycles

  • idle_step

  • idle_timeout

  • resolution