Merge remote-tracking branch 'origin/dev' into jesserockz-2026-503

This commit is contained in:
Jesse Hills
2026-07-30 15:34:19 +12:00
294 changed files with 20081 additions and 7996 deletions
+1 -1
View File
@@ -194,7 +194,7 @@ def cmd_update(args: argparse.Namespace) -> int:
def cmd_har_only(args: argparse.Namespace) -> int:
Path(args.har).write_text(run_waterfall(TARGET_MODULE))
Path(args.har).write_text(run_waterfall(TARGET_MODULE), encoding="utf-8")
print(f"Wrote waterfall HAR to {args.har}")
return 0
+19 -13
View File
@@ -348,9 +348,11 @@ def lint_const_ordered(fname, content):
(
mi,
1,
f"Constant {highlight(mline)} is not ordered, please make sure all "
f"constants are ordered. See line {mi} (should go to line {target}, "
f"{target_text})",
(
f"Constant {highlight(mline)} is not ordered, please make sure all "
f"constants are ordered. See line {mi} (should go to line {target}, "
f"{target_text})"
),
)
)
return errs
@@ -1005,12 +1007,14 @@ def lint_log_multiline_continuation(fname, content):
(
lineno,
col,
"Multi-line log message has a continuation line that does "
"not start with a space. The log viewer uses leading "
"whitespace to detect continuation lines and re-add the "
f"log tag prefix (e.g. {highlight('[C][component:042]:')}).\n"
"Either start the continuation with a space/indent, or "
"split into separate ESP_LOG* calls.",
(
"Multi-line log message has a continuation line that does "
"not start with a space. The log viewer uses leading "
"whitespace to detect continuation lines and re-add the "
f"log tag prefix (e.g. {highlight('[C][component:042]:')}).\n"
"Either start the continuation with a space/indent, or "
"split into separate ESP_LOG* calls."
),
)
)
return errs
@@ -1088,10 +1092,12 @@ def lint_test_package_key_matches_bus(fname, content):
(
lineno,
1,
f"Package key {highlight(pkg_key)} does not match bus directory "
f"{highlight(bus_dir)}. The package key must match the directory "
f"name under tests/test_build_components/common/. "
f"Change {highlight(pkg_key)} to {highlight(bus_dir)}.",
(
f"Package key {highlight(pkg_key)} does not match bus directory "
f"{highlight(bus_dir)}. The package key must match the directory "
f"name under tests/test_build_components/common/. "
f"Change {highlight(pkg_key)} to {highlight(bus_dir)}."
),
)
)
return errs
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Fail when a test fixture writes a platform-list domain as a single dict.
Component tests are merged and built in groups in CI (see
``script/merge_component_configs.py``). ESPHome's ``merge_config`` concatenates
two lists, but when one side is a dict it replaces the other side wholesale
(``esphome/config_helpers.py``). A domain such as ``one_wire:`` or ``ota:``
written in single-dict form therefore deletes every entry other components
contributed to that domain before it in the merge, and is itself deleted by any
list that merges after it. The resulting failure only appears when the affected
components land in the same group -- usually a full component matrix run on an
unrelated PR long after the fixture was written (this is what broke the
dallas_temp tests when ds2484 was added, see #17868).
This guard scans every fixture under ``tests/components/`` and rejects any
top-level domain written as a dict with a ``platform`` key. Such a domain is by
definition a platform list (single-dict form is only user-config sugar), so the
fix is always to write it as a one-element list:
one_wire:
- platform: gpio
pin: 4
"""
from __future__ import annotations
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))
from esphome.core import EsphomeError # noqa: E402
from script.analyze_component_buses import ISOLATED_COMPONENTS # noqa: E402
from script.merge_component_configs import load_yaml_file # noqa: E402
# Resolved relative to this file (not the CWD) so the scan cannot silently cover
# nothing when run from a different directory.
ROOT_DIR = Path(__file__).resolve().parent.parent
TESTS_DIR = ROOT_DIR / "tests" / "components"
def main() -> int:
offenders: list[str] = []
parse_errors: list[str] = []
fixtures_scanned = 0
for fixture in sorted(TESTS_DIR.glob("*/*.yaml")):
# Isolated components are never merged with others, so dict form
# cannot clobber anyone there.
if fixture.parent.name in ISOLATED_COMPONENTS:
continue
try:
data = load_yaml_file(fixture)
except EsphomeError as err:
parse_errors.append(f"{fixture.relative_to(ROOT_DIR)}: {err}")
continue
fixtures_scanned += 1
if not isinstance(data, dict):
continue
for key, value in data.items():
if isinstance(value, dict) and "platform" in value:
offenders.append(f"{fixture.relative_to(ROOT_DIR)}: '{key}:'")
if offenders:
print("Test fixtures with platform domains in single-dict form:\n")
for line in offenders:
print(f" - {line}")
print(
"\nWrite the domain as a one-element list ('- platform: ...') so "
"grouped CI builds can merge it with other components' entries; "
"in dict form it replaces or is replaced by their lists wholesale."
)
if parse_errors:
# A fixture we could not parse was never scanned, so the run is not a
# clean pass even if no offenders were found among the rest.
print(
f"\n{len(parse_errors)} test fixture(s) could not be parsed and "
"were not checked:"
)
for line in parse_errors:
print(f" - {line}")
if fixtures_scanned == 0:
# A scan that covered nothing is a false green -- the whole point of the
# guard is defeated. Fail loudly (wrong working directory or layout change).
print(
f"\nERROR: scanned 0 test fixtures under {TESTS_DIR}; "
"the guard covered nothing.",
file=sys.stderr,
)
if offenders or parse_errors or fixtures_scanned == 0:
return 1
print(
f"No single-dict platform domains found ({fixtures_scanned} fixtures scanned)."
)
return 0
if __name__ == "__main__":
sys.exit(main())
+1 -1
View File
@@ -269,7 +269,7 @@ def main() -> int:
if args.output_build_dir and build_dir:
build_dir_path = Path(args.output_build_dir)
build_dir_path.parent.mkdir(parents=True, exist_ok=True)
build_dir_path.write_text(build_dir)
build_dir_path.write_text(build_dir, encoding="utf-8")
print(f"Wrote build directory to {args.output_build_dir}", file=sys.stderr)
# Run detailed analysis if build directory available
+4 -1
View File
@@ -421,7 +421,10 @@ def _get_github_event_data() -> dict | None:
"""
github_event_path = os.environ.get("GITHUB_EVENT_PATH")
if github_event_path and Path(github_event_path).exists():
with Path(github_event_path).open() as f:
# The event payload is UTF-8 JSON; without an explicit encoding
# Windows decodes it as cp1252 and any non ASCII byte (an ellipsis in
# a commit title is enough) raises UnicodeDecodeError.
with Path(github_event_path).open(encoding="utf-8") as f:
return json.load(f)
return None
+1 -1
View File
@@ -1,5 +1,5 @@
{
"target_module": "esphome.__main__",
"margin_pct": 20,
"cumulative_us": 95000
"cumulative_us": 37200
}
+1 -1
View File
@@ -84,7 +84,7 @@ def _read_codspeed_version(cmake_path: Path) -> str:
"""Extract CODSPEED_VERSION from core/CMakeLists.txt."""
if not cmake_path.exists():
return "0.0.0"
for line in cmake_path.read_text().splitlines():
for line in cmake_path.read_text(encoding="utf-8").splitlines():
if line.startswith("set(CODSPEED_VERSION"):
return line.split()[1].rstrip(")")
return "0.0.0"
+2 -2
View File
@@ -367,7 +367,7 @@ def run_esphome_test(
output_file = build_dir / f"{component}.{test_name}.{platform_with_version}.yaml"
# Copy base file and substitute component test file reference
base_content = base_file.read_text()
base_content = base_file.read_text(encoding="utf-8")
# Get relative path from build dir to test file
repo_root = Path(__file__).parent.parent
component_test_ref = f"../../{test_file.relative_to(repo_root / 'tests')}"
@@ -524,7 +524,7 @@ def run_grouped_test(
# Create test file that includes merged config
output_file = build_dir / f"test_{group_name}.{platform_with_version}.yaml"
base_content = base_file.read_text()
base_content = base_file.read_text(encoding="utf-8")
merged_ref = merged_config_file.name
output_content = base_content.replace("$component_test_file", merged_ref)
output_file.write_text(output_content)