mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 11:07:33 +00:00
229 lines
8.5 KiB
Python
229 lines
8.5 KiB
Python
"""Tests for esphome.components.nrf52.framework helpers."""
|
|
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from esphome.components.nrf52.framework import (
|
|
_TOOLCHAIN_VERSION,
|
|
_get_toolchain_platform_info,
|
|
check_and_install,
|
|
)
|
|
from esphome.config_validation import Version
|
|
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
|
|
from esphome.core import CORE, EsphomeError
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("system", "machine", "expected"),
|
|
[
|
|
# default — no branch hit
|
|
("Linux", "x86_64", ("linux", "x86_64", "tar.xz")),
|
|
# arm64 → aarch64 rename
|
|
("Linux", "arm64", ("linux", "aarch64", "tar.xz")),
|
|
# darwin → macos rename only
|
|
("Darwin", "x86_64", ("macos", "x86_64", "tar.xz")),
|
|
# both renames apply
|
|
("Darwin", "arm64", ("macos", "aarch64", "tar.xz")),
|
|
# windows forces x86_64 + 7z; arm64 rename is overwritten
|
|
("Windows", "arm64", ("windows", "x86_64", "7z")),
|
|
],
|
|
)
|
|
def test_get_toolchain_platform_info(
|
|
system: str, machine: str, expected: tuple[str, str, str]
|
|
) -> None:
|
|
with (
|
|
patch("platform.system", return_value=system),
|
|
patch("platform.machine", return_value=machine),
|
|
):
|
|
assert _get_toolchain_platform_info() == expected
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers and fixtures for check_and_install tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_TEST_SDK_VERSION = "2.9.0"
|
|
|
|
|
|
@pytest.fixture
|
|
def nrf52_dirs(setup_core: Path) -> SimpleNamespace:
|
|
"""Populate CORE and pre-create SDK directories so sentinel.touch() succeeds."""
|
|
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: Version.parse(_TEST_SDK_VERSION)}
|
|
tools = CORE.data_dir / "sdk-nrf"
|
|
python_env = tools / "penvs" / f"v{_TEST_SDK_VERSION}"
|
|
framework = tools / "frameworks" / f"v{_TEST_SDK_VERSION}"
|
|
toolchain_dir = tools / "toolchains" / _TOOLCHAIN_VERSION
|
|
for d in (python_env, framework, toolchain_dir):
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
zephyr_scripts = framework / "zephyr" / "scripts"
|
|
zephyr_scripts.mkdir(parents=True, exist_ok=True)
|
|
(zephyr_scripts / "requirements.txt").touch()
|
|
return SimpleNamespace(
|
|
python_env=python_env,
|
|
framework=framework,
|
|
toolchain=toolchain_dir,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_nrf52_ops():
|
|
"""Patch all heavy I/O operations used by check_and_install."""
|
|
with (
|
|
patch("esphome.components.nrf52.framework.rmdir") as mock_rmdir,
|
|
patch("esphome.components.nrf52.framework.create_venv") as mock_create_venv,
|
|
patch(
|
|
"esphome.components.nrf52.framework.run_command_ok", return_value=True
|
|
) as mock_run_cmd,
|
|
patch(
|
|
"esphome.components.nrf52.framework.download_from_mirrors",
|
|
return_value="https://example.com/tc.tar.xz",
|
|
) as mock_download,
|
|
patch("esphome.components.nrf52.framework.archive_extract_all") as mock_extract,
|
|
):
|
|
yield SimpleNamespace(
|
|
rmdir=mock_rmdir,
|
|
create_venv=mock_create_venv,
|
|
run_command_ok=mock_run_cmd,
|
|
download_from_mirrors=mock_download,
|
|
archive_extract_all=mock_extract,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# check_and_install tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCheckAndInstall:
|
|
def test_all_installed_skips_all_steps(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""All three sentinels present → nothing downloaded or compiled."""
|
|
(nrf52_dirs.python_env / ".ready").touch()
|
|
(nrf52_dirs.python_env / ".zephyr_reqs_ready").touch()
|
|
(nrf52_dirs.framework / ".ready").touch()
|
|
(nrf52_dirs.toolchain / ".ready").touch()
|
|
|
|
check_and_install()
|
|
|
|
mock_nrf52_ops.create_venv.assert_not_called()
|
|
mock_nrf52_ops.run_command_ok.assert_not_called()
|
|
mock_nrf52_ops.download_from_mirrors.assert_not_called()
|
|
mock_nrf52_ops.archive_extract_all.assert_not_called()
|
|
|
|
def test_fresh_install_runs_all_steps(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""No sentinels → venv created, west installed, SDK init+update, toolchain downloaded."""
|
|
check_and_install()
|
|
|
|
mock_nrf52_ops.create_venv.assert_called_once()
|
|
# pip install requirements, west init, west update, pip install zephyr reqs
|
|
assert mock_nrf52_ops.run_command_ok.call_count == 4
|
|
# minimal SDK + per-arch toolchain
|
|
assert mock_nrf52_ops.download_from_mirrors.call_count == 2
|
|
assert mock_nrf52_ops.archive_extract_all.call_count == 2
|
|
assert (nrf52_dirs.python_env / ".ready").exists()
|
|
assert (nrf52_dirs.python_env / ".zephyr_reqs_ready").exists()
|
|
assert (nrf52_dirs.framework / ".ready").exists()
|
|
assert (nrf52_dirs.toolchain / ".ready").exists()
|
|
|
|
def test_venv_exists_installs_framework_and_toolchain(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""Venv ready but framework missing → skip venv creation, run SDK init+update."""
|
|
(nrf52_dirs.python_env / ".ready").touch()
|
|
|
|
check_and_install()
|
|
|
|
mock_nrf52_ops.create_venv.assert_not_called()
|
|
# west init, west update, pip install zephyr reqs
|
|
assert mock_nrf52_ops.run_command_ok.call_count == 3
|
|
# minimal SDK + per-arch toolchain
|
|
assert mock_nrf52_ops.download_from_mirrors.call_count == 2
|
|
|
|
def test_toolchain_only_missing(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""Venv and framework ready → only toolchain downloaded and extracted."""
|
|
(nrf52_dirs.python_env / ".ready").touch()
|
|
(nrf52_dirs.python_env / ".zephyr_reqs_ready").touch()
|
|
(nrf52_dirs.framework / ".ready").touch()
|
|
|
|
check_and_install()
|
|
|
|
mock_nrf52_ops.create_venv.assert_not_called()
|
|
mock_nrf52_ops.run_command_ok.assert_not_called()
|
|
# minimal SDK + per-arch toolchain
|
|
assert mock_nrf52_ops.download_from_mirrors.call_count == 2
|
|
assert mock_nrf52_ops.archive_extract_all.call_count == 2
|
|
|
|
def test_requirements_install_failure_raises(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""Failing pip install -r requirements.txt raises EsphomeError."""
|
|
mock_nrf52_ops.run_command_ok.return_value = False
|
|
|
|
with pytest.raises(EsphomeError, match="Install requirements"):
|
|
check_and_install()
|
|
|
|
def test_framework_init_failure_raises(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""Failing west init raises EsphomeError."""
|
|
(nrf52_dirs.python_env / ".ready").touch()
|
|
mock_nrf52_ops.run_command_ok.return_value = False
|
|
|
|
with pytest.raises(EsphomeError, match="Can't initialize"):
|
|
check_and_install()
|
|
|
|
def test_framework_update_failure_raises(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""Failing west update raises EsphomeError."""
|
|
(nrf52_dirs.python_env / ".ready").touch()
|
|
# init succeeds, update fails
|
|
mock_nrf52_ops.run_command_ok.side_effect = [True, False]
|
|
|
|
with pytest.raises(EsphomeError, match="Can't update"):
|
|
check_and_install()
|
|
|
|
def test_toolchain_download_passes_platform_substitutions(
|
|
self,
|
|
nrf52_dirs: SimpleNamespace,
|
|
mock_nrf52_ops: SimpleNamespace,
|
|
) -> None:
|
|
"""download_from_mirrors receives VERSION + platform triple from _get_toolchain_platform_info."""
|
|
(nrf52_dirs.python_env / ".ready").touch()
|
|
(nrf52_dirs.framework / ".ready").touch()
|
|
|
|
with patch(
|
|
"esphome.components.nrf52.framework._get_toolchain_platform_info",
|
|
return_value=("linux", "x86_64", "tar.xz"),
|
|
):
|
|
check_and_install()
|
|
|
|
args, _ = mock_nrf52_ops.download_from_mirrors.call_args
|
|
substitutions = args[1]
|
|
assert substitutions["VERSION"] == _TOOLCHAIN_VERSION
|
|
assert substitutions["sysname"] == "linux"
|
|
assert substitutions["machine"] == "x86_64"
|
|
assert substitutions["extension"] == "tar.xz"
|