Files

185 lines
6.0 KiB
Python

"""Tests for usb_host device matching validation."""
import logging
import pytest
from esphome.components.usb_host import _final_validate, validate_usb_clients
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.types import ConfigType
@pytest.mark.parametrize(
("first", "second"),
[
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0x4001},
{"id": "b", "vid": 0x303A, "pid": 0x4002},
id="different_pid",
),
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0x4001},
{"id": "b", "vid": 0x1A86, "pid": 0x4001},
id="different_vid",
),
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
{"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZWA-2"},
id="different_product",
),
pytest.param(
{
"id": "a",
"vid": 0x303A,
"pid": 0x4001,
"manufacturer": "Nabu Casa",
"product": "ZBT-2",
},
{
"id": "b",
"vid": 0x303A,
"pid": 0x4001,
"manufacturer": "Espressif",
"product": "ZBT-2",
},
id="different_manufacturer",
),
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0, "product": "ZBT-2"},
{"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZWA-2"},
id="wildcard_pid_separated_by_product",
),
pytest.param(
{"id": "a", "vid": 0, "pid": 0x4001},
{"id": "b", "vid": 0x303A, "pid": 0x4002},
id="wildcard_vid_different_pid",
),
],
)
def test_disjoint_clients_are_accepted(first: ConfigType, second: ConfigType) -> None:
configs = [first, second]
assert validate_usb_clients(configs) is configs
@pytest.mark.parametrize(
("first", "second"),
[
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0x4001},
{"id": "b", "vid": 0x303A, "pid": 0x4001},
id="exact_duplicate",
),
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0x4001},
{"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
id="unfiltered_shadows_filtered",
),
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
{"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
id="identical_filters",
),
pytest.param(
{"id": "a", "vid": 0, "pid": 0},
{"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
id="zero_ids_match_every_device",
),
pytest.param(
{"id": "a", "vid": 0x303A, "pid": 0},
{"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
id="wildcard_pid_shadows_filtered",
),
pytest.param(
{"id": "a", "vid": 0, "pid": 0x4001},
{"id": "b", "vid": 0x303A, "pid": 0},
id="wildcards_on_different_fields",
),
],
)
def test_overlapping_clients_are_rejected(
first: ConfigType, second: ConfigType
) -> None:
with pytest.raises(cv.Invalid, match="overlap"):
validate_usb_clients([first, second])
def test_every_pair_is_compared_not_just_neighbours() -> None:
configs = [
{"id": "a", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
{"id": "b", "vid": 0x303A, "pid": 0x4002},
{"id": "c", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"},
]
with pytest.raises(cv.Invalid, match="a, c"):
validate_usb_clients(configs)
def _run_final_validate(
devices: list[ConfigType], uarts: list[ConfigType]
) -> ConfigType:
config = {"id": "usb_host", "devices": devices}
token = fv.full_config.set({"usb_host": config, "usb_uart": uarts})
try:
return _final_validate(config)
finally:
fv.full_config.reset(token)
def test_device_duplicating_usb_uart_warns(caplog: pytest.LogCaptureFixture) -> None:
devices = [{"id": "device_0", "vid": 0x303A, "pid": 0x4001}]
uarts = [{"id": "uart_0", "vid": 0x303A, "pid": 0x4001}]
with caplog.at_level(logging.WARNING):
_run_final_validate(devices, uarts)
assert "'device_0'" in caplog.text
assert "'uart_0'" in caplog.text
assert "remove it from usb_host devices" in caplog.text
def test_wildcard_device_covering_usb_uart_suggests_narrowing(
caplog: pytest.LogCaptureFixture,
) -> None:
devices = [{"id": "device_0", "vid": 0x303A, "pid": 0}]
uarts = [{"id": "uart_0", "vid": 0x303A, "pid": 0x4001}]
with caplog.at_level(logging.WARNING):
_run_final_validate(devices, uarts)
assert "narrow its filter" in caplog.text
assert "remove it" not in caplog.text
def test_disjoint_device_and_usb_uart_do_not_warn(
caplog: pytest.LogCaptureFixture,
) -> None:
devices = [{"id": "device_0", "vid": 0x303A, "pid": 0x4002}]
uarts = [{"id": "uart_0", "vid": 0x303A, "pid": 0x4001}]
with caplog.at_level(logging.WARNING):
_run_final_validate(devices, uarts)
assert caplog.text == ""
_DUPLICATE_CLIENTS = [
{"id": "a", "vid": 0x303A, "pid": 0x4001},
{"id": "b", "vid": 0x303A, "pid": 0x4001},
]
@pytest.mark.parametrize(
("devices", "uarts"),
[([*_DUPLICATE_CLIENTS], []), ([], [*_DUPLICATE_CLIENTS])],
ids=["devices", "uarts"],
)
def test_overlap_within_one_component_is_rejected(
devices: list[ConfigType], uarts: list[ConfigType]
) -> None:
with pytest.raises(cv.Invalid, match="a, b"):
_run_final_validate(devices, uarts)
def test_device_inside_broader_usb_uart_suggests_removing(
caplog: pytest.LogCaptureFixture,
) -> None:
devices = [{"id": "device_0", "vid": 0x303A, "pid": 0x4001}]
uarts = [{"id": "uart_0", "vid": 0x303A, "pid": 0}]
with caplog.at_level(logging.WARNING):
_run_final_validate(devices, uarts)
assert "remove it from usb_host devices" in caplog.text