mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
Add filter_source_files_from_defines helper, click trigger test coverage
This commit is contained in:
@@ -5,6 +5,7 @@ from esphome.automation import Condition, maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
from esphome.components.const import CONF_ON_STATE_CHANGE
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DELAY,
|
||||
@@ -560,9 +561,9 @@ _CALLBACK_AUTOMATIONS = (
|
||||
async def _build_binary_sensor_automations(var, config):
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
if CONF_ON_CLICK in config or CONF_ON_DOUBLE_CLICK in config:
|
||||
if config.get(CONF_ON_CLICK) or config.get(CONF_ON_DOUBLE_CLICK):
|
||||
cg.add_define("USE_BINARY_SENSOR_CLICK_TRIGGER")
|
||||
if CONF_ON_MULTI_CLICK in config:
|
||||
if config.get(CONF_ON_MULTI_CLICK):
|
||||
cg.add_define("USE_BINARY_SENSOR_MULTI_CLICK_TRIGGER")
|
||||
|
||||
for conf in config.get(CONF_ON_CLICK, []):
|
||||
@@ -680,17 +681,13 @@ async def binary_sensor_invalidate_state_to_code(config, action_id, template_arg
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
"""automation.cpp only implements the click/double_click/multi_click
|
||||
triggers and filter.cpp is fully #ifdef'd on USE_BINARY_SENSOR_FILTER;
|
||||
skip copying them when unused so they are not opened and parsed."""
|
||||
defines = {define.name for define in CORE.defines}
|
||||
files: list[str] = []
|
||||
if not defines & {
|
||||
"USE_BINARY_SENSOR_CLICK_TRIGGER",
|
||||
"USE_BINARY_SENSOR_MULTI_CLICK_TRIGGER",
|
||||
}:
|
||||
files.append("automation.cpp")
|
||||
if "USE_BINARY_SENSOR_FILTER" not in defines:
|
||||
files.append("filter.cpp")
|
||||
return files
|
||||
# automation.cpp only implements the click/double_click/multi_click triggers
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_defines(
|
||||
{
|
||||
"automation.cpp": (
|
||||
"USE_BINARY_SENSOR_CLICK_TRIGGER",
|
||||
"USE_BINARY_SENSOR_MULTI_CLICK_TRIGGER",
|
||||
),
|
||||
"filter.cpp": "USE_BINARY_SENSOR_FILTER",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ from typing import Any
|
||||
from esphome import yaml_util
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_ENABLE_OTA_DOWNGRADE_PROTECTION
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ADVANCED,
|
||||
@@ -3452,10 +3453,8 @@ def process_stacktrace(config, line, backtrace_state):
|
||||
return backtrace_state
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
"""gpio.cpp only implements ESP32InternalGPIOPin (and its ISR helpers),
|
||||
which is instantiated solely by the pin schema codegen; skip copying it
|
||||
when the config uses no internal GPIO pins."""
|
||||
if not any(define.name == "USE_ESP32_INTERNAL_GPIO" for define in CORE.defines):
|
||||
return ["gpio.cpp"]
|
||||
return []
|
||||
# gpio.cpp only implements ESP32InternalGPIOPin and its ISR helpers, which
|
||||
# are instantiated solely by the pin schema codegen (esp32_pin_to_code)
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_defines(
|
||||
{"gpio.cpp": "USE_ESP32_INTERNAL_GPIO"}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "esphome/core/defines.h"
|
||||
// Also defines the core ISRInternalGPIOPin methods; those are only reachable
|
||||
// via ESP32InternalGPIOPin::to_isr(), so the same define gates both safely.
|
||||
#if defined(USE_ESP32) && defined(USE_ESP32_INTERNAL_GPIO)
|
||||
|
||||
#include "gpio.h"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
filter_source_files_from_platform,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
@@ -171,24 +174,17 @@ _filter_backend_source_files = filter_source_files_from_platform(
|
||||
)
|
||||
|
||||
|
||||
# USE_OTA_SIGNED_VERIFICATION_MULTI_KEY is set only on ESP32/IDF;
|
||||
# USE_OTA_PARTITIONS is set by the esphome OTA platform when
|
||||
# allow_partition_access is enabled.
|
||||
_filter_define_source_files = filter_source_files_from_defines(
|
||||
{
|
||||
"ota_signature_esp_idf.cpp": "USE_OTA_SIGNED_VERIFICATION_MULTI_KEY",
|
||||
"ota_bootloader_esp_idf.cpp": "USE_OTA_PARTITIONS",
|
||||
"ota_partitions_esp_idf.cpp": "USE_OTA_PARTITIONS",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
files = _filter_backend_source_files()
|
||||
# ota_signature_esp_idf.cpp implements multi-key OTA signature verification,
|
||||
# compiled only when the esp32 component enables it (external RSA signed
|
||||
# OTA sets USE_OTA_SIGNED_VERIFICATION_MULTI_KEY). The define is set only on
|
||||
# ESP32/IDF, so this also excludes the file on every other platform. Filter
|
||||
# it out otherwise so the (otherwise fully #ifdef'd-out) file isn't opened
|
||||
# and parsed on every build.
|
||||
if not any(
|
||||
define.name == "USE_OTA_SIGNED_VERIFICATION_MULTI_KEY"
|
||||
for define in CORE.defines
|
||||
):
|
||||
files.append("ota_signature_esp_idf.cpp")
|
||||
# ota_bootloader_esp_idf.cpp and ota_partitions_esp_idf.cpp are fully
|
||||
# #ifdef'd on USE_OTA_PARTITIONS (set by the esphome OTA platform when
|
||||
# allow_partition_access is enabled). Filter them out otherwise for the
|
||||
# same reason as above.
|
||||
if not any(define.name == "USE_OTA_PARTITIONS" for define in CORE.defines):
|
||||
files.append("ota_bootloader_esp_idf.cpp")
|
||||
files.append("ota_partitions_esp_idf.cpp")
|
||||
return files
|
||||
return _filter_backend_source_files() + _filter_define_source_files()
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
from esphome.components.const import CONF_B_CONSTANT
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ABOVE,
|
||||
@@ -1305,9 +1306,6 @@ async def to_code(config):
|
||||
cg.add_global(sensor_ns.using)
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
"""filter.cpp is fully #ifdef'd on USE_SENSOR_FILTER; skip copying it
|
||||
when no sensor uses filters so it is not opened and parsed."""
|
||||
if not any(define.name == "USE_SENSOR_FILTER" for define in CORE.defines):
|
||||
return ["filter.cpp"]
|
||||
return []
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_defines(
|
||||
{"filter.cpp": "USE_SENSOR_FILTER"}
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
@@ -258,9 +259,6 @@ async def text_sensor_state_to_code(config, condition_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
"""filter.cpp is fully #ifdef'd on USE_TEXT_SENSOR_FILTER; skip copying it
|
||||
when no text sensor uses filters so it is not opened and parsed."""
|
||||
if not any(define.name == "USE_TEXT_SENSOR_FILTER" for define in CORE.defines):
|
||||
return ["filter.cpp"]
|
||||
return []
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_defines(
|
||||
{"filter.cpp": "USE_TEXT_SENSOR_FILTER"}
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor, time
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_TIME_ID,
|
||||
@@ -10,7 +11,6 @@ from esphome.const import (
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_SECOND,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
uptime_ns = cg.esphome_ns.namespace("uptime")
|
||||
UptimeSecondsSensor = uptime_ns.class_(
|
||||
@@ -62,9 +62,6 @@ async def to_code(config):
|
||||
cg.add(var.set_time(time_id))
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
# uptime_timestamp_sensor.cpp is fully #ifdef'd on USE_TIME; skip it
|
||||
# when no time component is configured.
|
||||
if not any(define.name == "USE_TIME" for define in CORE.defines):
|
||||
return ["uptime_timestamp_sensor.cpp"]
|
||||
return []
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_defines(
|
||||
{"uptime_timestamp_sensor.cpp": "USE_TIME"}
|
||||
)
|
||||
|
||||
@@ -151,6 +151,31 @@ def filter_source_files_from_platform(
|
||||
return filter_source_files
|
||||
|
||||
|
||||
def filter_source_files_from_defines(
|
||||
files_map: dict[str, str | tuple[str, ...]],
|
||||
) -> Callable[[], list[str]]:
|
||||
"""Helper to build a FILTER_SOURCE_FILES function from a define mapping.
|
||||
|
||||
Args:
|
||||
files_map: Dict mapping filename to the define name (or tuple of
|
||||
define names) that keeps the file in the build; the file is
|
||||
excluded when none of its defines is set for the current config.
|
||||
|
||||
Returns:
|
||||
Function that returns the files to exclude for the current config.
|
||||
"""
|
||||
|
||||
def filter_source_files() -> list[str]:
|
||||
defines = {define.name for define in CORE.defines}
|
||||
return [
|
||||
filename
|
||||
for filename, needed in files_map.items()
|
||||
if defines.isdisjoint((needed,) if isinstance(needed, str) else needed)
|
||||
]
|
||||
|
||||
return filter_source_files
|
||||
|
||||
|
||||
def get_logger_level() -> str:
|
||||
"""Get the configured logger level.
|
||||
|
||||
|
||||
@@ -136,3 +136,19 @@ binary_sensor:
|
||||
invalid_cooldown: 2s
|
||||
then:
|
||||
- logger.log: "Click with custom cooldown"
|
||||
|
||||
# Test on_click and on_double_click (compiles match_interval via
|
||||
# USE_BINARY_SENSOR_CLICK_TRIGGER)
|
||||
- platform: template
|
||||
id: click_triggers
|
||||
name: "Click Triggers"
|
||||
on_click:
|
||||
min_length: 50ms
|
||||
max_length: 350ms
|
||||
then:
|
||||
- logger.log: "Clicked"
|
||||
on_double_click:
|
||||
min_length: 50ms
|
||||
max_length: 350ms
|
||||
then:
|
||||
- logger.log: "Double clicked"
|
||||
|
||||
@@ -6,6 +6,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
filter_source_files_from_platform,
|
||||
frameworks_for_platforms,
|
||||
get_logger_level,
|
||||
@@ -18,6 +19,7 @@ from esphome.const import (
|
||||
KEY_TARGET_PLATFORM,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import Define
|
||||
|
||||
|
||||
def test_filter_source_files_from_platform_esp32() -> None:
|
||||
@@ -148,3 +150,25 @@ def test_frameworks_for_platforms_derives_and_rejects_unknown() -> None:
|
||||
}
|
||||
with pytest.raises(ValueError, match="unknown platform"):
|
||||
frameworks_for_platforms(["esp32", "not_a_platform"])
|
||||
|
||||
|
||||
def test_filter_source_files_from_defines() -> None:
|
||||
"""Files are excluded unless one of their defines is set."""
|
||||
files_map: dict[str, str | tuple[str, ...]] = {
|
||||
"filter.cpp": "USE_SENSOR_FILTER",
|
||||
"automation.cpp": ("USE_CLICK", "USE_MULTI_CLICK"),
|
||||
}
|
||||
filter_func: Callable[[], list[str]] = filter_source_files_from_defines(files_map)
|
||||
|
||||
with patch("esphome.config_helpers.CORE") as mock_core:
|
||||
mock_core.defines = {Define("USE_SENSOR_FILTER")}
|
||||
assert filter_func() == ["automation.cpp"]
|
||||
|
||||
mock_core.defines = {Define("USE_MULTI_CLICK")}
|
||||
assert filter_func() == ["filter.cpp"]
|
||||
|
||||
mock_core.defines = {Define("USE_SENSOR_FILTER"), Define("USE_CLICK")}
|
||||
assert filter_func() == []
|
||||
|
||||
mock_core.defines = set()
|
||||
assert sorted(filter_func()) == ["automation.cpp", "filter.cpp"]
|
||||
|
||||
Reference in New Issue
Block a user