mirror of
https://github.com/esphome/esphome.git
synced 2026-09-23 21:14:03 +00:00
138 lines
5.4 KiB
Python
138 lines
5.4 KiB
Python
"""Listener and dumper StaticVector sizes come from codegen slot counts."""
|
|
|
|
from collections.abc import Callable, Generator
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from esphome import loader
|
|
from esphome.automation import ACTION_REGISTRY
|
|
from esphome.components import remote_base
|
|
import esphome.config_validation as cv
|
|
from esphome.core import CORE
|
|
|
|
from ..helpers import get_define_value
|
|
|
|
|
|
def test_dumper_and_listener_counts(
|
|
generate_main: Callable[[str | Path], str],
|
|
component_config_path: Callable[[str], Path],
|
|
) -> None:
|
|
generate_main(component_config_path("receiver_with_dumpers.yaml"))
|
|
# nec and rc_switch dumpers
|
|
assert get_define_value("REMOTE_BASE_DUMPER_COUNT") == "2"
|
|
# on_nec trigger plus the remote_receiver binary sensor
|
|
assert get_define_value("REMOTE_BASE_LISTENER_COUNT") == "2"
|
|
|
|
|
|
def test_bare_receiver_emits_no_counts(
|
|
generate_main: Callable[[str | Path], str],
|
|
component_config_path: Callable[[str], Path],
|
|
) -> None:
|
|
main_cpp = generate_main(component_config_path("receiver_bare.yaml"))
|
|
# the RMT ring is sized in setup() unless buffer_size is set
|
|
assert "set_buffer_size" not in main_cpp
|
|
assert get_define_value("REMOTE_BASE_DUMPER_COUNT") is None
|
|
assert get_define_value("REMOTE_BASE_LISTENER_COUNT") is None
|
|
|
|
|
|
def test_proxy_receivers_count_as_listeners(
|
|
generate_main: Callable[[str | Path], str],
|
|
component_config_path: Callable[[str], Path],
|
|
) -> None:
|
|
generate_main(component_config_path("receiver_with_proxies.yaml"))
|
|
# one proxy entity listens on each of the two receivers; every receiver's list gets the
|
|
# capacity of the busiest one, so this is the largest per receiver count, not the sum
|
|
assert get_define_value("REMOTE_BASE_LISTENER_COUNT") == "1"
|
|
assert get_define_value("REMOTE_BASE_DUMPER_COUNT") is None
|
|
|
|
|
|
def test_only_used_protocol_sources_are_compiled(
|
|
generate_main: Callable[[str | Path], str],
|
|
component_config_path: Callable[[str], Path],
|
|
) -> None:
|
|
generate_main(component_config_path("receiver_with_dumpers.yaml"))
|
|
excluded = set(remote_base.FILTER_SOURCE_FILES())
|
|
assert "nec_protocol.cpp" not in excluded
|
|
assert "rc_switch_protocol.cpp" not in excluded
|
|
assert "sony_protocol.cpp" in excluded
|
|
assert "remote_base.cpp" not in excluded
|
|
|
|
|
|
def test_every_registry_name_maps_to_a_protocol_source() -> None:
|
|
"""A registry name must resolve to a source file or request_protocol rejects it."""
|
|
names = (
|
|
set(remote_base.BINARY_SENSOR_REGISTRY)
|
|
| set(remote_base.DUMPER_REGISTRY)
|
|
| {key.removeprefix("on_") for key in remote_base.TRIGGER_REGISTRY}
|
|
| {
|
|
key.removeprefix("remote_transmitter.transmit_")
|
|
for key in ACTION_REGISTRY
|
|
if key.startswith("remote_transmitter.transmit_")
|
|
}
|
|
)
|
|
assert len(names) > 40
|
|
for name in names:
|
|
assert remote_base._protocol_stem(name) in remote_base._PROTOCOL_STEMS, name
|
|
|
|
|
|
@pytest.fixture
|
|
def restore_protocol_registries() -> Generator[None]:
|
|
"""Loading an external protocol component adds to module-level registries; undo that.
|
|
|
|
The loader caches the component too, so drop it or a second load would skip the
|
|
decorators and leave the restored registries without the external names.
|
|
"""
|
|
registries = (
|
|
remote_base.BINARY_SENSOR_REGISTRY,
|
|
remote_base.TRIGGER_REGISTRY,
|
|
remote_base.DUMPER_REGISTRY,
|
|
ACTION_REGISTRY,
|
|
)
|
|
saved = [dict(registry) for registry in registries]
|
|
yield
|
|
for registry, entries in zip(registries, saved, strict=True):
|
|
registry.clear()
|
|
registry.update(entries)
|
|
loader._COMPONENT_CACHE.pop("fake_protocol", None)
|
|
sys.modules.pop("esphome.components.fake_protocol", None)
|
|
|
|
|
|
@pytest.mark.usefixtures("restore_protocol_registries")
|
|
def test_external_protocols_register_without_a_remote_base_source(
|
|
generate_main: Callable[[str | Path], str],
|
|
component_config_path: Callable[[str], Path],
|
|
) -> None:
|
|
"""An external protocol goes through all four decorators without a source file here, so no define is emitted."""
|
|
main_cpp = generate_main(
|
|
component_config_path("receiver_with_external_protocol.yaml")
|
|
)
|
|
defines = {define.name for define in CORE.defines}
|
|
assert "USE_REMOTE_PROTOCOL_NEC" in defines
|
|
assert "USE_REMOTE_PROTOCOL_FAKE" not in defines
|
|
for cls in ("FakeBinarySensor", "FakeTrigger", "FakeDumper", "FakeAction"):
|
|
assert f"fake_protocol::{cls}" in main_cpp, cls
|
|
# fake and nec dumpers; on_fake and on_nec triggers plus the fake binary sensor
|
|
assert get_define_value("REMOTE_BASE_DUMPER_COUNT") == "2"
|
|
assert get_define_value("REMOTE_BASE_LISTENER_COUNT") == "3"
|
|
|
|
|
|
def test_request_protocol_rejects_unknown_names() -> None:
|
|
"""A misspelled protocol would otherwise surface only as a link error."""
|
|
with pytest.raises(ValueError, match="Unknown remote protocol 'toshiba'"):
|
|
remote_base.request_protocol("toshiba")
|
|
|
|
|
|
def test_dump_list_is_deduplicated_across_forms() -> None:
|
|
dumpers = remote_base.validate_dumpers(["raw", {"raw": None}, "nec", "nec"])
|
|
assert [
|
|
next(k for k in entry if k in remote_base.DUMPER_REGISTRY) for entry in dumpers
|
|
] == ["raw", "nec"]
|
|
|
|
|
|
@pytest.mark.parametrize("bad", [["nec", None], [5]])
|
|
def test_dump_list_rejects_invalid_entries_with_a_validation_error(bad: list) -> None:
|
|
with pytest.raises(cv.Invalid):
|
|
remote_base.validate_dumpers(bad)
|