From 2f1d3f8299fe7b8239e8a0af4aa0fad018ded3e6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Aug 2026 17:34:16 -0500 Subject: [PATCH] [core] Deduplicate the host program path lookup (#18897) --- esphome/__main__.py | 33 +++++++++++----------- tests/unit_tests/test_main.py | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 632d2ba3d0..1ebf194205 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1670,20 +1670,26 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None: if exit_code != 0: return exit_code if CORE.is_host: - if CORE.using_toolchain_esp_idf: - from esphome.espidf import toolchain - - program_path = str(toolchain.get_elf_path()) - else: - from esphome.platformio.toolchain import get_idedata - - program_path = str(get_idedata(config).firmware_elf_path) - _LOGGER.info("Successfully compiled program to path '%s'", program_path) + _LOGGER.info( + "Successfully compiled program to path '%s'", _host_program_path(config) + ) else: _LOGGER.info("Successfully compiled program.") return 0 +def _host_program_path(config: ConfigType) -> str: + """Return the compiled host ELF path.""" + if CORE.using_toolchain_esp_idf: + from esphome.espidf import toolchain + + return str(toolchain.get_elf_path()) + from esphome.platformio.toolchain import get_idedata + + # Memoized by compile_program's own call; this is a dict lookup + return str(get_idedata(config).firmware_elf_path) + + def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None: # Get devices, resolving special identifiers like OTA devices = choose_upload_log_host( @@ -1728,14 +1734,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None: return exit_code _LOGGER.info("Successfully compiled program.") if CORE.is_host: - if CORE.using_toolchain_esp_idf: - from esphome.espidf import toolchain - - program_path = str(toolchain.get_elf_path()) - else: - from esphome.platformio.toolchain import get_idedata - - program_path = str(get_idedata(config).firmware_elf_path) + program_path = _host_program_path(config) _LOGGER.info("Running program from path '%s'", program_path) return run_external_process(program_path) diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 08c99e2119..15b1105ed0 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -112,6 +112,7 @@ from esphome.const import ( PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, + PLATFORM_HOST, PLATFORM_NRF52, PLATFORM_RP2, Toolchain, @@ -7254,3 +7255,54 @@ async def test_wrap_to_code_comment_is_insertion_order_independent() -> None: assert first == second assert second.index("alpha") < second.index("beta") assert second.index("a: 2") < second.index("z: 1") + + +def test_host_program_path_platformio_toolchain() -> None: + """Host + PlatformIO toolchain reads the memoized idedata path.""" + setup_core(platform=PLATFORM_HOST) + idedata = SimpleNamespace(firmware_elf_path="/build/x/.pioenvs/x/program") + with patch( + "esphome.platformio.toolchain.get_idedata", return_value=idedata + ) as mock_get: + assert main._host_program_path({}) == "/build/x/.pioenvs/x/program" + mock_get.assert_called_once_with({}) + + +def test_host_program_path_esp_idf_toolchain() -> None: + """Host + native ESP-IDF toolchain asks the espidf toolchain for the ELF.""" + setup_core(platform=PLATFORM_HOST) + CORE.toolchain = Toolchain.ESP_IDF + with patch( + "esphome.espidf.toolchain.get_elf_path", return_value=Path("/b/app.elf") + ): + assert main._host_program_path({}) == str(Path("/b/app.elf")) + + +def test_command_compile_host_logs_program_path( + caplog: pytest.LogCaptureFixture, +) -> None: + """command_compile on host logs the compiled program path.""" + setup_core(platform=PLATFORM_HOST) + with ( + patch.object(main, "write_cpp", return_value=0), + patch.object(main, "compile_program", return_value=0), + patch.object(main, "_host_program_path", return_value="/b/program"), + caplog.at_level(logging.INFO), + ): + assert main.command_compile(SimpleNamespace(only_generate=False), {}) == 0 + assert "Successfully compiled program to path '/b/program'" in caplog.text + + +def test_command_run_host_executes_program(caplog: pytest.LogCaptureFixture) -> None: + """command_run on host logs and executes the compiled program directly.""" + setup_core(platform=PLATFORM_HOST) + with ( + patch.object(main, "write_cpp", return_value=0), + patch.object(main, "compile_program", return_value=0), + patch.object(main, "_host_program_path", return_value="/b/program"), + patch.object(main, "run_external_process", return_value=0) as mock_run, + caplog.at_level(logging.INFO), + ): + assert main.command_run(SimpleNamespace(), {}) == 0 + mock_run.assert_called_with("/b/program") + assert "Running program from path '/b/program'" in caplog.text