mirror of
https://github.com/esphome/esphome.git
synced 2026-07-11 09:25:34 +00:00
7d12b984a8
Doc and test updates from a code review of this PR: - Correct the `tail_us == 0 on Phase A-only ticks` claim in the Application::loop() comment and the RuntimeStatsCollector::record_loop_active docstring. `loop_tail_start_us` is set to `loop_before_end_us`, and `loop_now_us` is sampled later, so `tail_us` on Phase A-only ticks is the small gate-check + record prefix — tiny but non-zero. (Also flagged by Copilot on application.h:623 and runtime_stats.h:45.) - Call out ESP8266 as the floor case in the WDT_FEED_INTERVAL_MS margin table. Its soft WDT (~1.6 s) is the tightest margin at ~5x, so future changes to the constant need to preserve comfortable headroom there. - Tighten the test lower bound at tests/integration/test_loop_interval_decoupling.py from `2 <= loop_delta <= 6` to `3 <= loop_delta <= 6`. Allowing 2 would let a >50% slowdown from the 4-in-2s nominal pass as CI jitter, which undermines the regression signal. 3 keeps the test honest while still absorbing realistic CI jitter. - Add a second integration test (test_loop_interval_default_not_pulled_forward) that covers the inverse direction: at the default loop_interval_ with a fast scheduler item (5 ms — well under the old delay_time/2 = 8 ms floor), the component phase must still run at ~62 Hz, not the pre-fix ~128 Hz. This locks down the original 128 Hz → 62 Hz regression that motivated the PR.
76 lines
3.0 KiB
Python
76 lines
3.0 KiB
Python
"""Test that loop_interval_ no longer clamps scheduler cadence.
|
|
|
|
Regression test for the decoupling of Application::loop() component-phase
|
|
cadence from scheduler wake timing.
|
|
|
|
Setup:
|
|
- App.set_loop_interval(500) — raised for power-savings style cadence
|
|
- Scheduler interval at 50ms — should fire at 50ms regardless of loop_interval_
|
|
- Component loop (LoopTestComponent) — should run at 500ms cadence
|
|
|
|
Before the decoupling fix the old `std::max(next_schedule, delay_time / 2)`
|
|
floor clamped the sleep to ~250ms, so the 50ms scheduler only fired ~8 times
|
|
per 2s (vs the ~40 expected). After the fix the scheduler fires close to its
|
|
requested cadence while the component phase stays gated at loop_interval_.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_loop_interval_decoupling(
|
|
yaml_config: str,
|
|
run_compiled: RunCompiledFunction,
|
|
api_client_connected: APIClientConnectedFactory,
|
|
) -> None:
|
|
"""Raised loop_interval_ must not clamp scheduler item cadence."""
|
|
loop = asyncio.get_running_loop()
|
|
measurement_done: asyncio.Future[tuple[int, int]] = loop.create_future()
|
|
|
|
def on_log_line(line: str) -> None:
|
|
match = re.search(r"MEASUREMENT_DONE loop_delta=(\d+) sched_delta=(\d+)", line)
|
|
if match and not measurement_done.done():
|
|
measurement_done.set_result((int(match.group(1)), int(match.group(2))))
|
|
|
|
async with (
|
|
run_compiled(yaml_config, line_callback=on_log_line),
|
|
api_client_connected() as client,
|
|
):
|
|
device_info = await client.device_info()
|
|
assert device_info is not None
|
|
assert device_info.name == "loop-interval-decouple"
|
|
|
|
try:
|
|
loop_delta, sched_delta = await asyncio.wait_for(
|
|
measurement_done, timeout=10.0
|
|
)
|
|
except TimeoutError:
|
|
pytest.fail("MEASUREMENT_DONE marker never appeared")
|
|
|
|
# Observation window = 2s, loop_interval_ = 500ms.
|
|
# Component phase should fire ~4 times in 2s. The upper bound must be
|
|
# less than 8: the pre-decoupling behavior clamped to ~250ms cadence
|
|
# giving ~8 loops/2s, so allowing 8 would let the old behavior pass.
|
|
# Lower bound 3 (not 2) keeps the test honest: a >30% slowdown from
|
|
# the ~4 nominal is not normal CI jitter and should fail.
|
|
assert 3 <= loop_delta <= 6, (
|
|
f"Component loop should fire ~4 times in 2s at loop_interval=500ms, "
|
|
f"got {loop_delta}"
|
|
)
|
|
|
|
# Scheduler interval = 50ms → ~40 fires in 2s. Before the decoupling
|
|
# fix this clamped to ~8 fires. Assert >= 20 to catch the old clamped
|
|
# behavior with comfortable jitter headroom for slow CI hosts.
|
|
assert sched_delta >= 20, (
|
|
f"50ms scheduler interval should fire ~40 times in 2s but only "
|
|
f"fired {sched_delta}. This indicates loop_interval_ is still "
|
|
f"clamping scheduler cadence."
|
|
)
|