From 9c2a3baaa924f0bc3ff3f9c2e8fcdb831857cecd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 23 Aug 2026 19:38:12 -0500 Subject: [PATCH] Name the platform in the analyze-memory refusal, surface parser-shaped idedata failures, count builds per component under the no-tests flag --- esphome/__main__.py | 5 +++-- esphome/build_helpers/idedata.py | 8 +++++++- script/test_build_components.py | 21 +++++++++++++++------ tests/script/test_test_build_components.py | 22 ++++++++++++++++++++++ 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index b74775524f..c4f4b1d4e6 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -2001,9 +2001,10 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int: native_toolchain = _native_toolchain_module() if native_toolchain is None and not CORE.using_toolchain_platformio: _LOGGER.error( - "analyze-memory is not supported with the '%s' toolchain; it " - "requires a PlatformIO, ESP-IDF, or native Arduino build", + "analyze-memory is not supported with the '%s' toolchain on %s; " + "it requires a PlatformIO, ESP-IDF, or native Arduino build", CORE.toolchain.value if CORE.toolchain else "unresolved", + CORE.target_platform, ) return 1 diff --git a/esphome/build_helpers/idedata.py b/esphome/build_helpers/idedata.py index 7edd5a76ff..0ede397c55 100644 --- a/esphome/build_helpers/idedata.py +++ b/esphome/build_helpers/idedata.py @@ -48,7 +48,13 @@ def warn_if_idedata_missing(get_idedata: Callable[[], dict | None]) -> None: "memory-analysis data will be unavailable for this build)", err, ) - _LOGGER.debug("Idedata failure detail", exc_info=True) + if isinstance(err, (EsphomeError, OSError)): + # Routine environmental failures keep the detail at debug + _LOGGER.debug("Idedata failure detail", exc_info=True) + else: + # LookupError/ValueError/RuntimeError smell like a parsing bug; + # a permanently masked traceback would hide it on every build + _LOGGER.warning("Idedata failure detail", exc_info=True) _LOGGER = logging.getLogger(__name__) diff --git a/script/test_build_components.py b/script/test_build_components.py index 01121a277e..c5b2edc978 100755 --- a/script/test_build_components.py +++ b/script/test_build_components.py @@ -1202,12 +1202,21 @@ def test_components( toolchain=toolchain, ) - if fail_on_no_tests and not test_results: - # A green run that compiled nothing (renamed/removed test fixture, - # bad platform filter) must not pass CI. Opt-in: some legs (the - # esp32-ard smoke subset) legitimately match nothing. - print("No tests matched the requested components/platform") - return 1 + if fail_on_no_tests: + # A green run that built nothing for a requested component (renamed + # fixture, missing base file, version-suffix mismatch) must not pass + # CI. Per component: an all-or-nothing check would let one silent + # component hide behind the others. Opt-in: some legs (the esp32-ard + # smoke subset) legitimately match nothing. + built = {c for r in test_results for c in r.components} + if silent := [ + p for p in component_patterns if p and "*" not in p and p not in built + ]: + print(f"No tests ran for requested component(s): {', '.join(silent)}") + return 1 + if not test_results: + print("No tests matched the requested components/platform") + return 1 # Separate results into passed and failed passed_results = [r for r in test_results if r.success] diff --git a/tests/script/test_test_build_components.py b/tests/script/test_test_build_components.py index 4773d04b6c..b005ffe8c0 100644 --- a/tests/script/test_test_build_components.py +++ b/tests/script/test_test_build_components.py @@ -251,6 +251,28 @@ def test_components_empty_match_fails_with_flag( assert "No zz-none tests found" in capsys.readouterr().out +def test_components_component_with_no_base_file_fails_with_flag( + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A component whose fixture matches the platform but whose platform has + no base file builds nothing; under the flag that silent zero fails by + component name instead of hiding behind other components.""" + monkeypatch.setattr(tbc, "get_platform_base_files", lambda base_dir: {}) + rc = tbc.test_components( + ["logger"], + "esp8266-ard", + "compile", + False, + enable_grouping=False, + fail_on_no_tests=True, + ) + assert rc == 1 + assert "No tests ran for requested component(s): logger" in ( + capsys.readouterr().out + ) + + def test_components_empty_match_tolerated_without_flag() -> None: """The esp32-ard smoke leg deliberately builds only the subset with a matching fixture; without the flag an empty match stays green."""