mirror of
https://github.com/esphome/esphome.git
synced 2025-09-01 19:02:18 +01:00
Reduce entity memory usage by eliminating field shadowing and bit-packing (#9076)
This commit is contained in:
108
tests/integration/fixtures/host_mode_entity_fields.yaml
Normal file
108
tests/integration/fixtures/host_mode_entity_fields.yaml
Normal file
@@ -0,0 +1,108 @@
|
||||
esphome:
|
||||
name: host-test
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
|
||||
logger:
|
||||
|
||||
# Test various entity types with different flag combinations
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Test Normal Sensor"
|
||||
id: normal_sensor
|
||||
update_interval: 1s
|
||||
lambda: |-
|
||||
return 42.0;
|
||||
|
||||
- platform: template
|
||||
name: "Test Internal Sensor"
|
||||
id: internal_sensor
|
||||
internal: true
|
||||
update_interval: 1s
|
||||
lambda: |-
|
||||
return 43.0;
|
||||
|
||||
- platform: template
|
||||
name: "Test Disabled Sensor"
|
||||
id: disabled_sensor
|
||||
disabled_by_default: true
|
||||
update_interval: 1s
|
||||
lambda: |-
|
||||
return 44.0;
|
||||
|
||||
- platform: template
|
||||
name: "Test Mixed Flags Sensor"
|
||||
id: mixed_flags_sensor
|
||||
internal: true
|
||||
entity_category: diagnostic
|
||||
update_interval: 1s
|
||||
lambda: |-
|
||||
return 45.0;
|
||||
|
||||
- platform: template
|
||||
name: "Test Diagnostic Sensor"
|
||||
id: diagnostic_sensor
|
||||
entity_category: diagnostic
|
||||
update_interval: 1s
|
||||
lambda: |-
|
||||
return 46.0;
|
||||
|
||||
- platform: template
|
||||
name: "Test All Flags Sensor"
|
||||
id: all_flags_sensor
|
||||
internal: true
|
||||
disabled_by_default: true
|
||||
entity_category: diagnostic
|
||||
update_interval: 1s
|
||||
lambda: |-
|
||||
return 47.0;
|
||||
|
||||
# Also test other entity types to ensure bit-packing works across all
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Test Binary Sensor"
|
||||
entity_category: config
|
||||
lambda: |-
|
||||
return true;
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: "Test Text Sensor"
|
||||
disabled_by_default: true
|
||||
lambda: |-
|
||||
return {"Hello"};
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number"
|
||||
initial_value: 50
|
||||
min_value: 0
|
||||
max_value: 100
|
||||
step: 1
|
||||
optimistic: true
|
||||
entity_category: diagnostic
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
name: "Test Select"
|
||||
options:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
initial_option: "Option 1"
|
||||
optimistic: true
|
||||
internal: true
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Test Switch"
|
||||
optimistic: true
|
||||
disabled_by_default: true
|
||||
entity_category: config
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Test Button"
|
||||
on_press:
|
||||
- logger.log: "Button pressed"
|
93
tests/integration/test_host_mode_entity_fields.py
Normal file
93
tests/integration/test_host_mode_entity_fields.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Integration test for entity bit-packed fields."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from aioesphomeapi import EntityCategory, EntityState
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_host_mode_entity_fields(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test entity bit-packed fields work correctly with all possible values."""
|
||||
# Write, compile and run the ESPHome device, then connect to API
|
||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||
# Get all entities
|
||||
entities = await client.list_entities_services()
|
||||
|
||||
# Create a map of entity names to entity info
|
||||
entity_map = {}
|
||||
for entity in entities[0]:
|
||||
if hasattr(entity, "name"):
|
||||
entity_map[entity.name] = entity
|
||||
|
||||
# Test entities that should be visible via API (non-internal)
|
||||
visible_test_cases = [
|
||||
# (entity_name, expected_disabled_by_default, expected_entity_category)
|
||||
("Test Normal Sensor", False, EntityCategory.NONE),
|
||||
("Test Disabled Sensor", True, EntityCategory.NONE),
|
||||
("Test Diagnostic Sensor", False, EntityCategory.DIAGNOSTIC),
|
||||
("Test Switch", True, EntityCategory.CONFIG),
|
||||
("Test Binary Sensor", False, EntityCategory.CONFIG),
|
||||
("Test Number", False, EntityCategory.DIAGNOSTIC),
|
||||
]
|
||||
|
||||
# Test entities that should NOT be visible via API (internal)
|
||||
internal_entities = [
|
||||
"Test Internal Sensor",
|
||||
"Test Mixed Flags Sensor",
|
||||
"Test All Flags Sensor",
|
||||
"Test Select",
|
||||
]
|
||||
|
||||
# Verify visible entities
|
||||
for entity_name, expected_disabled, expected_category in visible_test_cases:
|
||||
assert entity_name in entity_map, (
|
||||
f"Entity '{entity_name}' not found - it should be visible via API"
|
||||
)
|
||||
entity = entity_map[entity_name]
|
||||
|
||||
# Check disabled_by_default flag
|
||||
assert entity.disabled_by_default == expected_disabled, (
|
||||
f"{entity_name}: disabled_by_default flag mismatch - "
|
||||
f"expected {expected_disabled}, got {entity.disabled_by_default}"
|
||||
)
|
||||
|
||||
# Check entity_category
|
||||
assert entity.entity_category == expected_category, (
|
||||
f"{entity_name}: entity_category mismatch - "
|
||||
f"expected {expected_category}, got {entity.entity_category}"
|
||||
)
|
||||
|
||||
# Verify internal entities are NOT visible
|
||||
for entity_name in internal_entities:
|
||||
assert entity_name not in entity_map, (
|
||||
f"Entity '{entity_name}' found in API response - "
|
||||
f"internal entities should not be exposed via API"
|
||||
)
|
||||
|
||||
# Subscribe to states to verify has_state flag works
|
||||
states: dict[int, EntityState] = {}
|
||||
state_received = asyncio.Event()
|
||||
|
||||
def on_state(state: EntityState) -> None:
|
||||
states[state.key] = state
|
||||
state_received.set()
|
||||
|
||||
client.subscribe_states(on_state)
|
||||
|
||||
# Wait for at least one state
|
||||
try:
|
||||
await asyncio.wait_for(state_received.wait(), timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("No states received within 5 seconds")
|
||||
|
||||
# Verify we received states (which means has_state flag is working)
|
||||
assert len(states) > 0, "No states received - has_state flag may not be working"
|
Reference in New Issue
Block a user