[core] Merge set_name + set_entity_strings into configure_entity_ (#14444)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
J. Nick Koston
2026-03-07 07:26:01 -10:00
committed by GitHub
parent f57fa4cc8d
commit 45f20d9c06
9 changed files with 112 additions and 99 deletions
+13 -2
View File
@@ -10,8 +10,8 @@ static const char *const TAG = "entity_base";
// Entity Name // Entity Name
const StringRef &EntityBase::get_name() const { return this->name_; } const StringRef &EntityBase::get_name() const { return this->name_; }
void EntityBase::set_name(const char *name) { this->set_name(name, 0); }
void EntityBase::set_name(const char *name, uint32_t object_id_hash) { void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed) {
this->name_ = StringRef(name); this->name_ = StringRef(name);
if (this->name_.empty()) { if (this->name_.empty()) {
#ifdef USE_DEVICES #ifdef USE_DEVICES
@@ -44,6 +44,17 @@ void EntityBase::set_name(const char *name, uint32_t object_id_hash) {
this->calc_object_id_(); this->calc_object_id_();
} }
} }
// Unpack entity string table indices.
// Packed: [23..16] icon | [15..8] UoM | [7..0] device_class (each 8 bits)
#ifdef USE_ENTITY_DEVICE_CLASS
this->device_class_idx_ = entity_strings_packed & 0xFF;
#endif
#ifdef USE_ENTITY_UNIT_OF_MEASUREMENT
this->uom_idx_ = (entity_strings_packed >> 8) & 0xFF;
#endif
#ifdef USE_ENTITY_ICON
this->icon_idx_ = (entity_strings_packed >> 16) & 0xFF;
#endif
} }
// Weak default lookup functions — overridden by generated code in main.cpp // Weak default lookup functions — overridden by generated code in main.cpp
+11 -19
View File
@@ -12,6 +12,10 @@
#include "device.h" #include "device.h"
#endif #endif
// Forward declarations for friend access from codegen-generated setup()
void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h
void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests
namespace esphome { namespace esphome {
// Extern lookup functions for entity string tables. // Extern lookup functions for entity string tables.
@@ -54,12 +58,8 @@ enum EntityCategory : uint8_t {
// The generic Entity base class that provides an interface common to all Entities. // The generic Entity base class that provides an interface common to all Entities.
class EntityBase { class EntityBase {
public: public:
// Get/set the name of this Entity // Get the name of this Entity
const StringRef &get_name() const; const StringRef &get_name() const;
void set_name(const char *name);
/// Set name with pre-computed object_id hash (avoids runtime hash calculation)
/// Use hash=0 for dynamic names that need runtime calculation
void set_name(const char *name, uint32_t object_id_hash);
// Get whether this Entity has its own name or it should use the device friendly_name. // Get whether this Entity has its own name or it should use the device friendly_name.
bool has_own_name() const { return this->flags_.has_own_name; } bool has_own_name() const { return this->flags_.has_own_name; }
@@ -104,20 +104,6 @@ class EntityBase {
this->flags_.entity_category = static_cast<uint8_t>(entity_category); this->flags_.entity_category = static_cast<uint8_t>(entity_category);
} }
// Set entity string table indices — one call per entity from codegen.
// Packed: [23..16] icon | [15..8] UoM | [7..0] device_class (each 8 bits)
void set_entity_strings([[maybe_unused]] uint32_t packed) {
#ifdef USE_ENTITY_DEVICE_CLASS
this->device_class_idx_ = packed & 0xFF;
#endif
#ifdef USE_ENTITY_UNIT_OF_MEASUREMENT
this->uom_idx_ = (packed >> 8) & 0xFF;
#endif
#ifdef USE_ENTITY_ICON
this->icon_idx_ = (packed >> 16) & 0xFF;
#endif
}
// Get this entity's device class into a stack buffer. // Get this entity's device class into a stack buffer.
// On non-ESP8266: returns pointer to PROGMEM string directly (buffer unused). // On non-ESP8266: returns pointer to PROGMEM string directly (buffer unused).
// On ESP8266: copies from PROGMEM to buffer, returns buffer pointer. // On ESP8266: copies from PROGMEM to buffer, returns buffer pointer.
@@ -239,6 +225,12 @@ class EntityBase {
} }
protected: protected:
friend void ::setup();
friend void ::original_setup();
/// Combined entity setup from codegen: set name, object_id hash, and entity string indices.
void configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed);
/// Non-template helper for make_entity_preference() to avoid code bloat. /// Non-template helper for make_entity_preference() to avoid code bloat.
/// When preference hash algorithm changes, migration logic goes here. /// When preference hash algorithm changes, migration logic goes here.
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version); ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version);
+11 -6
View File
@@ -31,8 +31,10 @@ DOMAIN = "entity_string_pool"
_KEY_DC_IDX = "_entity_dc_idx" _KEY_DC_IDX = "_entity_dc_idx"
_KEY_UOM_IDX = "_entity_uom_idx" _KEY_UOM_IDX = "_entity_uom_idx"
_KEY_ICON_IDX = "_entity_icon_idx" _KEY_ICON_IDX = "_entity_icon_idx"
_KEY_ENTITY_NAME = "_entity_name"
_KEY_OBJECT_ID_HASH = "_entity_object_id_hash"
# Bit layout for set_entity_strings(packed) — must match C++ setter in entity_base.h: # Bit layout for entity_strings_packed in configure_entity_() — must match C++ in entity_base.h:
# [23..16] icon (8 bits) | [15..8] UoM (8 bits) | [7..0] device_class (8 bits) # [23..16] icon (8 bits) | [15..8] UoM (8 bits) | [7..0] device_class (8 bits)
_DC_SHIFT = 0 _DC_SHIFT = 0
_UOM_SHIFT = 8 _UOM_SHIFT = 8
@@ -219,17 +221,18 @@ def setup_unit_of_measurement(config: ConfigType) -> None:
def finalize_entity_strings(var: MockObj, config: ConfigType) -> None: def finalize_entity_strings(var: MockObj, config: ConfigType) -> None:
"""Emit a single set_entity_strings() call with all packed indices. """Emit a single configure_entity_() call with name, hash, and packed string indices.
Call this at the end of each component's setup function, after Call this at the end of each component's setup function, after
setup_entity() and any register_device_class/register_unit_of_measurement calls. setup_entity() and any register_device_class/register_unit_of_measurement calls.
""" """
entity_name = config[_KEY_ENTITY_NAME]
object_id_hash = config[_KEY_OBJECT_ID_HASH]
dc_idx = config.get(_KEY_DC_IDX, 0) dc_idx = config.get(_KEY_DC_IDX, 0)
uom_idx = config.get(_KEY_UOM_IDX, 0) uom_idx = config.get(_KEY_UOM_IDX, 0)
icon_idx = config.get(_KEY_ICON_IDX, 0) icon_idx = config.get(_KEY_ICON_IDX, 0)
packed = (dc_idx << _DC_SHIFT) | (uom_idx << _UOM_SHIFT) | (icon_idx << _ICON_SHIFT) packed = (dc_idx << _DC_SHIFT) | (uom_idx << _UOM_SHIFT) | (icon_idx << _ICON_SHIFT)
if packed != 0: add(var.configure_entity_(entity_name, object_id_hash, packed))
add(var.set_entity_strings(packed))
def get_base_entity_object_id( def get_base_entity_object_id(
@@ -331,13 +334,15 @@ async def _setup_entity_impl(var: MockObj, config: ConfigType, platform: str) ->
device: MockObj = await get_variable(device_id_obj) device: MockObj = await get_variable(device_id_obj)
add(var.set_device(device)) add(var.set_device(device))
# Set the entity name with pre-computed object_id hash # Pre-compute entity name and object_id hash for configure_entity_()
# which is emitted later by finalize_entity_strings().
# For named entities: pre-compute hash from entity name # For named entities: pre-compute hash from entity name
# For empty-name entities: pass 0, C++ calculates hash at runtime from # For empty-name entities: pass 0, C++ calculates hash at runtime from
# device name, friendly_name, or app name (bug-for-bug compatibility) # device name, friendly_name, or app name (bug-for-bug compatibility)
entity_name = config[CONF_NAME] entity_name = config[CONF_NAME]
object_id_hash = fnv1_hash_object_id(entity_name) if entity_name else 0 object_id_hash = fnv1_hash_object_id(entity_name) if entity_name else 0
add(var.set_name(entity_name, object_id_hash)) config[_KEY_ENTITY_NAME] = entity_name
config[_KEY_OBJECT_ID_HASH] = object_id_hash
# Only set disabled_by_default if True (default is False) # Only set disabled_by_default if True (default is False)
if config[CONF_DISABLED_BY_DEFAULT]: if config[CONF_DISABLED_BY_DEFAULT]:
add(var.set_disabled_by_default(True)) add(var.set_disabled_by_default(True))
@@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main):
) )
# Then # Then
assert 'bs_1->set_name("test bs1",' in main_cpp assert 'bs_1->configure_entity_("test bs1",' in main_cpp
assert "bs_1->set_pin(" in main_cpp assert "bs_1->set_pin(" in main_cpp
+1 -1
View File
@@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main):
main_cpp = generate_main("tests/component_tests/button/test_button.yaml") main_cpp = generate_main("tests/component_tests/button/test_button.yaml")
# Then # Then
assert 'wol_1->set_name("wol_test_1",' in main_cpp assert 'wol_1->configure_entity_("wol_test_1",' in main_cpp
assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp
+13 -2
View File
@@ -1,5 +1,15 @@
"""Tests for the sensor component.""" """Tests for the sensor component."""
import re
def _extract_packed_value(main_cpp, var_name):
"""Extract the third (packed) argument from a configure_entity_ call."""
pattern = rf"{re.escape(var_name)}->configure_entity_\([^,]+,\s*\w+,\s*(\d+)\)"
match = re.search(pattern, main_cpp)
assert match, f"configure_entity_ call not found for {var_name}"
return int(match.group(1))
def test_sensor_device_class_set(generate_main): def test_sensor_device_class_set(generate_main):
""" """
@@ -10,5 +20,6 @@ def test_sensor_device_class_set(generate_main):
# When # When
main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml") main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml")
# Then # Then: device_class: voltage means packed value must be non-zero
assert "s_1->set_entity_strings(" in main_cpp packed = _extract_packed_value(main_cpp, "s_1")
assert packed != 0
+1 -1
View File
@@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main):
main_cpp = generate_main("tests/component_tests/text/test_text.yaml") main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
# Then # Then
assert 'it_1->set_name("test 1 text",' in main_cpp assert 'it_1->configure_entity_("test 1 text",' in main_cpp
def test_text_config_value_internal_set(generate_main): def test_text_config_value_internal_set(generate_main):
@@ -1,5 +1,15 @@
"""Tests for the text sensor component.""" """Tests for the text sensor component."""
import re
def _extract_packed_value(main_cpp, var_name):
"""Extract the third (packed) argument from a configure_entity_ call."""
pattern = rf"{re.escape(var_name)}->configure_entity_\([^,]+,\s*\w+,\s*(\d+)\)"
match = re.search(pattern, main_cpp)
assert match, f"configure_entity_ call not found for {var_name}"
return int(match.group(1))
def test_text_sensor_is_setup(generate_main): def test_text_sensor_is_setup(generate_main):
""" """
@@ -25,9 +35,9 @@ def test_text_sensor_sets_mandatory_fields(generate_main):
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
# Then # Then
assert 'ts_1->set_name("Template Text Sensor 1",' in main_cpp assert 'ts_1->configure_entity_("Template Text Sensor 1",' in main_cpp
assert 'ts_2->set_name("Template Text Sensor 2",' in main_cpp assert 'ts_2->configure_entity_("Template Text Sensor 2",' in main_cpp
assert 'ts_3->set_name("Template Text Sensor 3",' in main_cpp assert 'ts_3->configure_entity_("Template Text Sensor 3",' in main_cpp
def test_text_sensor_config_value_internal_set(generate_main): def test_text_sensor_config_value_internal_set(generate_main):
@@ -53,6 +63,9 @@ def test_text_sensor_device_class_set(generate_main):
# When # When
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
# Then # Then: ts_2 has device_class: timestamp, ts_3 has device_class: date
assert "ts_2->set_entity_strings(" in main_cpp # so their packed values must be non-zero
assert "ts_3->set_entity_strings(" in main_cpp packed_ts_2 = _extract_packed_value(main_cpp, "ts_2")
assert packed_ts_2 != 0
packed_ts_3 = _extract_packed_value(main_cpp, "ts_3")
assert packed_ts_3 != 0
+42 -61
View File
@@ -32,9 +32,11 @@ from esphome.helpers import sanitize, snake_case
from .common import load_config_from_fixture from .common import load_config_from_fixture
# Pre-compiled regex pattern for extracting names from set_name calls # Pre-compiled regex pattern for extracting names from configure_entity_/set_name calls
# Matches: .set_name("name", hash) or .set_name("name") # Matches: .configure_entity_("name", ...) or .set_name("name", ...)
SET_NAME_PATTERN = re.compile(r'\.set_name\(["\']([^"\']*)["\']') ENTITY_NAME_PATTERN = re.compile(
r'\.(?:configure_entity_|set_name)\(["\']([^"\']*)["\']'
)
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers" FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers"
@@ -276,15 +278,23 @@ def setup_test_environment() -> Generator[list[str], None, None]:
entity_helpers.add = original_add entity_helpers.add = original_add
def extract_object_id_from_expressions(expressions: list[str]) -> str | None: def extract_object_id_from_config(config: dict[str, Any]) -> str | None:
"""Extract the object ID that would be computed from set_name calls. """Extract the object ID from config keys set by _setup_entity_impl."""
name = config.get("_entity_name")
if name is None:
return None
if name:
return sanitize(snake_case(name))
# Empty name - fall back to friendly_name or device name
if CORE.friendly_name:
return sanitize(snake_case(CORE.friendly_name))
return sanitize(snake_case(CORE.name)) if CORE.name else None
Since object_id is now computed from the name (via snake_case + sanitize),
we extract the name from set_name() calls and compute the expected object_id. def extract_object_id_from_expressions(expressions: list[str]) -> str | None:
For empty names, we fall back to CORE.friendly_name or CORE.name. """Extract the object ID from configure_entity_() calls in generated expressions."""
"""
for expr in expressions: for expr in expressions:
if match := SET_NAME_PATTERN.search(expr): if match := ENTITY_NAME_PATTERN.search(expr):
name = match.group(1) name = match.group(1)
if name: if name:
return sanitize(snake_case(name)) return sanitize(snake_case(name))
@@ -299,8 +309,6 @@ def extract_object_id_from_expressions(expressions: list[str]) -> str | None:
async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) -> None: async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) -> None:
"""Test setup_entity with unique names.""" """Test setup_entity with unique names."""
added_expressions = setup_test_environment
# Create mock entities # Create mock entities
var1 = MockObj("sensor1") var1 = MockObj("sensor1")
var2 = MockObj("sensor2") var2 = MockObj("sensor2")
@@ -312,13 +320,10 @@ async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) ->
} }
await _setup_entity_impl(var1, config1, "sensor") await _setup_entity_impl(var1, config1, "sensor")
# Get object ID from first entity # Get object ID from first entity (stored in config, emitted later by finalize)
object_id1 = extract_object_id_from_expressions(added_expressions) object_id1 = extract_object_id_from_config(config1)
assert object_id1 == "temperature" assert object_id1 == "temperature"
# Clear for next entity
added_expressions.clear()
# Set up second entity with different name # Set up second entity with different name
config2 = { config2 = {
CONF_NAME: "Humidity", CONF_NAME: "Humidity",
@@ -327,7 +332,7 @@ async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) ->
await _setup_entity_impl(var2, config2, "sensor") await _setup_entity_impl(var2, config2, "sensor")
# Get object ID from second entity # Get object ID from second entity
object_id2 = extract_object_id_from_expressions(added_expressions) object_id2 = extract_object_id_from_config(config2)
assert object_id2 == "humidity" assert object_id2 == "humidity"
@@ -337,8 +342,6 @@ async def test_setup_entity_different_platforms(
) -> None: ) -> None:
"""Test that same name on different platforms doesn't conflict.""" """Test that same name on different platforms doesn't conflict."""
added_expressions = setup_test_environment
# Create mock entities # Create mock entities
sensor = MockObj("sensor1") sensor = MockObj("sensor1")
binary_sensor = MockObj("binary_sensor1") binary_sensor = MockObj("binary_sensor1")
@@ -356,15 +359,11 @@ async def test_setup_entity_different_platforms(
(text_sensor, "text_sensor"), (text_sensor, "text_sensor"),
] ]
object_ids: list[str] = []
for var, platform in platforms: for var, platform in platforms:
added_expressions.clear()
await _setup_entity_impl(var, config, platform) await _setup_entity_impl(var, config, platform)
object_id = extract_object_id_from_expressions(added_expressions)
object_ids.append(object_id)
# All should get base object ID without suffix # All should get the same object ID (name stored in config, not platform-specific)
assert all(obj_id == "status" for obj_id in object_ids) assert extract_object_id_from_config(config) == "status"
@pytest.fixture @pytest.fixture
@@ -389,7 +388,6 @@ async def test_setup_entity_with_devices(
setup_test_environment: list[str], mock_get_variable: dict[ID, MockObj] setup_test_environment: list[str], mock_get_variable: dict[ID, MockObj]
) -> None: ) -> None:
"""Test that same name on different devices doesn't conflict.""" """Test that same name on different devices doesn't conflict."""
added_expressions = setup_test_environment
# Create mock devices # Create mock devices
device1_id = ID("device1", type="Device") device1_id = ID("device1", type="Device")
@@ -418,24 +416,18 @@ async def test_setup_entity_with_devices(
} }
# Get object IDs # Get object IDs
object_ids: list[str] = []
for var, config in [(sensor1, config1), (sensor2, config2)]: for var, config in [(sensor1, config1), (sensor2, config2)]:
added_expressions.clear()
await _setup_entity_impl(var, config, "sensor") await _setup_entity_impl(var, config, "sensor")
object_id = extract_object_id_from_expressions(added_expressions)
object_ids.append(object_id)
# Both should get base object ID without suffix (different devices) # Both should get base object ID without suffix (different devices)
assert object_ids[0] == "temperature" assert extract_object_id_from_config(config1) == "temperature"
assert object_ids[1] == "temperature" assert extract_object_id_from_config(config2) == "temperature"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_setup_entity_empty_name(setup_test_environment: list[str]) -> None: async def test_setup_entity_empty_name(setup_test_environment: list[str]) -> None:
"""Test setup_entity with empty entity name.""" """Test setup_entity with empty entity name."""
added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
@@ -445,7 +437,7 @@ async def test_setup_entity_empty_name(setup_test_environment: list[str]) -> Non
await _setup_entity_impl(var, config, "sensor") await _setup_entity_impl(var, config, "sensor")
object_id = extract_object_id_from_expressions(added_expressions) object_id = extract_object_id_from_config(config)
# Should use friendly name # Should use friendly name
assert object_id == "test_device" assert object_id == "test_device"
@@ -456,8 +448,6 @@ async def test_setup_entity_special_characters(
) -> None: ) -> None:
"""Test setup_entity with names containing special characters.""" """Test setup_entity with names containing special characters."""
added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
@@ -466,7 +456,7 @@ async def test_setup_entity_special_characters(
} }
await _setup_entity_impl(var, config, "sensor") await _setup_entity_impl(var, config, "sensor")
object_id = extract_object_id_from_expressions(added_expressions) object_id = extract_object_id_from_config(config)
# Special characters should be sanitized # Special characters should be sanitized
assert object_id == "temperature_sensor_" assert object_id == "temperature_sensor_"
@@ -476,8 +466,6 @@ async def test_setup_entity_special_characters(
async def test_setup_entity_with_icon(setup_test_environment: list[str]) -> None: async def test_setup_entity_with_icon(setup_test_environment: list[str]) -> None:
"""Test setup_entity sets icon correctly.""" """Test setup_entity sets icon correctly."""
setup_test_environment # noqa: F841 - fixture initializes CORE state
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
@@ -800,10 +788,9 @@ async def test_setup_entity_empty_name_with_device(
# Check that set_device was called # Check that set_device was called
assert any("sensor1.set_device" in expr for expr in added_expressions) assert any("sensor1.set_device" in expr for expr in added_expressions)
# For empty-name entities, Python passes 0 - C++ calculates hash at runtime # For empty-name entities, Python stores hash 0 - C++ calculates hash at runtime
assert any('set_name("", 0)' in expr for expr in added_expressions), ( assert config.get("_entity_name") == ""
f"Expected set_name with hash 0, got {added_expressions}" assert config.get("_entity_object_id_hash") == 0
)
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -815,7 +802,6 @@ async def test_setup_entity_empty_name_with_mac_suffix(
For empty-name entities, Python passes 0 and C++ calculates the hash For empty-name entities, Python passes 0 and C++ calculates the hash
at runtime from friendly_name (bug-for-bug compatibility). at runtime from friendly_name (bug-for-bug compatibility).
""" """
added_expressions = setup_test_environment
# Set up CORE.config with name_add_mac_suffix enabled # Set up CORE.config with name_add_mac_suffix enabled
CORE.config = {"name_add_mac_suffix": True} CORE.config = {"name_add_mac_suffix": True}
@@ -831,10 +817,9 @@ async def test_setup_entity_empty_name_with_mac_suffix(
await _setup_entity_impl(var, config, "sensor") await _setup_entity_impl(var, config, "sensor")
# For empty-name entities, Python passes 0 - C++ calculates hash at runtime # For empty-name entities, Python stores hash 0 - C++ calculates hash at runtime
assert any('set_name("", 0)' in expr for expr in added_expressions), ( assert config.get("_entity_name") == ""
f"Expected set_name with hash 0, got {added_expressions}" assert config.get("_entity_object_id_hash") == 0
)
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -847,7 +832,6 @@ async def test_setup_entity_empty_name_with_mac_suffix_no_friendly_name(
at runtime. In this case C++ will hash the empty friendly_name at runtime. In this case C++ will hash the empty friendly_name
(bug-for-bug compatibility). (bug-for-bug compatibility).
""" """
added_expressions = setup_test_environment
# Set up CORE.config with name_add_mac_suffix enabled # Set up CORE.config with name_add_mac_suffix enabled
CORE.config = {"name_add_mac_suffix": True} CORE.config = {"name_add_mac_suffix": True}
@@ -863,10 +847,9 @@ async def test_setup_entity_empty_name_with_mac_suffix_no_friendly_name(
await _setup_entity_impl(var, config, "sensor") await _setup_entity_impl(var, config, "sensor")
# For empty-name entities, Python passes 0 - C++ calculates hash at runtime # For empty-name entities, Python stores hash 0 - C++ calculates hash at runtime
assert any('set_name("", 0)' in expr for expr in added_expressions), ( assert config.get("_entity_name") == ""
f"Expected set_name with hash 0, got {added_expressions}" assert config.get("_entity_object_id_hash") == 0
)
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -878,7 +861,6 @@ async def test_setup_entity_empty_name_no_mac_suffix_no_friendly_name(
For empty-name entities, Python passes 0 and C++ calculates the hash For empty-name entities, Python passes 0 and C++ calculates the hash
at runtime from the device name. at runtime from the device name.
""" """
added_expressions = setup_test_environment
# No MAC suffix (either not set or False) # No MAC suffix (either not set or False)
CORE.config = {} CORE.config = {}
@@ -896,10 +878,9 @@ async def test_setup_entity_empty_name_no_mac_suffix_no_friendly_name(
await _setup_entity_impl(var, config, "sensor") await _setup_entity_impl(var, config, "sensor")
# For empty-name entities, Python passes 0 - C++ calculates hash at runtime # For empty-name entities, Python stores hash 0 - C++ calculates hash at runtime
assert any('set_name("", 0)' in expr for expr in added_expressions), ( assert config.get("_entity_name") == ""
f"Expected set_name with hash 0, got {added_expressions}" assert config.get("_entity_object_id_hash") == 0
)
def test_register_string_overflow() -> None: def test_register_string_overflow() -> None:
@@ -976,7 +957,7 @@ async def test_setup_entity_direct_call(setup_test_environment: list[str]) -> No
# Direct call mode: await setup_entity(var, config, "camera") # Direct call mode: await setup_entity(var, config, "camera")
await setup_entity(var, config, "camera") await setup_entity(var, config, "camera")
# Should have called set_name # Should have emitted configure_entity_
object_id = extract_object_id_from_expressions(added_expressions) object_id = extract_object_id_from_expressions(added_expressions)
assert object_id == "my_camera" assert object_id == "my_camera"