diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index e029fa19bb..10d7f80834 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -1,16 +1,6 @@ """Tests for the binary sensor component.""" -import re - -_INTERNAL_BIT = 1 << 24 - - -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)) +from tests.component_tests.helpers import INTERNAL_BIT, extract_packed_value def test_binary_sensor_is_setup(generate_main): @@ -57,8 +47,8 @@ def test_binary_sensor_config_value_internal_set(generate_main): ) # Then: bs_1 has internal: true, bs_2 has internal: false - assert _extract_packed_value(main_cpp, "bs_1") & _INTERNAL_BIT != 0 - assert _extract_packed_value(main_cpp, "bs_2") & _INTERNAL_BIT == 0 + assert extract_packed_value(main_cpp, "bs_1") & INTERNAL_BIT != 0 + assert extract_packed_value(main_cpp, "bs_2") & INTERNAL_BIT == 0 def test_binary_sensor_config_value_use_raw_set(generate_main): diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py index 6973ec0f64..a35994a682 100644 --- a/tests/component_tests/button/test_button.py +++ b/tests/component_tests/button/test_button.py @@ -1,16 +1,6 @@ """Tests for the button component""" -import re - -_INTERNAL_BIT = 1 << 24 - - -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)) +from tests.component_tests.helpers import INTERNAL_BIT, extract_packed_value def test_button_is_setup(generate_main): @@ -52,5 +42,5 @@ def test_button_config_value_internal_set(generate_main): main_cpp = generate_main("tests/component_tests/button/test_button.yaml") # Then: wol_1 has internal: true, wol_2 has internal: false - assert _extract_packed_value(main_cpp, "wol_1") & _INTERNAL_BIT != 0 - assert _extract_packed_value(main_cpp, "wol_2") & _INTERNAL_BIT == 0 + assert extract_packed_value(main_cpp, "wol_1") & INTERNAL_BIT != 0 + assert extract_packed_value(main_cpp, "wol_2") & INTERNAL_BIT == 0 diff --git a/tests/component_tests/helpers.py b/tests/component_tests/helpers.py new file mode 100644 index 0000000000..e9fbb33173 --- /dev/null +++ b/tests/component_tests/helpers.py @@ -0,0 +1,15 @@ +"""Shared helpers for component tests.""" + +from __future__ import annotations + +import re + +INTERNAL_BIT = 1 << 24 + + +def extract_packed_value(main_cpp: str, var_name: str) -> int: + """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)) diff --git a/tests/component_tests/sensor/test_sensor.py b/tests/component_tests/sensor/test_sensor.py index d9ab3a022c..9d18fa36b8 100644 --- a/tests/component_tests/sensor/test_sensor.py +++ b/tests/component_tests/sensor/test_sensor.py @@ -1,14 +1,6 @@ """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)) +from tests.component_tests.helpers import extract_packed_value def test_sensor_device_class_set(generate_main): @@ -21,5 +13,5 @@ def test_sensor_device_class_set(generate_main): main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml") # Then: device_class: voltage means packed value must be non-zero - packed = _extract_packed_value(main_cpp, "s_1") + packed = extract_packed_value(main_cpp, "s_1") assert packed != 0 diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index 31c66d8784..c74dfb8a47 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -1,16 +1,6 @@ """Tests for the text component.""" -import re - -_INTERNAL_BIT = 1 << 24 - - -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)) +from tests.component_tests.helpers import INTERNAL_BIT, extract_packed_value def test_text_is_setup(generate_main): @@ -50,8 +40,8 @@ def test_text_config_value_internal_set(generate_main): main_cpp = generate_main("tests/component_tests/text/test_text.yaml") # Then: it_2 has internal: false, it_3 has internal: true - assert _extract_packed_value(main_cpp, "it_2") & _INTERNAL_BIT == 0 - assert _extract_packed_value(main_cpp, "it_3") & _INTERNAL_BIT != 0 + assert extract_packed_value(main_cpp, "it_2") & INTERNAL_BIT == 0 + assert extract_packed_value(main_cpp, "it_3") & INTERNAL_BIT != 0 def test_text_config_value_mode_set(generate_main): diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py index 2d77756b2c..1ff31ab96b 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.py +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -1,16 +1,6 @@ """Tests for the text sensor component.""" -import re - -_INTERNAL_BIT = 1 << 24 - - -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)) +from tests.component_tests.helpers import INTERNAL_BIT, extract_packed_value def test_text_sensor_is_setup(generate_main): @@ -52,8 +42,8 @@ def test_text_sensor_config_value_internal_set(generate_main): main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") # Then: ts_2 has internal: true, ts_3 has internal: false - assert _extract_packed_value(main_cpp, "ts_2") & _INTERNAL_BIT != 0 - assert _extract_packed_value(main_cpp, "ts_3") & _INTERNAL_BIT == 0 + assert extract_packed_value(main_cpp, "ts_2") & INTERNAL_BIT != 0 + assert extract_packed_value(main_cpp, "ts_3") & INTERNAL_BIT == 0 def test_text_sensor_device_class_set(generate_main): @@ -67,7 +57,7 @@ def test_text_sensor_device_class_set(generate_main): # Then: ts_2 has device_class: timestamp, ts_3 has device_class: date # so their packed values must be non-zero - packed_ts_2 = _extract_packed_value(main_cpp, "ts_2") + 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") + packed_ts_3 = extract_packed_value(main_cpp, "ts_3") assert packed_ts_3 != 0 diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index a68955fe08..d6cbb8c6be 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -21,12 +21,6 @@ from esphome.const import ( ) from esphome.core import CORE, ID, entity_helpers from esphome.core.entity_helpers import ( - _DC_SHIFT, - _DISABLED_BY_DEFAULT_SHIFT, - _ENTITY_CATEGORY_SHIFT, - _ICON_SHIFT, - _INTERNAL_SHIFT, - _UOM_SHIFT, _register_string, _setup_entity_impl, entity_duplicate_validator, @@ -946,7 +940,8 @@ async def test_setup_entity_with_entity_category( await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) - assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 + assert packed != 0 + assert "category:diagnostic" in added_expressions[0] @pytest.mark.asyncio @@ -998,6 +993,12 @@ async def test_setup_entity_decorator_mode(setup_test_environment: list[str]) -> # Tests for finalize_entity_strings packing +# +# These tests verify that flags and string indices produce non-zero packed values +# and correct inline comments. The actual bit layout correctness (Python _*_SHIFT +# matching C++ ENTITY_FIELD_*_SHIFT) is verified end-to-end by the integration +# test test_host_mode_entity_fields, which compiles firmware and checks values +# via the native API. def _extract_packed_value(expressions: list[str]) -> int: @@ -1029,7 +1030,7 @@ async def test_finalize_no_flags(setup_test_environment: list[str]) -> None: @pytest.mark.asyncio async def test_finalize_internal(setup_test_environment: list[str]) -> None: - """Test entity with internal=True packs the internal bit.""" + """Test entity with internal=True packs the internal flag.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { @@ -1040,15 +1041,15 @@ async def test_finalize_internal(setup_test_environment: list[str]) -> None: await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) - assert packed & (1 << _INTERNAL_SHIFT) != 0 - assert packed == (1 << _INTERNAL_SHIFT) + assert packed != 0 + assert "// internal" in added_expressions[0] @pytest.mark.asyncio async def test_finalize_disabled_by_default( setup_test_environment: list[str], ) -> None: - """Test entity with disabled_by_default=True packs the bit.""" + """Test entity with disabled_by_default=True packs the flag.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { @@ -1058,19 +1059,19 @@ async def test_finalize_disabled_by_default( await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) - assert packed & (1 << _DISABLED_BY_DEFAULT_SHIFT) != 0 - assert packed == (1 << _DISABLED_BY_DEFAULT_SHIFT) + assert packed != 0 + assert "// disabled_by_default" in added_expressions[0] @pytest.mark.asyncio async def test_finalize_entity_category( setup_test_environment: list[str], ) -> None: - """Test entity_category values (diagnostic=2, config=1) are packed.""" + """Test entity_category values are packed and described in comment.""" added_expressions = setup_test_environment var = MockObj("sensor1") - # Test diagnostic (value 2) + # Test diagnostic config = { CONF_NAME: "Test", CONF_DISABLED_BY_DEFAULT: False, @@ -1078,10 +1079,11 @@ async def test_finalize_entity_category( } await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) - packed = _extract_packed_value(added_expressions) - assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 + packed_diag = _extract_packed_value(added_expressions) + assert packed_diag != 0 + assert "category:diagnostic" in added_expressions[0] - # Test config (value 1) + # Test config — different packed value added_expressions.clear() config2 = { CONF_NAME: "Test2", @@ -1090,15 +1092,17 @@ async def test_finalize_entity_category( } await _setup_entity_impl(var, config2, "sensor") finalize_entity_strings(var, config2) - packed = _extract_packed_value(added_expressions) - assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 1 + packed_cfg = _extract_packed_value(added_expressions) + assert packed_cfg != 0 + assert packed_cfg != packed_diag + assert "category:config" in added_expressions[0] @pytest.mark.asyncio async def test_finalize_string_indices( setup_test_environment: list[str], ) -> None: - """Test device_class, unit_of_measurement, and icon are packed as indices.""" + """Test device_class, unit_of_measurement, and icon produce non-zero packed value.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { @@ -1113,14 +1117,11 @@ async def test_finalize_string_indices( setup_unit_of_measurement(config) finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) - # All three string indices should be non-zero - assert (packed >> _DC_SHIFT) & 0xFF != 0 - assert (packed >> _UOM_SHIFT) & 0xFF != 0 - assert (packed >> _ICON_SHIFT) & 0xFF != 0 - # No flags set - assert (packed >> _INTERNAL_SHIFT) & 1 == 0 - assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 0 - assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 0 + assert packed != 0 + comment = added_expressions[0] + assert "dc:temperature" in comment + assert "uom:°C" in comment + assert "icon:mdi:thermometer" in comment @pytest.mark.asyncio @@ -1144,14 +1145,7 @@ async def test_finalize_all_fields( setup_unit_of_measurement(config) finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) - # Verify flags - assert (packed >> _INTERNAL_SHIFT) & 1 == 1 - assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 1 - assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 - # Verify string indices are non-zero - assert (packed >> _DC_SHIFT) & 0xFF != 0 - assert (packed >> _UOM_SHIFT) & 0xFF != 0 - assert (packed >> _ICON_SHIFT) & 0xFF != 0 + assert packed != 0 # Verify comment contains all flags with actual string values comment_line = added_expressions[0] assert (