mirror of
https://github.com/esphome/esphome.git
synced 2025-08-09 11:57:46 +00:00
cover
This commit is contained in:
parent
67a20e212d
commit
2946bc9d72
156
tests/integration/fixtures/scheduler_string_test.yaml
Normal file
156
tests/integration/fixtures/scheduler_string_test.yaml
Normal file
@ -0,0 +1,156 @@
|
||||
esphome:
|
||||
name: scheduler-string-test
|
||||
on_boot:
|
||||
priority: -100
|
||||
then:
|
||||
- logger.log: "Starting scheduler string tests"
|
||||
platformio_options:
|
||||
build_flags:
|
||||
- "-DESPHOME_DEBUG_SCHEDULER" # Enable scheduler debug logging
|
||||
|
||||
host:
|
||||
api:
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
globals:
|
||||
- id: timeout_counter
|
||||
type: int
|
||||
initial_value: '0'
|
||||
- id: interval_counter
|
||||
type: int
|
||||
initial_value: '0'
|
||||
- id: dynamic_counter
|
||||
type: int
|
||||
initial_value: '0'
|
||||
- id: static_tests_done
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: dynamic_tests_done
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: results_reported
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
|
||||
script:
|
||||
- id: test_static_strings
|
||||
then:
|
||||
- logger.log: "Testing static string timeouts and intervals"
|
||||
- lambda: |-
|
||||
auto *component1 = id(test_sensor1);
|
||||
// Test 1: Static string literals with set_timeout
|
||||
App.scheduler.set_timeout(component1, "static_timeout_1", 100, []() {
|
||||
ESP_LOGI("test", "Static timeout 1 fired");
|
||||
id(timeout_counter) += 1;
|
||||
});
|
||||
|
||||
// Test 2: Static const char* with set_timeout
|
||||
static const char* TIMEOUT_NAME = "static_timeout_2";
|
||||
App.scheduler.set_timeout(component1, TIMEOUT_NAME, 200, []() {
|
||||
ESP_LOGI("test", "Static timeout 2 fired");
|
||||
id(timeout_counter) += 1;
|
||||
});
|
||||
|
||||
// Test 3: Static string literal with set_interval
|
||||
App.scheduler.set_interval(component1, "static_interval_1", 500, []() {
|
||||
ESP_LOGI("test", "Static interval 1 fired, count: %d", id(interval_counter));
|
||||
id(interval_counter) += 1;
|
||||
if (id(interval_counter) >= 3) {
|
||||
App.scheduler.cancel_interval(id(test_sensor1), "static_interval_1");
|
||||
ESP_LOGI("test", "Cancelled static interval 1");
|
||||
}
|
||||
});
|
||||
|
||||
// Test 4: Empty string (should be handled safely)
|
||||
App.scheduler.set_timeout(component1, "", 300, []() {
|
||||
ESP_LOGI("test", "Empty string timeout fired");
|
||||
});
|
||||
|
||||
- id: test_dynamic_strings
|
||||
then:
|
||||
- logger.log: "Testing dynamic string timeouts and intervals"
|
||||
- lambda: |-
|
||||
auto *component2 = id(test_sensor2);
|
||||
|
||||
// Test 5: Dynamic string with set_timeout (std::string)
|
||||
std::string dynamic_name = "dynamic_timeout_" + std::to_string(id(dynamic_counter)++);
|
||||
App.scheduler.set_timeout(component2, dynamic_name, 150, []() {
|
||||
ESP_LOGI("test", "Dynamic timeout fired");
|
||||
id(timeout_counter) += 1;
|
||||
});
|
||||
|
||||
// Test 6: Dynamic string with set_interval
|
||||
std::string interval_name = "dynamic_interval_" + std::to_string(id(dynamic_counter)++);
|
||||
App.scheduler.set_interval(component2, interval_name, 600, [interval_name]() {
|
||||
ESP_LOGI("test", "Dynamic interval fired: %s", interval_name.c_str());
|
||||
id(interval_counter) += 1;
|
||||
if (id(interval_counter) >= 6) {
|
||||
App.scheduler.cancel_interval(id(test_sensor2), interval_name);
|
||||
ESP_LOGI("test", "Cancelled dynamic interval");
|
||||
}
|
||||
});
|
||||
|
||||
// Test 7: Cancel with different string object but same content
|
||||
std::string cancel_name = "cancel_test";
|
||||
App.scheduler.set_timeout(component2, cancel_name, 5000, []() {
|
||||
ESP_LOGI("test", "This should be cancelled");
|
||||
});
|
||||
|
||||
// Cancel using a different string object
|
||||
std::string cancel_name_2 = "cancel_test";
|
||||
App.scheduler.cancel_timeout(component2, cancel_name_2);
|
||||
ESP_LOGI("test", "Cancelled timeout using different string object");
|
||||
|
||||
- id: report_results
|
||||
then:
|
||||
- lambda: |-
|
||||
ESP_LOGI("test", "Final results - Timeouts: %d, Intervals: %d",
|
||||
id(timeout_counter), id(interval_counter));
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: Test Sensor 1
|
||||
id: test_sensor1
|
||||
lambda: return 1.0;
|
||||
update_interval: never
|
||||
|
||||
- platform: template
|
||||
name: Test Sensor 2
|
||||
id: test_sensor2
|
||||
lambda: return 2.0;
|
||||
update_interval: never
|
||||
|
||||
interval:
|
||||
# Run static string tests after boot - using script to run once
|
||||
- interval: 0.5s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(static_tests_done) == false;'
|
||||
then:
|
||||
- lambda: 'id(static_tests_done) = true;'
|
||||
- script.execute: test_static_strings
|
||||
- logger.log: "Started static string tests"
|
||||
|
||||
# Run dynamic string tests after static tests
|
||||
- interval: 1s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(static_tests_done) && !id(dynamic_tests_done);'
|
||||
then:
|
||||
- lambda: 'id(dynamic_tests_done) = true;'
|
||||
- delay: 1s
|
||||
- script.execute: test_dynamic_strings
|
||||
|
||||
# Report results after all tests
|
||||
- interval: 1s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(dynamic_tests_done) && !id(results_reported);'
|
||||
then:
|
||||
- lambda: 'id(results_reported) = true;'
|
||||
- delay: 3s
|
||||
- script.execute: report_results
|
163
tests/integration/test_scheduler_string_test.py
Normal file
163
tests/integration/test_scheduler_string_test.py
Normal file
@ -0,0 +1,163 @@
|
||||
"""Test scheduler string optimization with static and dynamic strings."""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scheduler_string_test(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test that scheduler handles both static and dynamic strings correctly."""
|
||||
# Track counts
|
||||
timeout_count = 0
|
||||
interval_count = 0
|
||||
|
||||
# Events for each test completion
|
||||
static_timeout_1_fired = asyncio.Event()
|
||||
static_timeout_2_fired = asyncio.Event()
|
||||
static_interval_fired = asyncio.Event()
|
||||
static_interval_cancelled = asyncio.Event()
|
||||
empty_string_timeout_fired = asyncio.Event()
|
||||
dynamic_timeout_fired = asyncio.Event()
|
||||
dynamic_interval_fired = asyncio.Event()
|
||||
cancel_test_done = asyncio.Event()
|
||||
final_results_logged = asyncio.Event()
|
||||
|
||||
# Track interval counts
|
||||
static_interval_count = 0
|
||||
dynamic_interval_count = 0
|
||||
|
||||
def on_log_line(line: str) -> None:
|
||||
nonlocal \
|
||||
timeout_count, \
|
||||
interval_count, \
|
||||
static_interval_count, \
|
||||
dynamic_interval_count
|
||||
|
||||
# Strip ANSI color codes
|
||||
clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)
|
||||
|
||||
# Check for static timeout completions
|
||||
if "Static timeout 1 fired" in clean_line:
|
||||
static_timeout_1_fired.set()
|
||||
timeout_count += 1
|
||||
|
||||
elif "Static timeout 2 fired" in clean_line:
|
||||
static_timeout_2_fired.set()
|
||||
timeout_count += 1
|
||||
|
||||
# Check for static interval
|
||||
elif "Static interval 1 fired" in clean_line:
|
||||
match = re.search(r"count: (\d+)", clean_line)
|
||||
if match:
|
||||
static_interval_count = int(match.group(1))
|
||||
static_interval_fired.set()
|
||||
|
||||
elif "Cancelled static interval 1" in clean_line:
|
||||
static_interval_cancelled.set()
|
||||
|
||||
# Check for empty string timeout
|
||||
elif "Empty string timeout fired" in clean_line:
|
||||
empty_string_timeout_fired.set()
|
||||
|
||||
# Check for dynamic string tests
|
||||
elif "Dynamic timeout fired" in clean_line:
|
||||
dynamic_timeout_fired.set()
|
||||
timeout_count += 1
|
||||
|
||||
elif "Dynamic interval fired" in clean_line:
|
||||
dynamic_interval_count += 1
|
||||
dynamic_interval_fired.set()
|
||||
|
||||
# Check for cancel test
|
||||
elif "Cancelled timeout using different string object" in clean_line:
|
||||
cancel_test_done.set()
|
||||
|
||||
# Check for final results
|
||||
elif "Final results" in clean_line:
|
||||
match = re.search(r"Timeouts: (\d+), Intervals: (\d+)", clean_line)
|
||||
if match:
|
||||
timeout_count = int(match.group(1))
|
||||
interval_count = int(match.group(2))
|
||||
final_results_logged.set()
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=on_log_line),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify we can connect
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "scheduler-string-test"
|
||||
|
||||
# Wait for static string tests
|
||||
try:
|
||||
await asyncio.wait_for(static_timeout_1_fired.wait(), timeout=3.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Static timeout 1 did not fire within 3 seconds")
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(static_timeout_2_fired.wait(), timeout=3.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Static timeout 2 did not fire within 3 seconds")
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(static_interval_fired.wait(), timeout=3.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Static interval did not fire within 3 seconds")
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(static_interval_cancelled.wait(), timeout=3.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Static interval was not cancelled within 3 seconds")
|
||||
|
||||
# Verify static interval ran at least 3 times
|
||||
assert static_interval_count >= 2, (
|
||||
f"Expected static interval to run at least 3 times, got {static_interval_count + 1}"
|
||||
)
|
||||
|
||||
# Wait for dynamic string tests
|
||||
try:
|
||||
await asyncio.wait_for(dynamic_timeout_fired.wait(), timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Dynamic timeout did not fire within 5 seconds")
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(dynamic_interval_fired.wait(), timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Dynamic interval did not fire within 5 seconds")
|
||||
|
||||
# Wait for cancel test
|
||||
try:
|
||||
await asyncio.wait_for(cancel_test_done.wait(), timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Cancel test did not complete within 5 seconds")
|
||||
|
||||
# Wait for final results
|
||||
try:
|
||||
await asyncio.wait_for(final_results_logged.wait(), timeout=10.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Final results were not logged within 10 seconds")
|
||||
|
||||
# Verify results
|
||||
assert timeout_count >= 3, f"Expected at least 3 timeouts, got {timeout_count}"
|
||||
assert interval_count >= 3, (
|
||||
f"Expected at least 3 interval fires, got {interval_count}"
|
||||
)
|
||||
|
||||
# Empty string timeout DOES fire (scheduler accepts empty names)
|
||||
assert empty_string_timeout_fired.is_set(), "Empty string timeout should fire"
|
||||
|
||||
# Log final status
|
||||
print("\nScheduler string test completed successfully:")
|
||||
print(f" Timeouts fired: {timeout_count}")
|
||||
print(f" Intervals fired: {interval_count}")
|
||||
print(f" Static interval count: {static_interval_count + 1}")
|
||||
print(f" Dynamic interval count: {dynamic_interval_count}")
|
Loading…
x
Reference in New Issue
Block a user