mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 00:31:58 +00:00
[select][fan] Use StringRef for on_value/on_preset_set triggers to avoid heap allocation (#13324)
This commit is contained in:
85
tests/integration/fixtures/select_stringref_trigger.yaml
Normal file
85
tests/integration/fixtures/select_stringref_trigger.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
esphome:
|
||||
name: select-stringref-test
|
||||
friendly_name: Select StringRef Test
|
||||
|
||||
host:
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
api:
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
name: "Test Select"
|
||||
id: test_select
|
||||
optimistic: true
|
||||
options:
|
||||
- "Option A"
|
||||
- "Option B"
|
||||
- "Option C"
|
||||
initial_option: "Option A"
|
||||
on_value:
|
||||
then:
|
||||
# Test 1: Log the value directly (StringRef -> const char* via c_str())
|
||||
- logger.log:
|
||||
format: "Select value: %s"
|
||||
args: ['x.c_str()']
|
||||
# Test 2: String concatenation (StringRef + const char* -> std::string)
|
||||
- lambda: |-
|
||||
std::string with_suffix = x + " selected";
|
||||
ESP_LOGI("test", "Concatenated: %s", with_suffix.c_str());
|
||||
# Test 3: Comparison (StringRef == const char*)
|
||||
- lambda: |-
|
||||
if (x == "Option B") {
|
||||
ESP_LOGI("test", "Option B was selected");
|
||||
}
|
||||
# Test 4: Use index parameter (variable name is 'i')
|
||||
- lambda: |-
|
||||
ESP_LOGI("test", "Select index: %d", (int)i);
|
||||
# Test 5: StringRef.length() method
|
||||
- lambda: |-
|
||||
ESP_LOGI("test", "Length: %d", (int)x.length());
|
||||
# Test 6: StringRef.find() method with substring
|
||||
- lambda: |-
|
||||
if (x.find("Option") != std::string::npos) {
|
||||
ESP_LOGI("test", "Found 'Option' in value");
|
||||
}
|
||||
# Test 7: StringRef.find() method with character
|
||||
- lambda: |-
|
||||
size_t space_pos = x.find(' ');
|
||||
if (space_pos != std::string::npos) {
|
||||
ESP_LOGI("test", "Space at position: %d", (int)space_pos);
|
||||
}
|
||||
# Test 8: StringRef.substr() method
|
||||
- lambda: |-
|
||||
std::string prefix = x.substr(0, 6);
|
||||
ESP_LOGI("test", "Substr prefix: %s", prefix.c_str());
|
||||
|
||||
# Second select with numeric options to test ADL functions
|
||||
- platform: template
|
||||
name: "Baud Rate"
|
||||
id: baud_select
|
||||
optimistic: true
|
||||
options:
|
||||
- "9600"
|
||||
- "115200"
|
||||
initial_option: "9600"
|
||||
on_value:
|
||||
then:
|
||||
# Test 9: stoi via ADL
|
||||
- lambda: |-
|
||||
int baud = stoi(x);
|
||||
ESP_LOGI("test", "stoi result: %d", baud);
|
||||
# Test 10: stol via ADL
|
||||
- lambda: |-
|
||||
long baud_long = stol(x);
|
||||
ESP_LOGI("test", "stol result: %ld", baud_long);
|
||||
# Test 11: stof via ADL
|
||||
- lambda: |-
|
||||
float baud_float = stof(x);
|
||||
ESP_LOGI("test", "stof result: %.0f", baud_float);
|
||||
# Test 12: stod via ADL
|
||||
- lambda: |-
|
||||
double baud_double = stod(x);
|
||||
ESP_LOGI("test", "stod result: %.0f", baud_double);
|
||||
143
tests/integration/test_select_stringref_trigger.py
Normal file
143
tests/integration/test_select_stringref_trigger.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""Integration test for select on_value trigger with StringRef parameter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_select_stringref_trigger(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test select on_value trigger passes StringRef that works with string operations."""
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
# Track log messages to verify StringRef operations work
|
||||
value_logged_future = loop.create_future()
|
||||
concatenated_future = loop.create_future()
|
||||
comparison_future = loop.create_future()
|
||||
index_logged_future = loop.create_future()
|
||||
length_future = loop.create_future()
|
||||
find_substr_future = loop.create_future()
|
||||
find_char_future = loop.create_future()
|
||||
substr_future = loop.create_future()
|
||||
# ADL functions
|
||||
stoi_future = loop.create_future()
|
||||
stol_future = loop.create_future()
|
||||
stof_future = loop.create_future()
|
||||
stod_future = loop.create_future()
|
||||
|
||||
# Patterns to match in logs
|
||||
value_pattern = re.compile(r"Select value: Option B")
|
||||
concatenated_pattern = re.compile(r"Concatenated: Option B selected")
|
||||
comparison_pattern = re.compile(r"Option B was selected")
|
||||
index_pattern = re.compile(r"Select index: 1")
|
||||
length_pattern = re.compile(r"Length: 8") # "Option B" is 8 chars
|
||||
find_substr_pattern = re.compile(r"Found 'Option' in value")
|
||||
find_char_pattern = re.compile(r"Space at position: 6") # space at index 6
|
||||
substr_pattern = re.compile(r"Substr prefix: Option")
|
||||
# ADL function patterns (115200 from baud rate select)
|
||||
stoi_pattern = re.compile(r"stoi result: 115200")
|
||||
stol_pattern = re.compile(r"stol result: 115200")
|
||||
stof_pattern = re.compile(r"stof result: 115200")
|
||||
stod_pattern = re.compile(r"stod result: 115200")
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
"""Check log output for expected messages."""
|
||||
if not value_logged_future.done() and value_pattern.search(line):
|
||||
value_logged_future.set_result(True)
|
||||
if not concatenated_future.done() and concatenated_pattern.search(line):
|
||||
concatenated_future.set_result(True)
|
||||
if not comparison_future.done() and comparison_pattern.search(line):
|
||||
comparison_future.set_result(True)
|
||||
if not index_logged_future.done() and index_pattern.search(line):
|
||||
index_logged_future.set_result(True)
|
||||
if not length_future.done() and length_pattern.search(line):
|
||||
length_future.set_result(True)
|
||||
if not find_substr_future.done() and find_substr_pattern.search(line):
|
||||
find_substr_future.set_result(True)
|
||||
if not find_char_future.done() and find_char_pattern.search(line):
|
||||
find_char_future.set_result(True)
|
||||
if not substr_future.done() and substr_pattern.search(line):
|
||||
substr_future.set_result(True)
|
||||
# ADL functions
|
||||
if not stoi_future.done() and stoi_pattern.search(line):
|
||||
stoi_future.set_result(True)
|
||||
if not stol_future.done() and stol_pattern.search(line):
|
||||
stol_future.set_result(True)
|
||||
if not stof_future.done() and stof_pattern.search(line):
|
||||
stof_future.set_result(True)
|
||||
if not stod_future.done() and stod_pattern.search(line):
|
||||
stod_future.set_result(True)
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=check_output),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "select-stringref-test"
|
||||
|
||||
# List entities to find our select
|
||||
entities, _ = await client.list_entities_services()
|
||||
|
||||
select_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Test Select"),
|
||||
None,
|
||||
)
|
||||
assert select_entity is not None, "Test Select entity not found"
|
||||
|
||||
baud_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Baud Rate"),
|
||||
None,
|
||||
)
|
||||
assert baud_entity is not None, "Baud Rate entity not found"
|
||||
|
||||
# Change select to Option B - this should trigger on_value with StringRef
|
||||
client.select_command(select_entity.key, "Option B")
|
||||
# Change baud to 115200 - this tests ADL functions (stoi, stol, stof, stod)
|
||||
client.select_command(baud_entity.key, "115200")
|
||||
|
||||
# Wait for all log messages confirming StringRef operations work
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.gather(
|
||||
value_logged_future,
|
||||
concatenated_future,
|
||||
comparison_future,
|
||||
index_logged_future,
|
||||
length_future,
|
||||
find_substr_future,
|
||||
find_char_future,
|
||||
substr_future,
|
||||
stoi_future,
|
||||
stol_future,
|
||||
stof_future,
|
||||
stod_future,
|
||||
),
|
||||
timeout=5.0,
|
||||
)
|
||||
except TimeoutError:
|
||||
results = {
|
||||
"value_logged": value_logged_future.done(),
|
||||
"concatenated": concatenated_future.done(),
|
||||
"comparison": comparison_future.done(),
|
||||
"index_logged": index_logged_future.done(),
|
||||
"length": length_future.done(),
|
||||
"find_substr": find_substr_future.done(),
|
||||
"find_char": find_char_future.done(),
|
||||
"substr": substr_future.done(),
|
||||
"stoi": stoi_future.done(),
|
||||
"stol": stol_future.done(),
|
||||
"stof": stof_future.done(),
|
||||
"stod": stod_future.done(),
|
||||
}
|
||||
pytest.fail(f"StringRef operations failed - received: {results}")
|
||||
Reference in New Issue
Block a user