mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
[esp8266] Add native toolchain that builds the Arduino core without PlatformIO (#18539)
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
"""Tests for the native (non-PlatformIO) toolchain config validation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import esp8266
|
||||
from esphome.components.esp8266 import (
|
||||
ARDUINO_FRAMEWORK_SCHEMA,
|
||||
_resolve_toolchain,
|
||||
_validate_native_toolchain,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_PLATFORM_VERSION,
|
||||
CONF_SOURCE,
|
||||
CONF_TOOLCHAIN,
|
||||
CONF_VERSION,
|
||||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP8266,
|
||||
Toolchain,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.types import ConfigType
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _arduino_toolchain() -> Generator[None]:
|
||||
# The suite-wide reset_core fixture clears both after each test; the
|
||||
# shared backend resolver reads the platform as well as the toolchain,
|
||||
# and the decode-tool cache lives in CORE.data
|
||||
CORE.toolchain = Toolchain.ARDUINO
|
||||
CORE.data.setdefault(KEY_CORE, {})[KEY_TARGET_PLATFORM] = PLATFORM_ESP8266
|
||||
yield
|
||||
|
||||
|
||||
def _config(
|
||||
board: str = "nodemcuv2",
|
||||
version: str = "3.1.2",
|
||||
source: str | None = None,
|
||||
platform_version: str | None = None,
|
||||
) -> ConfigType:
|
||||
framework: dict[str, str] = {CONF_VERSION: version}
|
||||
if source is not None:
|
||||
framework[CONF_SOURCE] = source
|
||||
if platform_version is not None:
|
||||
framework[CONF_PLATFORM_VERSION] = platform_version
|
||||
# The real schema fills the source/platform_version defaults, so these
|
||||
# tests validate against what config validation actually emits
|
||||
return {
|
||||
CONF_FRAMEWORK: ARDUINO_FRAMEWORK_SCHEMA(framework),
|
||||
CONF_BOARD: board,
|
||||
}
|
||||
|
||||
|
||||
def test_valid_config_passes() -> None:
|
||||
config = _config()
|
||||
assert _validate_native_toolchain(config) is config
|
||||
|
||||
|
||||
def test_platformio_toolchain_skips_checks() -> None:
|
||||
# 3.0.2 is pio-legal (>= the global 3.0.0 floor) but below the native
|
||||
# toolchain's own 3.1.1 floor; the bogus board only the native path checks
|
||||
CORE.toolchain = Toolchain.PLATFORMIO
|
||||
config = _config(board="not_a_board", version="3.0.2")
|
||||
assert _validate_native_toolchain(config) is config
|
||||
|
||||
|
||||
def test_version_below_floor_rejected() -> None:
|
||||
# 3.1.0 has no registry package, so the native floor is 3.1.1
|
||||
with pytest.raises(cv.Invalid, match="3.1.1 or newer"):
|
||||
_validate_native_toolchain(_config(version="3.1.0"))
|
||||
|
||||
|
||||
def test_version_at_floor_accepted() -> None:
|
||||
_validate_native_toolchain(_config(version="3.1.1"))
|
||||
|
||||
|
||||
def test_custom_platform_version_warns_and_is_dropped(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
config = _config(platform_version="platformio/espressif8266@4.0.1")
|
||||
_validate_native_toolchain(config)
|
||||
assert "'platform_version' is ignored" in caplog.text
|
||||
assert CONF_PLATFORM_VERSION not in config[CONF_FRAMEWORK]
|
||||
|
||||
|
||||
def test_default_platform_version_does_not_warn(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
config = _config()
|
||||
_validate_native_toolchain(config)
|
||||
assert "'platform_version' is ignored" not in caplog.text
|
||||
assert CONF_PLATFORM_VERSION not in config[CONF_FRAMEWORK]
|
||||
|
||||
|
||||
def test_custom_source_rejected() -> None:
|
||||
with pytest.raises(cv.Invalid, match="custom framework source"):
|
||||
_validate_native_toolchain(
|
||||
_config(source="https://github.com/esp8266/Arduino.git")
|
||||
)
|
||||
|
||||
|
||||
def test_unsupported_board_rejected() -> None:
|
||||
with pytest.raises(cv.Invalid, match="not supported by"):
|
||||
_validate_native_toolchain(_config(board="not_a_board"))
|
||||
|
||||
|
||||
def test_yaml_toolchain_key_resolves() -> None:
|
||||
"""The documented `toolchain: arduino` YAML key selects the native path."""
|
||||
CORE.toolchain = None
|
||||
_resolve_toolchain({CONF_TOOLCHAIN: Toolchain.ARDUINO})
|
||||
assert CORE.toolchain == Toolchain.ARDUINO
|
||||
assert CORE.using_toolchain_arduino
|
||||
|
||||
|
||||
def test_yaml_toolchain_key_defaults_to_platformio() -> None:
|
||||
CORE.toolchain = None
|
||||
_resolve_toolchain({})
|
||||
assert CORE.toolchain == Toolchain.PLATFORMIO
|
||||
|
||||
|
||||
def test_decode_pc_native_missing_tools_warns_once(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""A stack dump of many addresses produces one missing-tool warning."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"esphome.arduino8266.toolchain.get_addr2line_path",
|
||||
return_value=tmp_path / "missing-addr2line",
|
||||
),
|
||||
patch(
|
||||
"esphome.arduino8266.toolchain.get_elf_path",
|
||||
return_value=tmp_path / "missing.elf",
|
||||
),
|
||||
):
|
||||
esp8266._decode_pc({}, "40201234")
|
||||
esp8266._decode_pc({}, "40201238")
|
||||
assert caplog.text.count("Cannot decode crash addresses") == 1
|
||||
|
||||
|
||||
def test_decode_pc_platformio_missing_tools_warns_once(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""The PlatformIO branch reports a missing addr2line/ELF at the same
|
||||
warning level as the native one; raw undecoded addresses with no
|
||||
stated reason are undiagnosable at default log level."""
|
||||
|
||||
CORE.toolchain = Toolchain.PLATFORMIO
|
||||
idedata = SimpleNamespace(addr2line_path=None, firmware_elf_path=None)
|
||||
with patch("esphome.platformio.toolchain.get_idedata", return_value=idedata):
|
||||
esp8266._decode_pc({}, "40201234")
|
||||
esp8266._decode_pc({}, "40201238")
|
||||
assert caplog.text.count("Cannot decode crash addresses") == 1
|
||||
|
||||
|
||||
def test_resolve_toolchain_rejects_unsupported() -> None:
|
||||
"""ESP8266 rejects a CLI toolchain it cannot serve, like every platform."""
|
||||
|
||||
CORE.toolchain = Toolchain.SDK_NRF
|
||||
with pytest.raises(cv.Invalid, match="Unsupported toolchain 'sdk-nrf'"):
|
||||
_resolve_toolchain({})
|
||||
|
||||
|
||||
def test_run_compile_platformio_falls_through() -> None:
|
||||
"""Under toolchain: platformio the hook returns False without touching
|
||||
the native backend; this is what keeps existing users on PlatformIO."""
|
||||
CORE.toolchain = Toolchain.PLATFORMIO
|
||||
with patch("esphome.arduino8266.toolchain.run_compile") as mock_native:
|
||||
assert esp8266.run_compile(SimpleNamespace(), {}) is False
|
||||
mock_native.assert_not_called()
|
||||
|
||||
|
||||
def test_run_compile_arduino_failure_raises() -> None:
|
||||
"""A non-zero native build fails by name instead of returning success."""
|
||||
CORE.verbose = False
|
||||
with (
|
||||
patch("esphome.arduino8266.toolchain.run_compile", return_value=1),
|
||||
pytest.raises(EsphomeError, match="native build failed"),
|
||||
):
|
||||
esp8266.run_compile(SimpleNamespace(), {})
|
||||
|
||||
|
||||
def test_copy_files_native_skips_platformio_scripts(tmp_path: Path) -> None:
|
||||
"""The native build writes no PlatformIO extra scripts."""
|
||||
CORE.build_path = tmp_path
|
||||
esp8266.copy_files()
|
||||
assert list(tmp_path.iterdir()) == []
|
||||
Reference in New Issue
Block a user