Files
esphome/tests/component_tests/cdc_acm_uart/test_init.py
T
ebb9037ea1 [bridge] New component and cdc_acm_uart platform (#11689)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-09-11 22:08:23 -05:00

155 lines
5.3 KiB
Python

"""Tests for the bridge cdc_acm_uart platform's final validation."""
import pytest
from esphome import config_validation as cv
from esphome.components.cdc_acm_uart import bridge
from esphome.components.cdc_acm_uart.bridge import CONF_USB_CDC_ACM_ID
from esphome.config import Config
from esphome.const import CONF_DEBUG, CONF_ID, CONF_UART_ID, PlatformFramework
from esphome.core import ID
from esphome.types import ConfigType
from tests.component_tests.types import SetCoreConfigCallable
_final_validate = bridge._final_validate
def _set_esp32_s3(set_core_config: SetCoreConfigCallable, **kwargs) -> None:
from esphome.components.esp32 import KEY_VARIANT, VARIANT_ESP32S3
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_VARIANT: VARIANT_ESP32S3},
**kwargs,
)
def _full_config(uarts: list[ConfigType] | None = None, **domains) -> Config:
"""A full config declaring uart_0 and uart_1 (plus any extra entries), as the ID
pass leaves it, so the debug check can resolve a uart_id to its declaration."""
uarts = uarts or [{CONF_ID: ID("uart_0")}, {CONF_ID: ID("uart_1")}]
full = Config()
full["uart"] = uarts
for index, uart_conf in enumerate(uarts):
full.declare_ids.append((uart_conf[CONF_ID], ["uart", index, CONF_ID]))
full.update(domains)
return full
def _bridge_config(uart_id: str, cdc_id: str) -> dict:
return {CONF_UART_ID: ID(uart_id), CONF_USB_CDC_ACM_ID: ID(cdc_id)}
def test_accepts_distinct_uart_and_cdc_interfaces(
set_core_config: SetCoreConfigCallable,
) -> None:
_set_esp32_s3(set_core_config, full_config=_full_config())
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
_final_validate(_bridge_config("uart_1", "cdc_acm_2"))
def test_rejects_two_bridges_sharing_a_uart(
set_core_config: SetCoreConfigCallable,
) -> None:
_set_esp32_s3(set_core_config, full_config=_full_config())
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
with pytest.raises(cv.Invalid, match="already bridged"):
_final_validate(_bridge_config("uart_0", "cdc_acm_2"))
def test_rejects_two_bridges_sharing_a_cdc_interface(
set_core_config: SetCoreConfigCallable,
) -> None:
_set_esp32_s3(set_core_config, full_config=_full_config())
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
with pytest.raises(cv.Invalid, match="already bridged"):
_final_validate(_bridge_config("uart_1", "cdc_acm_1"))
def test_rejects_uart_shared_with_another_component(
set_core_config: SetCoreConfigCallable,
) -> None:
_set_esp32_s3(
set_core_config,
full_config=_full_config(
sensor=[{"platform": "pzemac", CONF_UART_ID: ID("uart_0")}],
),
)
with pytest.raises(cv.Invalid, match="exclusive"):
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
def test_rejects_cdc_interface_shared_with_another_component(
set_core_config: SetCoreConfigCallable,
) -> None:
# The CDC instance is itself a uart::UARTComponent, so other components can bind
# it as a plain UART via uart_id -- that must be rejected just like UART sharing.
_set_esp32_s3(
set_core_config,
full_config=_full_config(
sensor=[{"platform": "pzemac", CONF_UART_ID: ID("cdc_acm_1")}],
),
)
with pytest.raises(cv.Invalid, match="exclusive"):
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
def test_rejects_uart_referenced_from_nested_config(
set_core_config: SetCoreConfigCallable,
) -> None:
# References can sit arbitrarily deep, e.g. inside an automation's action list.
_set_esp32_s3(
set_core_config,
full_config=_full_config(
binary_sensor=[
{
"platform": "gpio",
"on_press": [{"then": [{CONF_UART_ID: ID("uart_0")}]}],
}
],
),
)
with pytest.raises(cv.Invalid, match="exclusive"):
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
def test_ignores_other_components_on_other_uarts(
set_core_config: SetCoreConfigCallable,
) -> None:
_set_esp32_s3(
set_core_config,
full_config=_full_config(
sensor=[{"platform": "pzemac", CONF_UART_ID: ID("uart_1")}],
# The bridge domain itself is skipped: this bridge's own entry (and any
# bridge-vs-bridge sharing, which the seen-set already rejects) must not
# trip the exclusivity scan.
bridge=[_bridge_config("uart_0", "cdc_acm_1")],
),
)
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
def test_rejects_debug_on_bridged_uart(
set_core_config: SetCoreConfigCallable,
) -> None:
# The bridge talks to the IDF driver directly, so the uart debugger would see
# nothing and its dummy_receiver would steal RX bytes.
_set_esp32_s3(
set_core_config,
full_config=_full_config(uarts=[{CONF_ID: ID("uart_0"), CONF_DEBUG: {}}]),
)
with pytest.raises(cv.Invalid, match="debug"):
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))
def test_allows_debug_on_other_uart(
set_core_config: SetCoreConfigCallable,
) -> None:
_set_esp32_s3(
set_core_config,
full_config=_full_config(
uarts=[{CONF_ID: ID("uart_0")}, {CONF_ID: ID("uart_1"), CONF_DEBUG: {}}]
),
)
_final_validate(_bridge_config("uart_0", "cdc_acm_1"))