From 0576dd91b7d6d4d860c358c51af2e7ca1ee985d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Thu, 12 Feb 2026 11:59:19 +0000 Subject: [PATCH] Validate core_files.yaml base_platforms completeness (#162826) --- .core_files.yaml | 3 +- script/hassfest/__main__.py | 2 + script/hassfest/core_files.py | 50 ++++++++++++++++ tests/hassfest/test_core_files.py | 98 +++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 script/hassfest/core_files.py create mode 100644 tests/hassfest/test_core_files.py diff --git a/.core_files.yaml b/.core_files.yaml index 2a6db0a2943..ab763b77086 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -22,6 +22,7 @@ base_platforms: &base_platforms - homeassistant/components/calendar/** - homeassistant/components/camera/** - homeassistant/components/climate/** + - homeassistant/components/conversation/** - homeassistant/components/cover/** - homeassistant/components/date/** - homeassistant/components/datetime/** @@ -53,6 +54,7 @@ base_platforms: &base_platforms - homeassistant/components/update/** - homeassistant/components/vacuum/** - homeassistant/components/valve/** + - homeassistant/components/wake_word/** - homeassistant/components/water_heater/** - homeassistant/components/weather/** @@ -70,7 +72,6 @@ components: &components - homeassistant/components/cloud/** - homeassistant/components/config/** - homeassistant/components/configurator/** - - homeassistant/components/conversation/** - homeassistant/components/demo/** - homeassistant/components/device_automation/** - homeassistant/components/dhcp/** diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index 0e7c6c83d9b..3e979b1d8ce 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -15,6 +15,7 @@ from . import ( conditions, config_flow, config_schema, + core_files, dependencies, dhcp, docker, @@ -62,6 +63,7 @@ INTEGRATION_PLUGINS = [ config_flow, # This needs to run last, after translations are processed ] HASS_PLUGINS = [ + core_files, docker, mypy_config, metadata, diff --git a/script/hassfest/core_files.py b/script/hassfest/core_files.py new file mode 100644 index 00000000000..ac480de11a3 --- /dev/null +++ b/script/hassfest/core_files.py @@ -0,0 +1,50 @@ +"""Validate .core_files.yaml base_platforms alignment with entity platforms.""" + +import re + +from homeassistant.util.yaml import load_yaml_dict + +from .model import Config, Integration + +# Non-entity-platform components that belong in base_platforms +EXTRA_BASE_PLATFORMS = {"diagnostics"} + +_COMPONENT_RE = re.compile(r"homeassistant/components/([^/]+)/\*\*") + + +def validate(integrations: dict[str, Integration], config: Config) -> None: + """Validate that base_platforms in .core_files.yaml matches entity platforms.""" + if config.specific_integrations: + return + + core_files_path = config.root / ".core_files.yaml" + core_files = load_yaml_dict(str(core_files_path)) + + base_platform_entries = { + match.group(1) + for entry in core_files["base_platforms"] + if (match := _COMPONENT_RE.match(entry)) + } + + entity_platforms = { + integration.domain + for integration in integrations.values() + if integration.manifest.get("integration_type") == "entity" + and integration.domain != "tag" + } + + expected = entity_platforms | EXTRA_BASE_PLATFORMS + + for domain in sorted(expected - base_platform_entries): + config.add_error( + "core_files", + f"Entity platform '{domain}' is missing from " + "base_platforms in .core_files.yaml", + ) + + for domain in sorted(base_platform_entries - expected): + config.add_error( + "core_files", + f"'{domain}' in base_platforms in .core_files.yaml " + "is not an entity platform or in EXTRA_BASE_PLATFORMS", + ) diff --git a/tests/hassfest/test_core_files.py b/tests/hassfest/test_core_files.py new file mode 100644 index 00000000000..9594ab0c3fc --- /dev/null +++ b/tests/hassfest/test_core_files.py @@ -0,0 +1,98 @@ +"""Tests for hassfest core_files validation.""" + +from pathlib import Path +from unittest.mock import patch + +from script.hassfest.core_files import EXTRA_BASE_PLATFORMS, validate +from script.hassfest.model import Config, Integration + + +def _create_integration( + config: Config, domain: str, integration_type: str = "hub" +) -> Integration: + """Create a minimal Integration with the given type.""" + integration = Integration(config.core_integrations_path / domain, _config=config) + + integration._manifest = { + "domain": domain, + "name": domain, + "integration_type": integration_type, + } + return integration + + +def _create_core_files_yaml(base_platforms: list[str]) -> dict: + """Build a minimal .core_files.yaml dict.""" + return { + "base_platforms": [f"homeassistant/components/{p}/**" for p in base_platforms], + } + + +def test_skip_specific_integrations() -> None: + """Test that validation is skipped for specific integrations.""" + config = Config( + root=Path(".").absolute(), + specific_integrations=[Path("some/path")], + action="validate", + requirements=False, + ) + # Should not raise or add errors — it just returns early + validate({}, config) + assert not config.errors + + +def test_valid_alignment(config: Config) -> None: + """Test no errors when base_platforms matches entity platforms.""" + integrations = { + "sensor": _create_integration(config, "sensor", "entity"), + "light": _create_integration(config, "light", "entity"), + "tag": _create_integration(config, "tag", "entity"), # excluded + "mqtt": _create_integration(config, "mqtt", "hub"), + } + + core_files = _create_core_files_yaml(["sensor", "light", *EXTRA_BASE_PLATFORMS]) + + with patch("script.hassfest.core_files.load_yaml_dict", return_value=core_files): + validate(integrations, config) + + assert not config.errors + + +def test_missing_entity_platform(config: Config) -> None: + """Test error when an entity platform is missing from base_platforms.""" + integrations = { + "sensor": _create_integration(config, "sensor", "entity"), + "light": _create_integration(config, "light", "entity"), + } + + # light is missing from base_platforms + core_files = _create_core_files_yaml(["sensor", *EXTRA_BASE_PLATFORMS]) + + with patch("script.hassfest.core_files.load_yaml_dict", return_value=core_files): + validate(integrations, config) + + assert len(config.errors) == 1 + assert ( + config.errors[0].error + == "Entity platform 'light' is missing from base_platforms in .core_files.yaml" + ) + + +def test_unexpected_entry(config: Config) -> None: + """Test error when base_platforms contains a non-entity-platform entry.""" + integrations = { + "sensor": _create_integration(config, "sensor", "entity"), + } + + core_files = _create_core_files_yaml( + ["sensor", "unknown_thing", *EXTRA_BASE_PLATFORMS] + ) + + with patch("script.hassfest.core_files.load_yaml_dict", return_value=core_files): + validate(integrations, config) + + assert len(config.errors) == 1 + assert ( + config.errors[0].error + == "'unknown_thing' in base_platforms in .core_files.yaml is not an entity platform or in EXTRA_BASE_PLATFORMS" + )