[esp8266] Fail fast when Rosetta 2 is missing on Apple Silicon Macs (#17737)

This commit is contained in:
J. Nick Koston
2026-07-21 08:17:01 +12:00
committed by GitHub
parent 500d4aa9e9
commit cd8d76fa34
4 changed files with 138 additions and 4 deletions
+59 -2
View File
@@ -1,9 +1,15 @@
"""Tests for ESP8266 component."""
from __future__ import annotations
from collections.abc import Generator
from unittest.mock import MagicMock, patch
import pytest
from esphome.components.esp8266 import lambdas_use_scanf_float
from esphome.core import Lambda
from esphome.components import esp8266
from esphome.components.esp8266 import check_rosetta, lambdas_use_scanf_float
from esphome.core import EsphomeError, Lambda
from esphome.types import ConfigType
@@ -60,3 +66,54 @@ def test_lambdas_use_scanf_float_nested() -> None:
"""Test detection in deeply nested config."""
config: ConfigType = {"a": {"b": {"c": [Lambda('sscanf(buf, "%f", &v)')]}}}
assert lambdas_use_scanf_float(config) is True
@pytest.fixture
def apple_silicon_run(monkeypatch: pytest.MonkeyPatch) -> Generator[MagicMock]:
"""Simulate an Apple Silicon Mac and yield the mocked subprocess.run."""
monkeypatch.setattr(esp8266, "IS_MACOS", True)
with (
patch("esphome.components.esp8266.platform.machine", return_value="arm64"),
patch("esphome.components.esp8266.subprocess.run") as mock_run,
):
yield mock_run
@pytest.mark.parametrize(
("is_macos", "machine"),
[
(False, "arm64"),
(True, "x86_64"),
],
)
def test_check_rosetta_skips_other_systems(
monkeypatch: pytest.MonkeyPatch, is_macos: bool, machine: str
) -> None:
"""The check only probes on Apple Silicon Macs."""
monkeypatch.setattr(esp8266, "IS_MACOS", is_macos)
with (
patch("esphome.components.esp8266.platform.machine", return_value=machine),
patch("esphome.components.esp8266.subprocess.run") as mock_run,
):
check_rosetta()
mock_run.assert_not_called()
def test_check_rosetta_installed(apple_silicon_run: MagicMock) -> None:
"""No error when the x86_64 probe succeeds (Rosetta present)."""
apple_silicon_run.return_value = MagicMock(returncode=0)
check_rosetta()
apple_silicon_run.assert_called_once()
def test_check_rosetta_missing(apple_silicon_run: MagicMock) -> None:
"""A failing x86_64 probe raises an actionable error."""
apple_silicon_run.return_value = MagicMock(returncode=1)
with pytest.raises(EsphomeError, match="softwareupdate --install-rosetta"):
check_rosetta()
def test_check_rosetta_arch_unavailable(apple_silicon_run: MagicMock) -> None:
"""The build proceeds when arch(1) cannot be executed."""
apple_silicon_run.side_effect = OSError("no such file")
check_rosetta()
+37
View File
@@ -5450,6 +5450,43 @@ def _setup_build_info_test(
return build_info_path, firmware_path
def test_compile_program_esp8266_runs_rosetta_check(tmp_path: Path) -> None:
"""Test that compile_program runs the Rosetta preflight for ESP8266 targets."""
setup_core(platform=PLATFORM_ESP8266, tmp_path=tmp_path, name="test_device")
config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}}
args = MockArgs()
with (
patch(
"esphome.components.esp8266.check_rosetta",
side_effect=EsphomeError("Rosetta 2 is not installed"),
) as mock_check,
pytest.raises(EsphomeError, match="Rosetta 2 is not installed"),
):
compile_program(args, config)
mock_check.assert_called_once()
def test_compile_program_skips_rosetta_check_on_other_platforms(
tmp_path: Path,
mock_compile_build_info_run_compile: Mock,
mock_compile_build_info_get_idedata: Mock,
) -> None:
"""Test that the Rosetta preflight does not run for non-ESP8266 targets."""
_setup_build_info_test(tmp_path, firmware_first=True)
config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}}
args = MockArgs()
with patch("esphome.components.esp8266.check_rosetta") as mock_check:
result = compile_program(args, config)
assert result == 0
mock_check.assert_not_called()
def test_compile_program_emits_build_info_when_firmware_rebuilt(
tmp_path: Path,
caplog: pytest.LogCaptureFixture,