Merge remote-tracking branch 'origin/esp8266-arduino-toolchain' into esp8266-native-pch

This commit is contained in:
J. Nick Koston
2026-08-25 13:32:52 -05:00
18 changed files with 403 additions and 244 deletions
@@ -12,6 +12,7 @@ arbiter a full-duty scan would starve wifi, so the 30 ms default is kept.
from __future__ import annotations
from collections.abc import Callable
import logging
from pathlib import Path
import pytest
@@ -221,3 +222,54 @@ def test_connection_scan_window_codegen(
assert window_call in main_cpp
assert ("set_connection_scan_window(48)" in main_cpp) == connection_call
assert ("'connection_scan_window' has no effect" in caplog.text) == warns
@pytest.mark.parametrize(
("wifi", "params", "expect_warning"),
[
(True, {"interval": "1100ms", "window": "1100ms"}, True),
(True, {"interval": "1100ms", "window": "601ms"}, True),
(True, {"interval": "1100ms", "window": "600ms"}, False),
(False, {"interval": "1100ms", "window": "1100ms"}, False),
],
)
def test_long_window_with_wifi_warns(
stage_esp32: Callable[..., None],
caplog: pytest.LogCaptureFixture,
wifi: bool,
params: ConfigType,
expect_warning: bool,
) -> None:
"""A scan window above 600 ms warns only when wifi shares the radio."""
stage_esp32("5.5.5", wifi=wifi)
with caplog.at_level(logging.WARNING):
_scan_params({"scan_parameters": params})
assert ("starves wifi" in caplog.text) is expect_warning
def test_long_window_warns_with_coexistence_disabled(
stage_esp32: Callable[..., None],
caplog: pytest.LogCaptureFixture,
) -> None:
"""Disabling the arbiter is the worst case for a long window, so it still warns."""
stage_esp32("5.5.5", wifi=True)
with caplog.at_level(logging.WARNING):
_scan_params(
{
CONF_SOFTWARE_COEXISTENCE: False,
"scan_parameters": {"interval": "1100ms", "window": "1100ms"},
}
)
assert "BLE scan window of 1100ms" in caplog.text
def test_raised_window_warning_points_at_interval(
stage_esp32: Callable[..., None],
caplog: pytest.LogCaptureFixture,
) -> None:
"""When the window was raised to a long interval, the warning names the interval."""
stage_esp32("5.5.5", wifi=True)
with caplog.at_level(logging.WARNING):
_scan_params({"scan_parameters": {"interval": "1s"}})
assert "BLE scan interval of 1s" in caplog.text
assert "BLE scan window of" not in caplog.text
@@ -15,7 +15,12 @@ import pytest
sys.path.insert(0, str(Path(__file__).parents[4] / "script" / "api_protobuf"))
from api_protobuf import _make_ifdef_line, get_varint64_ifdef # noqa: E402
from api_protobuf import ( # noqa: E402
MAX_MESSAGE_ID,
_make_ifdef_line,
get_varint64_ifdef,
validate_message_id,
)
from google.protobuf import descriptor_pb2 # noqa: E402
@@ -91,3 +96,14 @@ def test_make_ifdef_line_conjunction_and_negation() -> None:
assert (
_make_ifdef_line("USE_X && !USE_Y") == "#if defined(USE_X) && !defined(USE_Y)"
)
def test_message_id_at_maximum_is_accepted() -> None:
# 16383 is the largest ID whose plaintext type varint fits the 2 bytes
# budgeted in HEADER_PADDING.
validate_message_id(MAX_MESSAGE_ID, "MaxMessage")
def test_message_id_above_maximum_is_rejected() -> None:
with pytest.raises(ValueError, match="exceeds the plaintext"):
validate_message_id(MAX_MESSAGE_ID + 1, "TooBigMessage")
+6 -3
View File
@@ -1602,15 +1602,18 @@ def test_ccache_env_opt_out_via_env(tmp_path: Path) -> None:
assert _ccache_env() == {"IDF_CCACHE_ENABLE": "0"}
def test_ccache_env_opt_in_without_binary(tmp_path: Path) -> None:
# Explicit IDF_CCACHE_ENABLE=1 forces it on; the probe verdict is
# ignored but the resolver still runs for its no-binary warning.
def test_ccache_env_opt_in_without_binary(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
# Explicit IDF_CCACHE_ENABLE=1 forces it on; without a usable binary
# idf.py silently skips ccache, so this branch must say so out loud.
p1, p2, p3 = _ccache_patches(tmp_path, None, tmp_path / "build")
with patch.dict("os.environ", {"IDF_CCACHE_ENABLE": "1"}, clear=True), p1, p2, p3:
env = _ccache_env()
assert env["IDF_CCACHE_ENABLE"] == "1"
assert env["CCACHE_DIR"] == str(tmp_path / "tools" / "ccache")
assert env["CCACHE_DEPEND"] == "1"
assert "no usable ccache binary" in caplog.text
def test_ccache_env_honors_shared_esphome_opt_out(tmp_path: Path) -> None:
@@ -534,6 +534,22 @@ def test_prefetch_packages_downloads_pending_in_parallel(tmp_path: Path) -> None
assert callable(call[1]["progress"])
def test_prefetch_packages_skips_freshly_installed_dest(tmp_path: Path) -> None:
"""A dest whose marker appeared while the worker waited on the lock is
already installed; re-downloading would orphan an archive copy."""
dest = tmp_path / "a"
dest.mkdir()
(dest / ".esphome_extracted").touch()
with (
patch.object(registry, "download_with_resume") as mock_download,
patch.object(
registry, "registry_download", side_effect=_resolve_for({"a": 10})
),
):
registry.prefetch_packages([("a", "1.0", dest, [])], tmp_path / "dl")
mock_download.assert_not_called()
def test_prefetch_packages_dedupes_duplicate_entries(tmp_path: Path) -> None:
"""Duplicate (name, version) entries would race each other between two
workers; only one survives (and one is too few to parallelize)."""
+31 -32
View File
@@ -14,6 +14,7 @@ from unittest.mock import MagicMock, patch
import pytest
from esphome.build_helpers.tools_cache import TOOLS_CACHE_SPECS
from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
@@ -68,17 +69,12 @@ def _isolate_platformio_paths(tmp_path_factory: pytest.TempPathFactory) -> Any:
test_clean_all_partial_exists) install their own inner patch which
stacks on top of this one and wins for the duration of their block.
Also pin ``ESPHOME_ESP_IDF_PREFIX``, ``ESPHOME_SDK_NRF_PREFIX`` and
``ESPHOME_ARDUINO8266_PREFIX`` to
nonexistent tmp dirs, and patch ``platformdirs.user_cache_dir``, for the
same reason: ``clean_all`` removes the machine-global toolchain installs
Also pin every ``TOOLS_CACHE_SPECS`` env override to a nonexistent tmp
dir, and patch ``platformdirs.user_cache_dir``, for the same reason: ``clean_all`` removes the machine-global toolchain installs
and their default cache root, which otherwise resolve to the real
``~/.cache/esphome``.
"""
pio_root = tmp_path_factory.mktemp("isolated_pio") / "nonexistent"
idf_root = tmp_path_factory.mktemp("isolated_idf") / "nonexistent"
sdk_nrf_root = tmp_path_factory.mktemp("isolated_sdk_nrf") / "nonexistent"
arduino8266_root = tmp_path_factory.mktemp("isolated_arduino8266") / "nonexistent"
cache_root = tmp_path_factory.mktemp("isolated_cache") / "nonexistent"
mock_cfg = MagicMock()
mock_cfg.get.side_effect = lambda section, option: (
@@ -92,9 +88,12 @@ def _isolate_platformio_paths(tmp_path_factory: pytest.TempPathFactory) -> Any:
patch.dict(
"os.environ",
{
"ESPHOME_ESP_IDF_PREFIX": str(idf_root),
"ESPHOME_SDK_NRF_PREFIX": str(sdk_nrf_root),
"ESPHOME_ARDUINO8266_PREFIX": str(arduino8266_root),
# Derived from the registry so a new backend's cache can
# never drift out of the sandbox and hit a real toolchain
env_var: str(
tmp_path_factory.mktemp(f"isolated_{subdir}") / "nonexistent"
)
for env_var, subdir in TOOLS_CACHE_SPECS
},
),
patch("platformdirs.user_cache_dir", return_value=str(cache_root)),
@@ -1040,28 +1039,6 @@ def test_clean_all_removes_global_idf_install(
assert str(idf_install.resolve()) in caplog.text
@patch("esphome.writer.CORE")
def test_clean_all_removes_global_arduino8266_install(
mock_core: MagicMock,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""clean_all removes the machine-global native arduino8266 install dir."""
arduino8266_install = tmp_path / "arduino8266_install"
(arduino8266_install / "frameworks").mkdir(parents=True)
monkeypatch.setenv("ESPHOME_ARDUINO8266_PREFIX", str(arduino8266_install))
config_dir = tmp_path / "config"
config_dir.mkdir()
with caplog.at_level("INFO"):
clean_all([str(config_dir)])
assert not arduino8266_install.exists()
assert str(arduino8266_install.resolve()) in caplog.text
@patch("esphome.writer.CORE")
def test_clean_all_removes_global_sdk_nrf_install(
mock_core: MagicMock,
@@ -1086,6 +1063,28 @@ def test_clean_all_removes_global_sdk_nrf_install(
assert str(sdk_nrf_install.resolve()) in caplog.text
@patch("esphome.writer.CORE")
def test_clean_all_removes_global_arduino8266_install(
mock_core: MagicMock,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""clean_all removes the machine-global native arduino8266 install dir."""
arduino8266_install = tmp_path / "arduino8266_install"
(arduino8266_install / "frameworks").mkdir(parents=True)
monkeypatch.setenv("ESPHOME_ARDUINO8266_PREFIX", str(arduino8266_install))
config_dir = tmp_path / "config"
config_dir.mkdir()
with caplog.at_level("INFO"):
clean_all([str(config_dir)])
assert not arduino8266_install.exists()
assert str(arduino8266_install.resolve()) in caplog.text
@patch("esphome.writer.CORE")
def test_clean_all_removes_default_cache_root(
mock_core: MagicMock,