[api] Cover the PlatformIO toolchain in the managed component tests

The ESP-IDF framework can also be built with the PlatformIO toolchain, and the
managed components are used on both. Record why that choice is deliberately
toolchain-independent: wireguard splits on the same condition, and if the two
ever disagree one of them converts a second libsodium next to the managed one.

The test now sets the toolchain explicitly and asserts the PlatformIO one takes
the managed path too, so the condition cannot narrow without a test failing.
This commit is contained in:
Jesse Hills
2026-08-18 07:24:25 +12:00
parent 6d14778123
commit eb53ed5558
2 changed files with 58 additions and 18 deletions
+10 -5
View File
@@ -502,12 +502,17 @@ async def to_code(config: ConfigType) -> None:
# and plaintext disabled. Only a factory reset can remove it. # and plaintext disabled. Only a factory reset can remove it.
cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_PLAINTEXT")
cg.add_define("USE_API_NOISE") cg.add_define("USE_API_NOISE")
# Both libraries build themselves as ESP-IDF components, so on ESP-IDF # Both libraries build themselves as ESP-IDF components, so on ESP32
# they are pulled straight from the component registry instead of going # they are pulled straight from the component registry instead of going
# through ESPHome's PlatformIO-library converter. Not on the Arduino # through ESPHome's PlatformIO-library converter. Deliberately not
# framework: arduino-esp32 brings its own espressif/libsodium (on IDF # conditional on the toolchain: wireguard splits on the same condition,
# < 6.0), and IDF refuses to build with two managed components whose # and if the two disagree one of them converts a second libsodium next
# names differ only by namespace. # to the managed one.
#
# Not on the Arduino framework though: arduino-esp32 depends on
# espressif/libsodium of its own (on IDF < 6.0), so the component
# manager would see two managed components whose names match once the
# namespace is stripped, and refuse to pick between them.
if CORE.is_esp32 and not CORE.using_arduino: if CORE.is_esp32 and not CORE.using_arduino:
from esphome.components.esp32 import add_idf_component from esphome.components.esp32 import add_idf_component
@@ -1,12 +1,13 @@
"""Tests for the noise-c/libsodium library wiring in api's to_code. """Tests for the noise-c/libsodium library wiring in api's to_code.
On ESP-IDF (not Arduino) both libraries build themselves as native ESP-IDF On ESP32 (but not the Arduino framework) both libraries build themselves as
managed components, so they are declared via add_idf_component() instead of native ESP-IDF managed components, so they are declared via add_idf_component()
going through ESPHome's PlatformIO-library converter. On every other target instead of going through ESPHome's PlatformIO-library converter, on either
(and on the Arduino framework, where arduino-esp32 brings its own bundled toolchain. Elsewhere noise-c still goes through that converter via
espressif/libsodium) noise-c still goes through the PlatformIO-library cg.add_library(): on the Arduino framework because arduino-esp32 depends on
converter via cg.add_library(). This drives the real to_code() coroutine so espressif/libsodium of its own, and off ESP32 because there are no IDF
both branches of that decision are exercised end to end, not just mocked. components at all. This drives the real to_code() coroutine so every branch of
that decision is exercised end to end, not just mocked.
""" """
from __future__ import annotations from __future__ import annotations
@@ -26,6 +27,7 @@ from esphome.const import (
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
Framework, Framework,
Platform, Platform,
Toolchain,
) )
from esphome.core import CORE, ID from esphome.core import CORE, ID
@@ -51,8 +53,9 @@ def fixture_encryption_key() -> str:
return base64.b64encode(b"0" * 32).decode() return base64.b64encode(b"0" * 32).decode()
def _setup_core(platform: Platform, framework: Framework) -> None: def _setup_core(platform: Platform, framework: Framework, toolchain: Toolchain) -> None:
CORE.reset() CORE.reset()
CORE.toolchain = toolchain
CORE.data[KEY_CORE] = { CORE.data[KEY_CORE] = {
KEY_TARGET_PLATFORM: str(platform), KEY_TARGET_PLATFORM: str(platform),
KEY_TARGET_FRAMEWORK: str(framework), KEY_TARGET_FRAMEWORK: str(framework),
@@ -65,9 +68,10 @@ def test_to_code_esp32_idf_encryption_uses_managed_idf_components(
encryption_key: str, encryption_key: str,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""On ESP32 + ESP-IDF, noise-c and libsodium are declared as managed IDF """On ESP32 + the ESP-IDF toolchain, noise-c and libsodium are declared as
components (add_idf_component), not converted PlatformIO libraries.""" managed IDF components (add_idf_component), not converted PlatformIO
_setup_core(Platform.ESP32, Framework.ESP_IDF) libraries."""
_setup_core(Platform.ESP32, Framework.ESP_IDF, Toolchain.ESP_IDF)
config = _build_config(encryption_key) config = _build_config(encryption_key)
CORE.component_ids.add("api_id") CORE.component_ids.add("api_id")
@@ -96,7 +100,7 @@ def test_to_code_esp32_arduino_encryption_uses_add_library(
"""On the Arduino framework, arduino-esp32 brings its own bundled """On the Arduino framework, arduino-esp32 brings its own bundled
espressif/libsodium, so noise-c must still go through the PlatformIO- espressif/libsodium, so noise-c must still go through the PlatformIO-
library converter (cg.add_library) instead of add_idf_component().""" library converter (cg.add_library) instead of add_idf_component()."""
_setup_core(Platform.ESP32, Framework.ARDUINO) _setup_core(Platform.ESP32, Framework.ARDUINO, Toolchain.ESP_IDF)
config = _build_config(encryption_key) config = _build_config(encryption_key)
CORE.component_ids.add("api_id") CORE.component_ids.add("api_id")
@@ -117,13 +121,44 @@ def test_to_code_esp32_arduino_encryption_uses_add_library(
add_idf_component_mock.assert_not_called() add_idf_component_mock.assert_not_called()
def test_to_code_esp32_idf_platformio_toolchain_also_uses_managed_components(
encryption_key: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The ESP-IDF framework can also be built with the PlatformIO toolchain,
and the managed components are used there too. The choice deliberately does
not depend on the toolchain: wireguard splits on the same condition, and if
the two ever disagree one of them converts a second libsodium next to the
managed one, which IDF refuses to build."""
_setup_core(Platform.ESP32, Framework.ESP_IDF, Toolchain.PLATFORMIO)
config = _build_config(encryption_key)
CORE.component_ids.add("api_id")
add_idf_component_calls: list[dict] = []
monkeypatch.setattr(
esp32,
"add_idf_component",
lambda **kwargs: add_idf_component_calls.append(kwargs),
)
add_library_mock = MagicMock()
monkeypatch.setattr(cg, "add_library", add_library_mock)
asyncio.run(api.to_code(config))
assert add_idf_component_calls == [
{"name": "esphome/noise-c", "ref": api.NOISE_C_VERSION},
{"name": "esphome/libsodium", "ref": api.LIBSODIUM_VERSION},
]
add_library_mock.assert_not_called()
def test_to_code_non_esp32_encryption_uses_add_library( def test_to_code_non_esp32_encryption_uses_add_library(
encryption_key: str, encryption_key: str,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
"""Off ESP32 entirely (e.g. host), noise-c always goes through the """Off ESP32 entirely (e.g. host), noise-c always goes through the
PlatformIO-library converter -- add_idf_component is ESP-IDF-only.""" PlatformIO-library converter -- add_idf_component is ESP-IDF-only."""
_setup_core(Platform.HOST, Framework.NATIVE) _setup_core(Platform.HOST, Framework.NATIVE, Toolchain.PLATFORMIO)
config = _build_config(encryption_key) config = _build_config(encryption_key)
CORE.component_ids.add("api_id") CORE.component_ids.add("api_id")