mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 17:00:21 +00:00
[esp8266] Use our own compiler builds for the native toolchain (#19849)
This commit is contained in:
@@ -54,6 +54,91 @@ def test_tools_path_default_and_prefix(tmp_path: Path) -> None:
|
||||
assert path != Path.cwd()
|
||||
|
||||
|
||||
def test_toolchain_builds_are_pinned() -> None:
|
||||
"""A release must not be pinned without its checksums."""
|
||||
for sha256, size in framework.TOOLCHAIN_BUILDS.values():
|
||||
assert len(sha256) == 64
|
||||
assert size > 0
|
||||
|
||||
|
||||
def test_toolchain_download() -> None:
|
||||
sha256, size = framework.TOOLCHAIN_BUILDS["darwin_arm64"]
|
||||
with (
|
||||
patch.object(framework, "TOOLCHAIN_VERSION", "1.2.3"),
|
||||
patch.object(framework, "get_systype", return_value="darwin_arm64"),
|
||||
):
|
||||
download = framework.toolchain_download()
|
||||
releases = "https://github.com/esphome-libs/xtensa-lx106-elf-toolchain/releases/"
|
||||
archive = "toolchain-xtensa-lx106-elf-1.2.3-darwin_arm64.tar.gz"
|
||||
assert download == (f"{releases}download/1.2.3/{archive}", sha256, size)
|
||||
|
||||
|
||||
def test_toolchain_download_unsupported_system() -> None:
|
||||
with (
|
||||
patch.object(framework, "get_systype", return_value="linux_armv7l"),
|
||||
pytest.raises(
|
||||
EsphomeError, match=r"linux_armv7l.*darwin_arm64.*toolchain: platformio"
|
||||
),
|
||||
):
|
||||
framework.toolchain_download()
|
||||
|
||||
|
||||
def _fake_framework(tmp_path: Path) -> None:
|
||||
"""The layout install_package expects of an installed framework."""
|
||||
for sub in ("cores/esp8266", "tools/sdk", "libraries"):
|
||||
(tmp_path / "frameworks" / "3.30102.0" / sub).mkdir(parents=True)
|
||||
(tmp_path / "frameworks" / "3.30102.0" / ".esphome_extracted").touch()
|
||||
|
||||
|
||||
def test_check_and_install_mirror_skips_pinned_toolchain(tmp_path: Path) -> None:
|
||||
"""With a mirror override an unsupported host can bring its own toolchain."""
|
||||
with (
|
||||
patch.dict(os.environ, {"ESPHOME_ARDUINO8266_PREFIX": str(tmp_path)}),
|
||||
patch.object(framework, "ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS", ["http://m"]),
|
||||
patch.object(framework, "install_package") as mock_install,
|
||||
patch.object(framework, "prefetch_packages") as mock_prefetch,
|
||||
patch.object(framework, "find_ninja", return_value=tmp_path / "ninja"),
|
||||
):
|
||||
framework.check_and_install(cv.Version(3, 1, 2))
|
||||
assert mock_prefetch.call_args.args[2] == {}
|
||||
assert mock_install.call_args_list[1].kwargs["resolve"] is None
|
||||
|
||||
|
||||
def test_check_and_install_installed_toolchain_on_unsupported_host(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""The host is only checked when the toolchain must be downloaded."""
|
||||
_fake_framework(tmp_path)
|
||||
toolchain = tmp_path / "toolchains" / framework.TOOLCHAIN_VERSION
|
||||
for sub in ("bin", "xtensa-lx106-elf"):
|
||||
(toolchain / sub).mkdir(parents=True)
|
||||
(toolchain / ".esphome_extracted").touch()
|
||||
with (
|
||||
patch.dict(os.environ, {"ESPHOME_ARDUINO8266_PREFIX": str(tmp_path)}),
|
||||
patch.object(framework, "get_systype", return_value="linux_armv7l"),
|
||||
patch.object(framework, "prefetch_packages"),
|
||||
patch.object(framework, "find_ninja", return_value=tmp_path / "ninja"),
|
||||
):
|
||||
paths = framework.check_and_install(cv.Version(3, 1, 2))
|
||||
assert paths.toolchain == toolchain
|
||||
|
||||
|
||||
def test_check_and_install_unsupported_host_without_toolchain_raises(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Without an installed toolchain the download is needed, and the host
|
||||
error names the ways out."""
|
||||
_fake_framework(tmp_path)
|
||||
with (
|
||||
patch.dict(os.environ, {"ESPHOME_ARDUINO8266_PREFIX": str(tmp_path)}),
|
||||
patch.object(framework, "get_systype", return_value="linux_armv7l"),
|
||||
patch.object(framework, "prefetch_packages"),
|
||||
patch.object(framework, "find_ninja", return_value=tmp_path / "ninja"),
|
||||
pytest.raises(EsphomeError, match=r"linux_armv7l.*toolchain: platformio"),
|
||||
):
|
||||
framework.check_and_install(cv.Version(3, 1, 2))
|
||||
|
||||
|
||||
def test_check_and_install_returns_paths(tmp_path: Path) -> None:
|
||||
with (
|
||||
patch.dict(os.environ, {"ESPHOME_ARDUINO8266_PREFIX": str(tmp_path)}),
|
||||
@@ -76,7 +161,10 @@ def test_check_and_install_returns_paths(tmp_path: Path) -> None:
|
||||
framework.ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS,
|
||||
tmp_path / "downloads",
|
||||
)
|
||||
assert fw_call.kwargs["expect"] == ("cores/esp8266", "tools/sdk", "libraries")
|
||||
assert fw_call.kwargs == {
|
||||
"expect": ("cores/esp8266", "tools/sdk", "libraries"),
|
||||
"resolve": None,
|
||||
}
|
||||
assert tc_call.args == (
|
||||
framework.TOOLCHAIN_PACKAGE,
|
||||
framework.TOOLCHAIN_VERSION,
|
||||
@@ -84,7 +172,10 @@ def test_check_and_install_returns_paths(tmp_path: Path) -> None:
|
||||
framework.ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS,
|
||||
tmp_path / "downloads",
|
||||
)
|
||||
assert tc_call.kwargs["expect"] == ("bin", "xtensa-lx106-elf")
|
||||
assert tc_call.kwargs == {
|
||||
"expect": ("bin", "xtensa-lx106-elf"),
|
||||
"resolve": framework.toolchain_download,
|
||||
}
|
||||
# The prefetch sees the same package specs as the installs
|
||||
assert mock_prefetch.call_args.args == (
|
||||
[
|
||||
@@ -102,6 +193,7 @@ def test_check_and_install_returns_paths(tmp_path: Path) -> None:
|
||||
),
|
||||
],
|
||||
tmp_path / "downloads",
|
||||
{framework.TOOLCHAIN_PACKAGE: framework.toolchain_download},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5999,6 +5999,26 @@ def test_compile_program_esp8266_runs_rosetta_check(tmp_path: Path) -> None:
|
||||
mock_check.assert_called_once()
|
||||
|
||||
|
||||
def test_compile_program_esp8266_native_skips_rosetta_check(tmp_path: Path) -> None:
|
||||
"""The native toolchain has an arm64 macOS build, so nothing is probed."""
|
||||
setup_core(platform=PLATFORM_ESP8266, tmp_path=tmp_path, name="test_device")
|
||||
CORE.toolchain = Toolchain.ARDUINO
|
||||
|
||||
config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}}
|
||||
|
||||
with (
|
||||
patch("esphome.components.esp8266.check_rosetta") as mock_check,
|
||||
patch(
|
||||
"esphome.components.esp8266.run_compile",
|
||||
side_effect=EsphomeError("compile reached"),
|
||||
),
|
||||
pytest.raises(EsphomeError, match="compile reached"),
|
||||
):
|
||||
compile_program(MockArgs(), config)
|
||||
|
||||
mock_check.assert_not_called()
|
||||
|
||||
|
||||
def test_compile_program_skips_rosetta_check_on_other_platforms(
|
||||
tmp_path: Path,
|
||||
mock_compile_build_info_run_compile: Mock,
|
||||
|
||||
@@ -311,6 +311,56 @@ def test_install_package_downloads_via_registry(tmp_path: Path) -> None:
|
||||
assert mock_download.call_args[1] == {"sha256": "abc123", "size": 42}
|
||||
|
||||
|
||||
def test_install_package_downloads_pinned(tmp_path: Path) -> None:
|
||||
"""A resolver's download is verified like a registry one, with no lookup."""
|
||||
dest = tmp_path / "pkg"
|
||||
pinned = registry.Download("http://y/pinned.tar.gz", "def456", 7)
|
||||
with (
|
||||
patch.object(registry, "download_with_resume") as mock_download,
|
||||
patch.object(registry, "archive_extract_all") as mock_extract,
|
||||
patch.object(registry, "registry_download") as mock_registry,
|
||||
):
|
||||
mock_extract.side_effect = lambda *_a, **_kw: (dest / "payload").mkdir(
|
||||
parents=True
|
||||
)
|
||||
registry.install_package(
|
||||
"pkg",
|
||||
"1.0.0",
|
||||
dest,
|
||||
[],
|
||||
tmp_path / "dl",
|
||||
expect=("payload",),
|
||||
resolve=lambda: pinned,
|
||||
)
|
||||
mock_registry.assert_not_called()
|
||||
assert mock_download.call_args[0][0] == "http://y/pinned.tar.gz"
|
||||
assert mock_download.call_args[1] == {"sha256": "def456", "size": 7}
|
||||
|
||||
|
||||
def test_install_package_mirror_wins_over_pinned(tmp_path: Path) -> None:
|
||||
"""A mirror override replaces the pinned download."""
|
||||
dest = tmp_path / "pkg"
|
||||
with (
|
||||
patch.object(registry, "download_from_mirrors") as mock_mirrors,
|
||||
patch.object(registry, "download_with_resume") as mock_download,
|
||||
patch.object(registry, "archive_extract_all") as mock_extract,
|
||||
):
|
||||
mock_extract.side_effect = lambda *_a, **_kw: (dest / "payload").mkdir(
|
||||
parents=True
|
||||
)
|
||||
registry.install_package(
|
||||
"pkg",
|
||||
"1.0.0",
|
||||
dest,
|
||||
["http://mirror/{VERSION}"],
|
||||
tmp_path / "dl",
|
||||
expect=("payload",),
|
||||
resolve=lambda: registry.Download("http://y/pinned.tar.gz", "def456", 7),
|
||||
)
|
||||
mock_mirrors.assert_called_once()
|
||||
mock_download.assert_not_called()
|
||||
|
||||
|
||||
def test_install_package_validates_expected_layout(tmp_path: Path) -> None:
|
||||
"""The success marker is only written when the extracted tree is usable."""
|
||||
dest = tmp_path / "pkg"
|
||||
@@ -535,6 +585,29 @@ def test_prefetch_packages_downloads_pending_in_parallel(tmp_path: Path) -> None
|
||||
assert callable(call[1]["progress"])
|
||||
|
||||
|
||||
def test_prefetch_packages_uses_pinned_download(tmp_path: Path) -> None:
|
||||
"""A package with a resolver skips the registry; the rest use it."""
|
||||
with (
|
||||
patch.object(registry, "download_with_resume") as mock_download,
|
||||
patch.object(
|
||||
registry, "registry_download", side_effect=_resolve_for({"a": 10})
|
||||
) as mock_registry,
|
||||
):
|
||||
registry.prefetch_packages(
|
||||
[
|
||||
("a", "1.0", tmp_path / "a", []),
|
||||
("b", "2.0", tmp_path / "b", []),
|
||||
],
|
||||
tmp_path / "dl",
|
||||
{"b": lambda: registry.Download("http://y/b.tar.gz", "def456", 20)},
|
||||
)
|
||||
mock_registry.assert_called_once_with("a", "1.0")
|
||||
calls = sorted(mock_download.call_args_list, key=lambda c: c[0][0])
|
||||
assert [c[0][0] for c in calls] == ["http://x/a.tar.gz", "http://y/b.tar.gz"]
|
||||
assert calls[1][1]["sha256"] == "def456"
|
||||
assert calls[1][1]["size"] == 20
|
||||
|
||||
|
||||
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."""
|
||||
@@ -620,13 +693,13 @@ def test_prefetch_packages_leaves_a_long_held_lock_to_its_holder(
|
||||
mock_download.assert_not_called()
|
||||
|
||||
|
||||
def test_already_installed_probe(tmp_path: Path) -> None:
|
||||
def test_is_installed_probe(tmp_path: Path) -> None:
|
||||
"""Both arms of the marker probe the prefetch worker keys on."""
|
||||
dest = tmp_path / "pkg"
|
||||
dest.mkdir()
|
||||
assert registry._already_installed(dest) is False
|
||||
assert registry.is_installed(dest) is False
|
||||
(dest / ".esphome_extracted").touch()
|
||||
assert registry._already_installed(dest) is True
|
||||
assert registry.is_installed(dest) is True
|
||||
|
||||
|
||||
def test_prefetch_packages_dedupes_duplicate_entries(tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user