diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 05f545c861..d869026a85 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -50,22 +50,22 @@ __attribute__((weak)) const char *entity_device_class_lookup(uint16_t) { return __attribute__((weak)) const char *entity_uom_lookup(uint16_t) { return ""; } __attribute__((weak)) const char *entity_icon_lookup(uint16_t) { return ""; } +static constexpr auto EMPTY_REF = StringRef::from_lit(""); + // Entity device class (from packed index) StringRef EntityBase::get_device_class_ref() const { - static constexpr auto EMPTY = StringRef::from_lit(""); uint16_t idx = (this->entity_string_packed_ >> ENTITY_STR_DC_SHIFT) & ENTITY_STR_DC_MASK; if (idx == 0) - return EMPTY; + return EMPTY_REF; return StringRef(entity_device_class_lookup(idx)); } std::string EntityBase::get_device_class() const { return std::string(this->get_device_class_ref().c_str()); } // Entity unit of measurement (from packed index) StringRef EntityBase::get_unit_of_measurement_ref() const { - static constexpr auto EMPTY = StringRef::from_lit(""); uint16_t idx = (this->entity_string_packed_ >> ENTITY_STR_UOM_SHIFT) & ENTITY_STR_UOM_MASK; if (idx == 0) - return EMPTY; + return EMPTY_REF; return StringRef(entity_uom_lookup(idx)); } std::string EntityBase::get_unit_of_measurement() const { @@ -74,14 +74,13 @@ std::string EntityBase::get_unit_of_measurement() const { // Entity icon (from packed index) StringRef EntityBase::get_icon_ref() const { - static constexpr auto EMPTY = StringRef::from_lit(""); #ifdef USE_ENTITY_ICON uint16_t idx = (this->entity_string_packed_ >> ENTITY_STR_ICON_SHIFT) & ENTITY_STR_ICON_MASK; if (idx == 0) - return EMPTY; + return EMPTY_REF; return StringRef(entity_icon_lookup(idx)); #else - return EMPTY; + return EMPTY_REF; #endif } std::string EntityBase::get_icon() const { return std::string(this->get_icon_ref().c_str()); } diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 367da3d9ec..55d14d5d19 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -60,7 +60,7 @@ class EntityStringPool: device_classes: dict[str, int] = field(default_factory=dict) units: dict[str, int] = field(default_factory=dict) icons: dict[str, int] = field(default_factory=dict) - _tables_registered: bool = False + tables_registered: bool = False def _get_pool() -> EntityStringPool: @@ -73,9 +73,9 @@ def _get_pool() -> EntityStringPool: def _ensure_tables_registered() -> None: """Schedule the table generation job (once).""" pool = _get_pool() - if pool._tables_registered: + if pool.tables_registered: return - pool._tables_registered = True + pool.tables_registered = True CORE.add_job(_generate_tables_job) diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index b937b2fcee..5dc017a5ed 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -11,6 +11,7 @@ from esphome.config_validation import Invalid from esphome.const import ( CONF_DEVICE_ID, CONF_DISABLED_BY_DEFAULT, + CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -18,6 +19,7 @@ from esphome.const import ( ) from esphome.core import CORE, ID, entity_helpers from esphome.core.entity_helpers import ( + _register_string, _setup_entity_impl, entity_duplicate_validator, get_base_entity_object_id, @@ -895,3 +897,30 @@ async def test_setup_entity_empty_name_no_mac_suffix_no_friendly_name( assert any('set_name("", 0)' in expr for expr in added_expressions), ( f"Expected set_name with hash 0, got {added_expressions}" ) + + +def test_register_string_overflow() -> None: + """Test _register_string raises ValueError when max count is exceeded.""" + category: dict[str, int] = {} + for i in range(3): + _register_string(f"val_{i}", category, 3, "test") + with pytest.raises(ValueError, match="Too many unique test values"): + _register_string("overflow", category, 3, "test") + + +@pytest.mark.asyncio +async def test_setup_entity_with_entity_category( + setup_test_environment: list[str], +) -> None: + """Test setup_entity sets entity_category correctly.""" + added_expressions = setup_test_environment + var = MockObj("sensor1") + config = { + CONF_NAME: "Temperature", + CONF_DISABLED_BY_DEFAULT: False, + CONF_ENTITY_CATEGORY: "diagnostic", + } + await _setup_entity_impl(var, config, "sensor") + assert any( + 'set_entity_category("diagnostic")' in expr for expr in added_expressions + )