mirror of
https://github.com/esphome/esphome.git
synced 2025-10-12 06:43:48 +01:00
fix
This commit is contained in:
@@ -102,15 +102,59 @@ def prefix_substitutions_in_dict(
|
|||||||
return data
|
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:
|
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:
|
Args:
|
||||||
data: YAML data structure (dict, list, or scalar)
|
data: YAML data structure (dict, list, or scalar)
|
||||||
prefix: Prefix to add to IDs
|
prefix: Prefix to add to IDs
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Data structure with prefixed IDs
|
Data structure with prefixed IDs and ID references
|
||||||
"""
|
"""
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
result = {}
|
result = {}
|
||||||
@@ -118,6 +162,9 @@ def prefix_ids_in_dict(data: Any, prefix: str) -> Any:
|
|||||||
if key == "id" and isinstance(value, str):
|
if key == "id" and isinstance(value, str):
|
||||||
# Prefix the ID value
|
# Prefix the ID value
|
||||||
result[key] = f"{prefix}_{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:
|
else:
|
||||||
# Recursively process the value
|
# Recursively process the value
|
||||||
result[key] = prefix_ids_in_dict(value, prefix)
|
result[key] = prefix_ids_in_dict(value, prefix)
|
||||||
@@ -186,12 +233,8 @@ def merge_component_configs(
|
|||||||
# Prefix substitution references throughout the config
|
# Prefix substitution references throughout the config
|
||||||
comp_data = prefix_substitutions_in_dict(comp_data, comp_name)
|
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
|
# 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)
|
merged_config_data = merge_config(merged_config_data, comp_data)
|
||||||
|
|
||||||
# Add packages back (only once, since they're identical)
|
# Add packages back (only once, since they're identical)
|
||||||
@@ -205,6 +248,9 @@ def merge_component_configs(
|
|||||||
if "packages" in first_comp_data:
|
if "packages" in first_comp_data:
|
||||||
merged_config_data["packages"] = first_comp_data["packages"]
|
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
|
# Write merged config
|
||||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
yaml_content = yaml_util.dump(merged_config_data)
|
yaml_content = yaml_util.dump(merged_config_data)
|
||||||
|
@@ -355,7 +355,61 @@ def test_components(
|
|||||||
# Run tests
|
# Run tests
|
||||||
failed_tests = []
|
failed_tests = []
|
||||||
passed_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 component, test_files in sorted(all_tests.items()):
|
||||||
for test_file in test_files:
|
for test_file in test_files:
|
||||||
test_name, platform = parse_test_filename(test_file)
|
test_name, platform = parse_test_filename(test_file)
|
||||||
@@ -369,6 +423,11 @@ def test_components(
|
|||||||
|
|
||||||
for base_file in base_files:
|
for base_file in base_files:
|
||||||
platform_with_version = extract_platform_with_version(base_file)
|
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(
|
success = run_esphome_test(
|
||||||
component=component,
|
component=component,
|
||||||
test_file=test_file,
|
test_file=test_file,
|
||||||
@@ -404,6 +463,10 @@ def test_components(
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Skip if already tested in a group
|
||||||
|
if (component, platform_with_version) in tested_components:
|
||||||
|
continue
|
||||||
|
|
||||||
success = run_esphome_test(
|
success = run_esphome_test(
|
||||||
component=component,
|
component=component,
|
||||||
test_file=test_file,
|
test_file=test_file,
|
||||||
|
Reference in New Issue
Block a user