mirror of
https://github.com/esphome/esphome.git
synced 2026-08-23 06:36:23 +00:00
120 lines
4.1 KiB
Python
120 lines
4.1 KiB
Python
"""Tests for ESP8266 component."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Generator
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
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
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("src", "expected"),
|
|
[
|
|
# Basic float formats
|
|
('sscanf(buf, "%f", &v)', True),
|
|
('sscanf(buf, "%F", &v)', True),
|
|
('sscanf(buf, "%e", &v)', True),
|
|
('sscanf(buf, "%E", &v)', True),
|
|
('sscanf(buf, "%g", &v)', True),
|
|
('sscanf(buf, "%G", &v)', True),
|
|
('sscanf(buf, "%a", &v)', True),
|
|
('sscanf(buf, "%A", &v)', True),
|
|
# With modifiers
|
|
('sscanf(buf, "%lf", &v)', True),
|
|
('sscanf(buf, "%Lf", &v)', True),
|
|
('sscanf(buf, "%8lf", &v)', True),
|
|
('sscanf(buf, "%*f")', True),
|
|
('sscanf(buf, "%.2f", &v)', True),
|
|
# Mixed formats
|
|
('sscanf(buf, "%d,%f", &a, &b)', True),
|
|
# fscanf and std::sscanf
|
|
('fscanf(fp, "%f", &v)', True),
|
|
('std::sscanf(buf, "%f", &v)', True),
|
|
# Multi-line
|
|
('sscanf(buf,\n"%f", &v)', True),
|
|
# No float format
|
|
('sscanf(buf, "%d", &v)', False),
|
|
('sscanf(buf, "%s", s)', False),
|
|
# printf not scanf
|
|
('printf("%f", val)', False),
|
|
# %f in a different statement after scanf
|
|
('sscanf(buf, "%d", &x); printf("%f", val);', False),
|
|
# scanf %f in comment only
|
|
('// sscanf(buf, "%f", &v)\nsscanf(buf, "%d", &x)', False),
|
|
('/* sscanf(buf, "%f") */\nsscanf(buf, "%d", &x)', False),
|
|
],
|
|
)
|
|
def test_lambdas_use_scanf_float(src: str, expected: bool) -> None:
|
|
"""Test scanf float detection in lambda source."""
|
|
config: ConfigType = {"test": [Lambda(src)]}
|
|
assert lambdas_use_scanf_float(config) is expected
|
|
|
|
|
|
def test_lambdas_use_scanf_float_no_lambdas() -> None:
|
|
"""Test with config containing no lambdas."""
|
|
config: ConfigType = {"key": "value", "list": [1, 2]}
|
|
assert lambdas_use_scanf_float(config) is False
|
|
|
|
|
|
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()
|