1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-12 06:43:48 +01:00
This commit is contained in:
J. Nick Koston
2025-10-08 15:45:59 -10:00
parent 7a337f5b03
commit 2c35d91a66
2 changed files with 116 additions and 7 deletions

View File

@@ -102,15 +102,59 @@ def prefix_substitutions_in_dict(
return data
def deduplicate_by_id(data: dict) -> dict:
"""Deduplicate list items with the same ID.
Keeps only the first occurrence of each ID. If items with the same ID
are identical, this silently deduplicates. If they differ, the first
one is kept (ESPHome's validation will catch if this causes issues).
Args:
data: Parsed config dictionary
Returns:
Config with deduplicated lists
"""
if not isinstance(data, dict):
return data
result = {}
for key, value in data.items():
if isinstance(value, list):
# Check for items with 'id' field
seen_ids = set()
deduped_list = []
for item in value:
if isinstance(item, dict) and "id" in item:
item_id = item["id"]
if item_id not in seen_ids:
seen_ids.add(item_id)
deduped_list.append(item)
# else: skip duplicate ID (keep first occurrence)
else:
# No ID, just add it
deduped_list.append(item)
result[key] = deduped_list
elif isinstance(value, dict):
# Recursively deduplicate nested dicts
result[key] = deduplicate_by_id(value)
else:
result[key] = value
return result
def prefix_ids_in_dict(data: Any, prefix: str) -> Any:
"""Recursively prefix all 'id' fields in a data structure.
"""Recursively prefix all 'id' fields and ID references in a data structure.
Args:
data: YAML data structure (dict, list, or scalar)
prefix: Prefix to add to IDs
Returns:
Data structure with prefixed IDs
Data structure with prefixed IDs and ID references
"""
if isinstance(data, dict):
result = {}
@@ -118,6 +162,9 @@ def prefix_ids_in_dict(data: Any, prefix: str) -> Any:
if key == "id" and isinstance(value, str):
# Prefix the ID value
result[key] = f"{prefix}_{value}"
elif key.endswith("_id") and isinstance(value, str):
# Prefix ID references (uart_id, spi_id, i2c_id, etc.)
result[key] = f"{prefix}_{value}"
else:
# Recursively process the value
result[key] = prefix_ids_in_dict(value, prefix)
@@ -186,12 +233,8 @@ def merge_component_configs(
# Prefix substitution references throughout the config
comp_data = prefix_substitutions_in_dict(comp_data, comp_name)
# Note: We don't prefix IDs because that requires updating all ID references
# throughout the config, which is complex. Instead, we rely on components
# already having unique IDs (which they should if properly designed).
# ESPHome's merge_config will handle ID conflicts by replacing duplicates.
# Use ESPHome's merge_config to merge this component into the result
# merge_config handles list merging with ID-based deduplication automatically
merged_config_data = merge_config(merged_config_data, comp_data)
# Add packages back (only once, since they're identical)
@@ -205,6 +248,9 @@ def merge_component_configs(
if "packages" in first_comp_data:
merged_config_data["packages"] = first_comp_data["packages"]
# Deduplicate items with same ID (keeps first occurrence)
merged_config_data = deduplicate_by_id(merged_config_data)
# Write merged config
output_file.parent.mkdir(parents=True, exist_ok=True)
yaml_content = yaml_util.dump(merged_config_data)

View File

@@ -355,7 +355,61 @@ def test_components(
# Run tests
failed_tests = []
passed_tests = []
tested_components = set() # Track which components were tested in groups
# First, run grouped tests if grouping is enabled
if enable_grouping:
for (platform, signature), components in grouped_components.items():
# Only group if we have multiple components with same signature
if len(components) <= 1:
continue
# Filter out components not in our test list
components_to_group = [c for c in components if c in all_tests]
if len(components_to_group) <= 1:
continue
# Get platform base files
if platform not in platform_bases:
continue
for base_file in platform_bases[platform]:
platform_with_version = extract_platform_with_version(base_file)
# Skip if platform filter doesn't match
if platform_filter and platform != platform_filter:
continue
if (
platform_filter
and platform_with_version != platform_filter
and not platform_with_version.startswith(f"{platform_filter}-")
):
continue
# Run grouped test
success = run_grouped_test(
components=components_to_group,
platform=platform,
platform_with_version=platform_with_version,
base_file=base_file,
build_dir=build_dir,
tests_dir=tests_dir,
esphome_command=esphome_command,
continue_on_fail=continue_on_fail,
)
# Mark all components as tested
for comp in components_to_group:
tested_components.add((comp, platform_with_version))
# Record result for each component
test_id = f"GROUPED[{','.join(components_to_group[:3])}{'...' if len(components_to_group) > 3 else ''}].{platform_with_version}"
if success:
passed_tests.append(test_id)
else:
failed_tests.append(test_id)
# Then run individual tests for components not in groups
for component, test_files in sorted(all_tests.items()):
for test_file in test_files:
test_name, platform = parse_test_filename(test_file)
@@ -369,6 +423,11 @@ def test_components(
for base_file in base_files:
platform_with_version = extract_platform_with_version(base_file)
# Skip if already tested in a group
if (component, platform_with_version) in tested_components:
continue
success = run_esphome_test(
component=component,
test_file=test_file,
@@ -404,6 +463,10 @@ def test_components(
):
continue
# Skip if already tested in a group
if (component, platform_with_version) in tested_components:
continue
success = run_esphome_test(
component=component,
test_file=test_file,