mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
[esp8266] Fail fast when Rosetta 2 is missing on Apple Silicon Macs (#17737)
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user