Files
esphome/tests
J. Nick Koston a0ac226e6c [core] decouple main loop cadence from scheduler wake timing
Restructures Application::loop() into two independent phases to stop the
scheduler from silently pulling the component loop cadence forward.

Before: Application::loop() bounded its sleep by
  min(loop_interval_ - elapsed, next_schedule_in())
with a delay_time/2 floor. Any scheduler item due sooner than
loop_interval_/2 dragged the whole component phase with it. On a typical
ESP32 config with default loop_interval_=16ms, combined scheduler
activity from api / esp32_ble / esp32_ble_tracker / debug was keeping
every component's loop() running at ~128 Hz instead of the documented
~62 Hz.

This has become more visible recently as more components convert to
PollingComponent (which uses set_interval internally) and more in-tree
code uses set_interval / set_timeout directly. Adding or removing any
scheduled item silently changed every other component's loop cadence.
App.set_loop_interval() for power savings was also silently defeated.

After:
- Phase A (every tick): drain wake notifications, run scheduler.call(),
  feed WDT
- Phase B (gated by loop_interval_ or HighFrequencyLoopRequester):
  iterate registered components and update last_loop_

Sleep = min(time-until-next-component-phase, next_schedule_in()). When
a scheduler event wakes us early, Phase A services it and the component
phase stays gated independently. loop_interval_ is now a true minimum
interval between component phases.

The delay_time/2 floor is removed. Any legitimate need to wake faster
than loop_interval_ has proper mechanisms:
- HighFrequencyLoopRequester for sustained fast-loop needs
- Application::wake_loop_threadsafe() from any context (new in 2026.4.0)
  for one-shot wake-on-event

Also guards against set_interval(0) misuse — it asks the main loop to
spin forever, which was never the intended API. Warns at creation time
pointing authors at HighFrequencyLoopRequester. set_timeout(0)/defer()
is unaffected; zero-delay one-shots remain legitimate.

Runtime stats: process_pending_stats is now called on every tick (not
just when the component phase runs) so log_interval_ isn't quantized to
the component-phase cadence. Added an inline fast-path gate in
runtime_stats.h that early-outs unless now >= next_log_time_, keeping
Application::loop() slim; the log_stats_ work stays out-of-line.

Ordering constraints preserved:
- defer() callbacks still FIFO before components same-tick (Phase A
  runs before Phase B)
- Scheduled items still execute before components when both due
- Scheduled callbacks still run on main thread only
- loop_component_start_time_ is still set fresh at each component's loop
- WDT is still fed at least once per tick
2026-04-16 14:27:39 -10:00
..
2022-09-06 15:48:01 +12:00
2023-09-12 07:13:24 +12:00

Tests for ESPHome

This directory contains some tests for ESPHome. At the moment, all the tests only work by simply executing esphome over some YAML files that are made to test whether the yaml gets converted to the proper C++ code.

Of course this is all just very high-level and things like unit tests would be much better. So if you have time and know how to set up a unit testing framework for python, please do give it a try.

When adding entries in test_.yaml files we usually need only one file updated, unless conflicting code is generated for different configurations, e.g. wifi and ethernet cannot be tested on the same device.

Current test_.yaml file contents.

Test name Platform Network BLE
test1.yaml ESP32 wifi None
test2.yaml ESP32 ethernet esp32_ble_tracker
test3.yaml ESP8266 wifi N/A
test4.yaml ESP32 ethernet None
test5.yaml ESP32 wifi ble_server
test6.yaml RP2040 wifi N/A
test7.yaml ESP32-C3 wifi N/A
test8.yaml ESP32-S3 wifi None
test10.yaml ESP32 wifi None