mirror of
https://github.com/esphome/esphome.git
synced 2026-05-21 02:01:57 +08:00
[core] Fix area/device hash collision validation not running (#15259)
CI / Run script/clang-tidy for ZEPHYR (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Blocked by required conditions
CI / Test components batch (${{ matrix.components }}) (push) Blocked by required conditions
CI / pre-commit.ci lite (push) Blocked by required conditions
CI / Build target branch for memory impact (push) Blocked by required conditions
CI / Build PR branch for memory impact (push) Blocked by required conditions
CI / Comment memory impact (push) Blocked by required conditions
CI / CI Status (push) Blocked by required conditions
Synchronise Device Classes from Home Assistant / Sync Device Classes (push) Has been cancelled
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
Stale / stale (push) Has been cancelled
Lock closed issues and PRs / lock (push) Has been cancelled
Publish Release / Initialize build (push) Has been cancelled
Publish Release / Build and publish to PyPi (push) Has been cancelled
Publish Release / Build ESPHome amd64 (push) Has been cancelled
Publish Release / Build ESPHome arm64 (push) Has been cancelled
Publish Release / Publish ESPHome docker to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome docker to ghcr (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to ghcr (push) Has been cancelled
Publish Release / deploy-ha-addon-repo (push) Has been cancelled
Publish Release / deploy-esphome-schema (push) Has been cancelled
Publish Release / version-notifier (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Blocked by required conditions
CI / Test components batch (${{ matrix.components }}) (push) Blocked by required conditions
CI / pre-commit.ci lite (push) Blocked by required conditions
CI / Build target branch for memory impact (push) Blocked by required conditions
CI / Build PR branch for memory impact (push) Blocked by required conditions
CI / Comment memory impact (push) Blocked by required conditions
CI / CI Status (push) Blocked by required conditions
Synchronise Device Classes from Home Assistant / Sync Device Classes (push) Has been cancelled
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
Stale / stale (push) Has been cancelled
Lock closed issues and PRs / lock (push) Has been cancelled
Publish Release / Initialize build (push) Has been cancelled
Publish Release / Build and publish to PyPi (push) Has been cancelled
Publish Release / Build ESPHome amd64 (push) Has been cancelled
Publish Release / Build ESPHome arm64 (push) Has been cancelled
Publish Release / Publish ESPHome docker to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome docker to ghcr (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to ghcr (push) Has been cancelled
Publish Release / deploy-ha-addon-repo (push) Has been cancelled
Publish Release / deploy-esphome-schema (push) Has been cancelled
Publish Release / version-notifier (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
This commit is contained in:
@@ -958,6 +958,23 @@ class FinalValidateValidationStep(ConfigValidationStep):
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
class CoreFinalValidateStep(ConfigValidationStep):
|
||||
"""Run final validation on core esphome config (area/device hash collisions)."""
|
||||
|
||||
# Same priority as component final validate steps
|
||||
priority = -20.0
|
||||
|
||||
def run(self, result: Config) -> None:
|
||||
if result.errors:
|
||||
return
|
||||
|
||||
token = fv.full_config.set(result)
|
||||
with result.catch_error([CONF_ESPHOME]):
|
||||
if CONF_ESPHOME in result:
|
||||
core_config.validate_ids_and_references(result[CONF_ESPHOME])
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
class PinUseValidationCheck(ConfigValidationStep):
|
||||
"""Check for pin reuse"""
|
||||
|
||||
@@ -1085,6 +1102,7 @@ def validate_config(
|
||||
for domain, conf in config.items():
|
||||
result.add_validation_step(LoadValidationStep(domain, conf))
|
||||
result.add_validation_step(IDPassValidationStep())
|
||||
result.add_validation_step(CoreFinalValidateStep())
|
||||
result.add_validation_step(PinUseValidationCheck())
|
||||
|
||||
result.add_validation_step(RemoveReferenceValidationStep())
|
||||
|
||||
@@ -156,22 +156,22 @@ def validate_ids_and_references(config: ConfigType) -> ConfigType:
|
||||
hash_dict[hash_val] = id_obj.id
|
||||
|
||||
# Collect all areas
|
||||
all_areas: list[dict[str, str | core.ID]] = []
|
||||
all_areas: list[tuple[dict[str, str | core.ID], str]] = []
|
||||
if CONF_AREA in config:
|
||||
all_areas.append(config[CONF_AREA])
|
||||
all_areas.extend(config[CONF_AREAS])
|
||||
all_areas.append((config[CONF_AREA], CONF_AREA))
|
||||
all_areas.extend((area, CONF_AREAS) for area in config.get(CONF_AREAS, []))
|
||||
|
||||
# Validate area hash collisions and collect IDs
|
||||
area_hashes: dict[int, str] = {}
|
||||
area_ids: set[str] = set()
|
||||
for area in all_areas:
|
||||
for area, key in all_areas:
|
||||
area_id: core.ID = area[CONF_ID]
|
||||
check_hash_collision(area_id, area_hashes, "Area", [CONF_AREAS, area_id.id])
|
||||
check_hash_collision(area_id, area_hashes, "Area", [key, area_id.id])
|
||||
area_ids.add(area_id.id)
|
||||
|
||||
# Validate device hash collisions and area references
|
||||
device_hashes: dict[int, str] = {}
|
||||
for device in config[CONF_DEVICES]:
|
||||
for device in config.get(CONF_DEVICES, []):
|
||||
device_id: core.ID = device[CONF_ID]
|
||||
check_hash_collision(
|
||||
device_id, device_hashes, "Device", [CONF_DEVICES, device_id.id]
|
||||
@@ -329,9 +329,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references)
|
||||
|
||||
|
||||
PRELOAD_CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
|
||||
@@ -1006,6 +1006,18 @@ def lint_log_in_header(fname, line, col, content):
|
||||
)
|
||||
|
||||
|
||||
@lint_content_find_check(
|
||||
"FINAL_VALIDATE_SCHEMA",
|
||||
include=["esphome/core/*.py"],
|
||||
exclude=["esphome/core/entity_helpers.py"],
|
||||
)
|
||||
def lint_final_validate_in_core(fname, line, col, content):
|
||||
return (
|
||||
"FINAL_VALIDATE_SCHEMA in esphome/core/ is not picked up by the component loader. "
|
||||
"Use CoreFinalValidateStep in esphome/config.py instead."
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
colorama.init()
|
||||
|
||||
|
||||
@@ -248,6 +248,24 @@ def test_area_id_hash_collision(
|
||||
)
|
||||
|
||||
|
||||
def test_area_singular_hash_collision(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that area hash collisions between singular area: and areas: list are detected."""
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "area_singular_hash_collision.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is None
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert (
|
||||
"Area ID 'd6ka' with hash 3082558663 collides with existing area ID 'test_2258'"
|
||||
in captured.out
|
||||
)
|
||||
# Error path should point to 'areas' (where the colliding entry is), not 'area'
|
||||
assert "areas" in captured.out
|
||||
|
||||
|
||||
def test_device_duplicate_id(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test
|
||||
area:
|
||||
id: test_2258
|
||||
name: "Area 1"
|
||||
areas:
|
||||
- id: d6ka
|
||||
name: "Area 2"
|
||||
|
||||
host:
|
||||
Reference in New Issue
Block a user