mirror of
https://github.com/esphome/esphome.git
synced 2025-10-21 03:03:50 +01:00
dry
This commit is contained in:
@@ -34,6 +34,8 @@ from typing import Any
|
|||||||
# Add esphome to path
|
# Add esphome to path
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
from helpers import BASE_BUS_COMPONENTS
|
||||||
|
|
||||||
from esphome import yaml_util
|
from esphome import yaml_util
|
||||||
from esphome.config_helpers import Extend, Remove
|
from esphome.config_helpers import Extend, Remove
|
||||||
|
|
||||||
@@ -67,18 +69,6 @@ NO_BUSES_SIGNATURE = "no_buses"
|
|||||||
# Isolated components have unique signatures and cannot be merged with others
|
# Isolated components have unique signatures and cannot be merged with others
|
||||||
ISOLATED_SIGNATURE_PREFIX = "isolated_"
|
ISOLATED_SIGNATURE_PREFIX = "isolated_"
|
||||||
|
|
||||||
# Base bus components - these ARE the bus implementations and should not
|
|
||||||
# be flagged as needing migration since they are the platform/base components
|
|
||||||
BASE_BUS_COMPONENTS = {
|
|
||||||
"i2c",
|
|
||||||
"spi",
|
|
||||||
"uart",
|
|
||||||
"modbus",
|
|
||||||
"canbus",
|
|
||||||
"remote_transmitter",
|
|
||||||
"remote_receiver",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Components that must be tested in isolation (not grouped or batched with others)
|
# Components that must be tested in isolation (not grouped or batched with others)
|
||||||
# These have known build issues that prevent grouping
|
# These have known build issues that prevent grouping
|
||||||
# NOTE: This should be kept in sync with both test_build_components and split_components_for_ci.py
|
# NOTE: This should be kept in sync with both test_build_components and split_components_for_ci.py
|
||||||
|
@@ -38,6 +38,7 @@ Options:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
from collections import Counter
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
from functools import cache
|
from functools import cache
|
||||||
import json
|
import json
|
||||||
@@ -48,11 +49,13 @@ import sys
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from helpers import (
|
from helpers import (
|
||||||
|
BASE_BUS_COMPONENTS,
|
||||||
CPP_FILE_EXTENSIONS,
|
CPP_FILE_EXTENSIONS,
|
||||||
ESPHOME_COMPONENTS_PATH,
|
|
||||||
PYTHON_FILE_EXTENSIONS,
|
PYTHON_FILE_EXTENSIONS,
|
||||||
changed_files,
|
changed_files,
|
||||||
get_all_dependencies,
|
get_all_dependencies,
|
||||||
|
get_component_from_path,
|
||||||
|
get_component_test_files,
|
||||||
get_components_from_integration_fixtures,
|
get_components_from_integration_fixtures,
|
||||||
parse_test_filename,
|
parse_test_filename,
|
||||||
root_path,
|
root_path,
|
||||||
@@ -142,11 +145,8 @@ def should_run_integration_tests(branch: str | None = None) -> bool:
|
|||||||
|
|
||||||
# Check if any required components changed
|
# Check if any required components changed
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.startswith(ESPHOME_COMPONENTS_PATH):
|
component = get_component_from_path(file)
|
||||||
parts = file.split("/")
|
if component and component in all_required_components:
|
||||||
if len(parts) >= 3:
|
|
||||||
component = parts[2]
|
|
||||||
if component in all_required_components:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -261,10 +261,7 @@ def _component_has_tests(component: str) -> bool:
|
|||||||
Returns:
|
Returns:
|
||||||
True if the component has test YAML files
|
True if the component has test YAML files
|
||||||
"""
|
"""
|
||||||
tests_dir = Path(root_path) / "tests" / "components" / component
|
return bool(get_component_test_files(component))
|
||||||
if not tests_dir.exists():
|
|
||||||
return False
|
|
||||||
return any(tests_dir.glob("test.*.yaml"))
|
|
||||||
|
|
||||||
|
|
||||||
def detect_memory_impact_config(
|
def detect_memory_impact_config(
|
||||||
@@ -291,16 +288,14 @@ def detect_memory_impact_config(
|
|||||||
files = changed_files(branch)
|
files = changed_files(branch)
|
||||||
|
|
||||||
# Find all changed components (excluding core and base bus components)
|
# Find all changed components (excluding core and base bus components)
|
||||||
changed_component_set = set()
|
changed_component_set: set[str] = set()
|
||||||
has_core_changes = False
|
has_core_changes = False
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.startswith(ESPHOME_COMPONENTS_PATH):
|
component = get_component_from_path(file)
|
||||||
parts = file.split("/")
|
if component:
|
||||||
if len(parts) >= 3:
|
|
||||||
component = parts[2]
|
|
||||||
# Skip base bus components as they're used across many builds
|
# Skip base bus components as they're used across many builds
|
||||||
if component not in ["i2c", "spi", "uart", "modbus", "canbus"]:
|
if component not in BASE_BUS_COMPONENTS:
|
||||||
changed_component_set.add(component)
|
changed_component_set.add(component)
|
||||||
elif file.startswith("esphome/"):
|
elif file.startswith("esphome/"):
|
||||||
# Core ESPHome files changed (not component-specific)
|
# Core ESPHome files changed (not component-specific)
|
||||||
@@ -321,25 +316,24 @@ def detect_memory_impact_config(
|
|||||||
return {"should_run": "false"}
|
return {"should_run": "false"}
|
||||||
|
|
||||||
# Find components that have tests and collect their supported platforms
|
# Find components that have tests and collect their supported platforms
|
||||||
components_with_tests = []
|
components_with_tests: list[str] = []
|
||||||
component_platforms_map = {} # Track which platforms each component supports
|
component_platforms_map: dict[
|
||||||
|
str, set[Platform]
|
||||||
|
] = {} # Track which platforms each component supports
|
||||||
|
|
||||||
for component in sorted(changed_component_set):
|
for component in sorted(changed_component_set):
|
||||||
tests_dir = Path(root_path) / "tests" / "components" / component
|
|
||||||
if not tests_dir.exists():
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Look for test files on preferred platforms
|
# Look for test files on preferred platforms
|
||||||
test_files = list(tests_dir.glob("test.*.yaml"))
|
test_files = get_component_test_files(component)
|
||||||
if not test_files:
|
if not test_files:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check if component has tests for any preferred platform
|
# Check if component has tests for any preferred platform
|
||||||
available_platforms = []
|
available_platforms = [
|
||||||
for test_file in test_files:
|
platform
|
||||||
_, platform = parse_test_filename(test_file)
|
for test_file in test_files
|
||||||
if platform != "all" and platform in MEMORY_IMPACT_PLATFORM_PREFERENCE:
|
if (platform := parse_test_filename(test_file)[1]) != "all"
|
||||||
available_platforms.append(platform)
|
and platform in MEMORY_IMPACT_PLATFORM_PREFERENCE
|
||||||
|
]
|
||||||
|
|
||||||
if not available_platforms:
|
if not available_platforms:
|
||||||
continue
|
continue
|
||||||
@@ -367,10 +361,10 @@ def detect_memory_impact_config(
|
|||||||
else:
|
else:
|
||||||
# No common platform - pick the most commonly supported platform
|
# No common platform - pick the most commonly supported platform
|
||||||
# This allows testing components individually even if they can't be merged
|
# This allows testing components individually even if they can't be merged
|
||||||
platform_counts = {}
|
# Count how many components support each platform
|
||||||
for platforms in component_platforms_map.values():
|
platform_counts = Counter(
|
||||||
for p in platforms:
|
p for platforms in component_platforms_map.values() for p in platforms
|
||||||
platform_counts[p] = platform_counts.get(p, 0) + 1
|
)
|
||||||
# Pick the platform supported by most components, preferring earlier in MEMORY_IMPACT_PLATFORM_PREFERENCE
|
# Pick the platform supported by most components, preferring earlier in MEMORY_IMPACT_PLATFORM_PREFERENCE
|
||||||
platform = max(
|
platform = max(
|
||||||
platform_counts.keys(),
|
platform_counts.keys(),
|
||||||
|
@@ -29,6 +29,18 @@ YAML_FILE_EXTENSIONS = (".yaml", ".yml")
|
|||||||
# Component path prefix
|
# Component path prefix
|
||||||
ESPHOME_COMPONENTS_PATH = "esphome/components/"
|
ESPHOME_COMPONENTS_PATH = "esphome/components/"
|
||||||
|
|
||||||
|
# Base bus components - these ARE the bus implementations and should not
|
||||||
|
# be flagged as needing migration since they are the platform/base components
|
||||||
|
BASE_BUS_COMPONENTS = {
|
||||||
|
"i2c",
|
||||||
|
"spi",
|
||||||
|
"uart",
|
||||||
|
"modbus",
|
||||||
|
"canbus",
|
||||||
|
"remote_transmitter",
|
||||||
|
"remote_receiver",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_list_components_output(output: str) -> list[str]:
|
def parse_list_components_output(output: str) -> list[str]:
|
||||||
"""Parse the output from list-components.py script.
|
"""Parse the output from list-components.py script.
|
||||||
@@ -63,6 +75,48 @@ def parse_test_filename(test_file: Path) -> tuple[str, str]:
|
|||||||
return parts[0], "all"
|
return parts[0], "all"
|
||||||
|
|
||||||
|
|
||||||
|
def get_component_from_path(file_path: str) -> str | None:
|
||||||
|
"""Extract component name from a file path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to a file (e.g., "esphome/components/wifi/wifi.cpp")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Component name if path is in components directory, None otherwise
|
||||||
|
"""
|
||||||
|
if not file_path.startswith(ESPHOME_COMPONENTS_PATH):
|
||||||
|
return None
|
||||||
|
parts = file_path.split("/")
|
||||||
|
if len(parts) >= 3:
|
||||||
|
return parts[2]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_component_test_files(
|
||||||
|
component: str, *, all_variants: bool = False
|
||||||
|
) -> list[Path]:
|
||||||
|
"""Get test files for a component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component: Component name (e.g., "wifi")
|
||||||
|
all_variants: If True, returns all test files including variants (test-*.yaml).
|
||||||
|
If False, returns only base test files (test.*.yaml).
|
||||||
|
Default is False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of test file paths for the component, or empty list if none exist
|
||||||
|
"""
|
||||||
|
tests_dir = Path(root_path) / "tests" / "components" / component
|
||||||
|
if not tests_dir.exists():
|
||||||
|
return []
|
||||||
|
|
||||||
|
if all_variants:
|
||||||
|
# Match both test.*.yaml and test-*.yaml patterns
|
||||||
|
return list(tests_dir.glob("test[.-]*.yaml"))
|
||||||
|
# Match only test.*.yaml (base tests)
|
||||||
|
return list(tests_dir.glob("test.*.yaml"))
|
||||||
|
|
||||||
|
|
||||||
def styled(color: str | tuple[str, ...], msg: str, reset: bool = True) -> str:
|
def styled(color: str | tuple[str, ...], msg: str, reset: bool = True) -> str:
|
||||||
prefix = "".join(color) if isinstance(color, tuple) else color
|
prefix = "".join(color) if isinstance(color, tuple) else color
|
||||||
suffix = colorama.Style.RESET_ALL if reset else ""
|
suffix = colorama.Style.RESET_ALL if reset else ""
|
||||||
@@ -331,10 +385,8 @@ def _filter_changed_ci(files: list[str]) -> list[str]:
|
|||||||
# because changes in one file can affect other files in the same component.
|
# because changes in one file can affect other files in the same component.
|
||||||
filtered_files = []
|
filtered_files = []
|
||||||
for f in files:
|
for f in files:
|
||||||
if f.startswith(ESPHOME_COMPONENTS_PATH):
|
component = get_component_from_path(f)
|
||||||
# Check if file belongs to any of the changed components
|
if component and component in component_set:
|
||||||
parts = f.split("/")
|
|
||||||
if len(parts) >= 3 and parts[2] in component_set:
|
|
||||||
filtered_files.append(f)
|
filtered_files.append(f)
|
||||||
|
|
||||||
return filtered_files
|
return filtered_files
|
||||||
|
@@ -4,7 +4,7 @@ from collections.abc import Callable
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from helpers import changed_files, git_ls_files
|
from helpers import changed_files, get_component_from_path, git_ls_files
|
||||||
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
KEY_CORE,
|
KEY_CORE,
|
||||||
@@ -30,10 +30,8 @@ def get_all_component_files() -> list[str]:
|
|||||||
def extract_component_names_array_from_files_array(files):
|
def extract_component_names_array_from_files_array(files):
|
||||||
components = []
|
components = []
|
||||||
for file in files:
|
for file in files:
|
||||||
file_parts = file.split("/")
|
component_name = get_component_from_path(file)
|
||||||
if len(file_parts) >= 4:
|
if component_name and component_name not in components:
|
||||||
component_name = file_parts[2]
|
|
||||||
if component_name not in components:
|
|
||||||
components.append(component_name)
|
components.append(component_name)
|
||||||
return components
|
return components
|
||||||
|
|
||||||
|
@@ -28,6 +28,7 @@ from script.analyze_component_buses import (
|
|||||||
create_grouping_signature,
|
create_grouping_signature,
|
||||||
merge_compatible_bus_groups,
|
merge_compatible_bus_groups,
|
||||||
)
|
)
|
||||||
|
from script.helpers import get_component_test_files
|
||||||
|
|
||||||
# Weighting for batch creation
|
# Weighting for batch creation
|
||||||
# Isolated components can't be grouped/merged, so they count as 10x
|
# Isolated components can't be grouped/merged, so they count as 10x
|
||||||
@@ -45,17 +46,12 @@ def has_test_files(component_name: str, tests_dir: Path) -> bool:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
component_name: Name of the component
|
component_name: Name of the component
|
||||||
tests_dir: Path to tests/components directory
|
tests_dir: Path to tests/components directory (unused, kept for compatibility)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if the component has test.*.yaml files
|
True if the component has test.*.yaml files
|
||||||
"""
|
"""
|
||||||
component_dir = tests_dir / component_name
|
return bool(get_component_test_files(component_name))
|
||||||
if not component_dir.exists() or not component_dir.is_dir():
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check for test.*.yaml files
|
|
||||||
return any(component_dir.glob("test.*.yaml"))
|
|
||||||
|
|
||||||
|
|
||||||
def create_intelligent_batches(
|
def create_intelligent_batches(
|
||||||
|
@@ -39,6 +39,7 @@ from script.analyze_component_buses import (
|
|||||||
merge_compatible_bus_groups,
|
merge_compatible_bus_groups,
|
||||||
uses_local_file_references,
|
uses_local_file_references,
|
||||||
)
|
)
|
||||||
|
from script.helpers import get_component_test_files
|
||||||
from script.merge_component_configs import merge_component_configs
|
from script.merge_component_configs import merge_component_configs
|
||||||
|
|
||||||
|
|
||||||
@@ -100,10 +101,10 @@ def find_component_tests(
|
|||||||
if not comp_dir.is_dir():
|
if not comp_dir.is_dir():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Find test files - either base only (test.*.yaml) or all (test[.-]*.yaml)
|
# Get test files using helper function
|
||||||
pattern = "test.*.yaml" if base_only else "test[.-]*.yaml"
|
test_files = get_component_test_files(comp_dir.name, all_variants=not base_only)
|
||||||
for test_file in comp_dir.glob(pattern):
|
if test_files:
|
||||||
component_tests[comp_dir.name].append(test_file)
|
component_tests[comp_dir.name] = test_files
|
||||||
|
|
||||||
return dict(component_tests)
|
return dict(component_tests)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user