mirror of
https://github.com/esphome/esphome.git
synced 2026-09-14 00:28:39 +00:00
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>
155 lines
5.3 KiB
Python
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"))
|