From c2ddc170638deb1ff2474d3e67ff434c8f4f7001 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Aug 2026 11:05:04 -0500 Subject: [PATCH] [libretiny] Give each platform its own CONFIG_SCHEMA instance (#18056) --- esphome/components/bk72xx/__init__.py | 6 ++- .../libretiny/generate_components.py | 6 ++- esphome/components/ln882x/__init__.py | 6 ++- esphome/components/rtl87xx/__init__.py | 6 ++- tests/unit_tests/components/test_libretiny.py | 38 ++++++++++++++++++- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/esphome/components/bk72xx/__init__.py b/esphome/components/bk72xx/__init__.py index 3ffab0f3a5..ee9bf1e0d4 100644 --- a/esphome/components/bk72xx/__init__.py +++ b/esphome/components/bk72xx/__init__.py @@ -51,7 +51,11 @@ def _set_core_data(config): return config -CONFIG_SCHEMA = libretiny.BASE_SCHEMA +# extend({}) makes this platform's own schema instance: BASE_SCHEMA is shared +# by every LibreTiny platform, and prepending this platform's _set_core_data +# onto the shared object would run it for every platform's validation once two +# platform modules are imported in one process (device-builder, tests). +CONFIG_SCHEMA = libretiny.BASE_SCHEMA.extend({}) PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA diff --git a/esphome/components/libretiny/generate_components.py b/esphome/components/libretiny/generate_components.py index 791a2659a9..4997878657 100644 --- a/esphome/components/libretiny/generate_components.py +++ b/esphome/components/libretiny/generate_components.py @@ -65,6 +65,10 @@ def _set_core_data(config): return config +# extend({}) makes this platform's own schema instance: BASE_SCHEMA is shared +# by every LibreTiny platform, and prepending this platform's _set_core_data +# onto the shared object would run it for every platform's validation once two +# platform modules are imported in one process (device-builder, tests). CONFIG_SCHEMA = {SCHEMA} PIN_SCHEMA = {PIN_SCHEMA} @@ -117,7 +121,7 @@ VAR_GPIO_PIN = "validate_pin" VAR_GPIO_USAGE = "validate_usage" # lines for code snippets -SCHEMA_BASE = "libretiny.BASE_SCHEMA" +SCHEMA_BASE = "libretiny.BASE_SCHEMA.extend({})" SCHEMA_EXTRA = f"libretiny.BASE_SCHEMA.extend({VAR_SCHEMA})" PIN_SCHEMA_BASE = "libretiny.gpio.BASE_PIN_SCHEMA" PIN_SCHEMA_EXTRA = f"libretiny.BASE_PIN_SCHEMA.extend({VAR_PIN_SCHEMA})" diff --git a/esphome/components/ln882x/__init__.py b/esphome/components/ln882x/__init__.py index 9c91827522..6da5a4969c 100644 --- a/esphome/components/ln882x/__init__.py +++ b/esphome/components/ln882x/__init__.py @@ -51,7 +51,11 @@ def _set_core_data(config): return config -CONFIG_SCHEMA = libretiny.BASE_SCHEMA +# extend({}) makes this platform's own schema instance: BASE_SCHEMA is shared +# by every LibreTiny platform, and prepending this platform's _set_core_data +# onto the shared object would run it for every platform's validation once two +# platform modules are imported in one process (device-builder, tests). +CONFIG_SCHEMA = libretiny.BASE_SCHEMA.extend({}) PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index a3b1dba4f2..a8eabae9a0 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -51,7 +51,11 @@ def _set_core_data(config): return config -CONFIG_SCHEMA = libretiny.BASE_SCHEMA +# extend({}) makes this platform's own schema instance: BASE_SCHEMA is shared +# by every LibreTiny platform, and prepending this platform's _set_core_data +# onto the shared object would run it for every platform's validation once two +# platform modules are imported in one process (device-builder, tests). +CONFIG_SCHEMA = libretiny.BASE_SCHEMA.extend({}) PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA diff --git a/tests/unit_tests/components/test_libretiny.py b/tests/unit_tests/components/test_libretiny.py index ee00bdc180..de54fcc6ca 100644 --- a/tests/unit_tests/components/test_libretiny.py +++ b/tests/unit_tests/components/test_libretiny.py @@ -2,7 +2,8 @@ import pytest -from esphome.components.libretiny import _detect_variant +from esphome.components import bk72xx, ln882x, rtl87xx +from esphome.components.libretiny import BASE_SCHEMA, _detect_variant from esphome.components.libretiny.const import ( FAMILY_LN882H, KEY_COMPONENT_DATA, @@ -11,7 +12,7 @@ from esphome.components.libretiny.const import ( from esphome.components.ln882x import COMPONENT_DATA import esphome.config_validation as cv from esphome.const import CONF_BOARD, CONF_FAMILY -from esphome.core import CORE +from esphome.core import CORE, KEY_CORE @pytest.fixture @@ -50,3 +51,36 @@ def test_detect_variant_unknown_board_still_raises(ln882x_core_data: None) -> No """Ids outside the rename map keep the family-override error.""" with pytest.raises(cv.Invalid, match="This board is unknown"): _detect_variant({CONF_BOARD: "not-a-real-board"}) + + +def test_platform_schemas_are_isolated_instances() -> None: + """Each LibreTiny platform must own its CONFIG_SCHEMA instance. + + BASE_SCHEMA is shared; every platform prepends its own _set_core_data + extra. On the shared object, importing two platform modules in one process + made either platform's validation run both extras, so the wrong platform's + component data won and known boards failed to resolve. + """ + platforms = (bk72xx, ln882x, rtl87xx) + schemas = [platform.CONFIG_SCHEMA for platform in platforms] + assert len({id(schema) for schema in (BASE_SCHEMA, *schemas)}) == 4 + # The shared base must not have accumulated any platform's extra. + # prepend_extra wraps validators in _Schema, so unwrap before comparing. + base_extras = [extra.schema for extra in BASE_SCHEMA._extra_schemas] + for platform in platforms: + assert platform._set_core_data not in base_extras + + +def test_each_platform_resolves_its_own_boards() -> None: + """Validating one platform's config must leave that platform's component + data in CORE.data. On the shared schema, the last-imported platform's + _set_core_data won for every platform, so known boards failed to resolve + with "This board is unknown".""" + CORE.data[KEY_CORE] = {} # written by the schema's _update_core_data extra + for platform, board in ( + (ln882x, "generic-ln882h"), + (bk72xx, "generic-bk7252"), + (rtl87xx, "generic-rtl8720cf-2mb-896k"), + ): + platform.CONFIG_SCHEMA({CONF_BOARD: board}) + assert CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] is platform.COMPONENT_DATA