Files
devx/tests/unit/test_install_tools.py
T
emilandemo 3d4b4940ff
Post-merge / detect-and-configure (push) Successful in 16s
Post-merge / release-and-maintain (push) Successful in 1m2s
DEVX-153: feat: sync missing features from v0.49.x line to master
Co-authored-by: emil User <emil.simeonov@tutanota.com>
2026-08-09 01:09:20 +00:00

493 lines
20 KiB
Python

from __future__ import annotations
import platform
import urllib.request
from pathlib import Path
from unittest.mock import patch
import pytest
from click import ClickException
from click.testing import CliRunner
import devx.tools.install_tools as install_tools
class TestArch:
def test_amd64(self) -> None:
with patch.object(platform, "machine", return_value="x86_64"):
assert install_tools._arch() == "amd64"
def test_arm64(self) -> None:
with patch.object(platform, "machine", return_value="aarch64"):
assert install_tools._arch() == "arm64"
def test_unsupported(self) -> None:
with patch.object(platform, "machine", return_value="riscv64"):
with pytest.raises(ClickException):
install_tools._arch()
class TestIsInstalled:
def test_on_path(self) -> None:
with patch("shutil.which", return_value="/usr/bin/actionlint"):
assert install_tools._is_installed("actionlint") is True
def test_in_target_dir(self, tmp_path: Path) -> None:
with patch.object(install_tools, "TARGET_DIR", tmp_path):
(tmp_path / "actionlint").touch()
with patch("shutil.which", return_value=None):
assert install_tools._is_installed("actionlint") is True
def test_not_installed(self, tmp_path: Path) -> None:
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch("shutil.which", return_value=None):
assert install_tools._is_installed("actionlint") is False
class TestDownload:
def test_download(self, tmp_path: Path) -> None:
dest = tmp_path / "file.bin"
class _FakeResponse:
def __init__(self) -> None:
self._sent = False
def __enter__(self) -> _FakeResponse:
return self
def __exit__(self, *args: object) -> None:
pass
def read(self, n: int = -1) -> bytes:
if self._sent:
return b""
self._sent = True
return b"data"
with patch("urllib.request.urlopen", return_value=_FakeResponse()) as mock_urlopen:
install_tools._download("https://example.com/file", dest)
mock_urlopen.assert_called_once()
call_args = mock_urlopen.call_args
req = call_args.args[0]
assert isinstance(req, urllib.request.Request)
assert req.get_header("User-agent") == "devx/install-tools"
assert dest.read_bytes() == b"data"
class TestDownloadBinary:
def test_download(self, tmp_path: Path) -> None:
dest = tmp_path / "act_runner"
def _write_file(url: str, path: Path) -> tuple[str, None]:
Path(path).write_bytes(b"binary")
return str(path), None
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(install_tools, "_download", side_effect=_write_file):
result = install_tools._download_binary("https://example.com/act_runner", "act_runner")
assert result == dest
assert dest.exists()
assert dest.stat().st_mode & 0o111
class TestDownloadAndExtractTarball:
def test_extract(self, tmp_path: Path) -> None:
import tarfile
# Create a fake tarball with a binary
tarball_path = tmp_path / "archive.tar.gz"
binary_content = b"fake binary"
with tarfile.open(tarball_path, "w:gz") as tar:
import io
info = tarfile.TarInfo(name="actionlint")
info.size = len(binary_content)
tar.addfile(info, io.BytesIO(binary_content))
target_dir = tmp_path / "bin"
target_dir.mkdir()
with patch.object(install_tools, "TARGET_DIR", target_dir):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
result = install_tools._download_and_extract_tarball(
"https://example.com/actionlint.tar.gz", "actionlint"
)
assert result == target_dir / "actionlint"
assert result.exists()
assert result.read_bytes() == binary_content
def test_binary_not_found(self, tmp_path: Path) -> None:
import tarfile
tarball_path = tmp_path / "archive.tar.gz"
with tarfile.open(tarball_path, "w:gz") as tar:
import io
info = tarfile.TarInfo(name="other_file")
info.size = 0
tar.addfile(info, io.BytesIO(b""))
target_dir = tmp_path / "bin"
target_dir.mkdir()
with patch.object(install_tools, "TARGET_DIR", target_dir):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
with pytest.raises(ClickException, match="not found in archive"):
install_tools._download_and_extract_tarball("https://example.com/actionlint.tar.gz", "actionlint")
class TestInstallActionlint:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_actionlint() is True
def test_install(self, tmp_path: Path) -> None:
import tarfile
tarball_path = tmp_path / "archive.tar.gz"
binary_content = b"fake actionlint"
with tarfile.open(tarball_path, "w:gz") as tar:
import io
info = tarfile.TarInfo(name="actionlint")
info.size = len(binary_content)
tar.addfile(info, io.BytesIO(binary_content))
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
assert install_tools.install_actionlint() is True
assert (tmp_path / "actionlint").exists()
class TestInstallGitCliff:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_git_cliff() is True
def test_install(self, tmp_path: Path) -> None:
import tarfile
tarball_path = tmp_path / "archive.tar.gz"
binary_content = b"fake git-cliff"
with tarfile.open(tarball_path, "w:gz") as tar:
import io
info = tarfile.TarInfo(name="git-cliff")
info.size = len(binary_content)
tar.addfile(info, io.BytesIO(binary_content))
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
assert install_tools.install_git_cliff() is True
assert (tmp_path / "git-cliff").exists()
class TestInstallActRunner:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_act_runner() is True
def test_install(self, tmp_path: Path) -> None:
def _write_file(url: str, path: Path) -> tuple[str, None]:
Path(path).write_bytes(b"binary")
return str(path), None
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(install_tools, "_download", side_effect=_write_file):
assert install_tools.install_act_runner() is True
assert (tmp_path / "act_runner").exists()
class TestInstallTea:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_tea() is True
def test_install(self, tmp_path: Path) -> None:
def _write_file(url: str, path: Path) -> tuple[str, None]:
Path(path).write_bytes(b"binary")
return str(path), None
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(install_tools, "_download", side_effect=_write_file):
assert install_tools.install_tea() is True
assert (tmp_path / "tea").exists()
def test_install_fallback_to_second_url(self, tmp_path: Path) -> None:
"""First URL fails (403), second URL succeeds."""
call_count = [0]
def _download_side_effect(url: str, dest: Path) -> None:
call_count[0] += 1
if call_count[0] == 1:
raise OSError("HTTP Error 403: Forbidden")
Path(dest).write_bytes(b"binary")
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(install_tools, "_download", side_effect=_download_side_effect):
assert install_tools.install_tea() is True
assert (tmp_path / "tea").exists()
assert call_count[0] == 2
def test_install_all_urls_fail(self, tmp_path: Path) -> None:
"""All URLs fail — should raise ClickException."""
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(install_tools, "_download", side_effect=OSError("403 Forbidden")):
with pytest.raises(ClickException, match="Failed to download tea"):
install_tools.install_tea()
class TestInstallHadolint:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_hadolint() is True
def test_install(self, tmp_path: Path) -> None:
def _write_file(url: str, path: Path) -> tuple[str, None]:
Path(path).write_bytes(b"binary")
return str(path), None
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(install_tools, "_download", side_effect=_write_file):
assert install_tools.install_hadolint() is True
assert (tmp_path / "hadolint").exists()
class TestInstallTofu:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_tofu() is True
def test_install(self, tmp_path: Path) -> None:
import io
import tarfile
tarball_path = tmp_path / "archive.tar.gz"
binary_content = b"fake tofu"
with tarfile.open(tarball_path, "w:gz") as tar:
info = tarfile.TarInfo(name="tofu")
info.size = len(binary_content)
tar.addfile(info, io.BytesIO(binary_content))
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(platform, "system", return_value="Linux"):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
assert install_tools.install_tofu() is True
assert (tmp_path / "tofu").exists()
class TestInstallVale:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_vale() is True
def test_install(self, tmp_path: Path) -> None:
import io
import tarfile
tarball_path = tmp_path / "archive.tar.gz"
binary_content = b"fake vale"
with tarfile.open(tarball_path, "w:gz") as tar:
info = tarfile.TarInfo(name="vale")
info.size = len(binary_content)
tar.addfile(info, io.BytesIO(binary_content))
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(platform, "machine", return_value="x86_64"):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
assert install_tools.install_vale() is True
assert (tmp_path / "vale").exists()
class TestInstallPromtool:
def test_already_installed(self) -> None:
with patch.object(install_tools, "_is_installed", return_value=True):
assert install_tools.install_promtool() is True
def test_install(self, tmp_path: Path) -> None:
import io
import tarfile
tarball_path = tmp_path / "archive.tar.gz"
binary_content = b"fake promtool"
with tarfile.open(tarball_path, "w:gz") as tar:
info = tarfile.TarInfo(name="promtool")
info.size = len(binary_content)
tar.addfile(info, io.BytesIO(binary_content))
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch.object(install_tools, "_arch", return_value="amd64"):
with patch.object(
install_tools,
"_download",
side_effect=lambda url, dest: Path(dest).write_bytes(tarball_path.read_bytes()),
):
assert install_tools.install_promtool() is True
assert (tmp_path / "promtool").exists()
def test_url_contains_version(self, tmp_path: Path) -> None:
"""Verify the download URL includes the correct promtool version."""
captured_url = []
def fake_extract(url: str, binary_name: str) -> Path:
captured_url.append(url)
return tmp_path / binary_name
with patch.object(install_tools, "_is_installed", return_value=False):
with patch.object(install_tools, "_download_and_extract_tarball", side_effect=fake_extract):
install_tools.install_promtool()
assert any(f"v{install_tools.PROMTOOL_VERSION}" in url for url in captured_url)
class TestListTools:
def test_list(self, tmp_path: Path) -> None:
with patch.object(install_tools, "TARGET_DIR", tmp_path):
with patch("shutil.which", return_value=None):
with patch.object(install_tools, "TOOL_NAMES", ["actionlint", "git-cliff", "act_runner"]):
install_tools.list_tools()
class TestInstallTool:
def test_actionlint(self) -> None:
with patch.object(install_tools, "install_actionlint", return_value=True) as mock:
assert install_tools._install_tool("actionlint") is True
mock.assert_called_once()
def test_git_cliff(self) -> None:
with patch.object(install_tools, "install_git_cliff", return_value=True) as mock:
assert install_tools._install_tool("git-cliff") is True
mock.assert_called_once()
def test_act_runner(self) -> None:
with patch.object(install_tools, "install_act_runner", return_value=True) as mock:
assert install_tools._install_tool("act_runner") is True
mock.assert_called_once()
def test_tea(self) -> None:
with patch.object(install_tools, "install_tea", return_value=True) as mock:
assert install_tools._install_tool("tea") is True
mock.assert_called_once()
def test_hadolint(self) -> None:
with patch.object(install_tools, "install_hadolint", return_value=True) as mock:
assert install_tools._install_tool("hadolint") is True
mock.assert_called_once()
def test_tofu(self) -> None:
with patch.object(install_tools, "install_tofu", return_value=True) as mock:
assert install_tools._install_tool("tofu") is True
mock.assert_called_once()
def test_vale(self) -> None:
with patch.object(install_tools, "install_vale", return_value=True) as mock:
assert install_tools._install_tool("vale") is True
mock.assert_called_once()
def test_promtool(self) -> None:
with patch.object(install_tools, "install_promtool", return_value=True) as mock:
assert install_tools._install_tool("promtool") is True
mock.assert_called_once()
def test_unknown_tool(self) -> None:
with pytest.raises(ClickException, match="Unknown tool"):
install_tools._install_tool("unknown")
class TestMain:
def test_list_status(self) -> None:
runner = CliRunner()
with patch.object(install_tools, "_is_installed", return_value=True):
result = runner.invoke(install_tools.main, ["--list"])
assert result.exit_code == 0
assert "actionlint" in result.output
def test_install_all(self) -> None:
runner = CliRunner()
with patch.object(install_tools, "_install_tool", return_value=True) as mock_install:
result = runner.invoke(install_tools.main, [])
assert result.exit_code == 0
assert mock_install.call_count == 8
def test_install_specific_tool(self) -> None:
runner = CliRunner()
with patch.object(install_tools, "_install_tool", return_value=True) as mock_install:
result = runner.invoke(install_tools.main, ["--tool", "actionlint"])
assert result.exit_code == 0
mock_install.assert_called_once_with("actionlint")
def test_install_multiple_specific_tools(self) -> None:
runner = CliRunner()
with patch.object(install_tools, "_install_tool", return_value=True) as mock_install:
result = runner.invoke(install_tools.main, ["--tool", "git-cliff", "--tool", "tea"])
assert result.exit_code == 0
assert mock_install.call_count == 2
def test_install_failure(self) -> None:
runner = CliRunner()
with patch.object(install_tools, "_install_tool", side_effect=Exception("network error")):
result = runner.invoke(install_tools.main, ["--tool", "actionlint"])
assert result.exit_code != 0
def test_path_reminder(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""When TARGET_DIR is not in PATH, a reminder is printed."""
monkeypatch.setenv("PATH", "/usr/bin:/bin")
runner = CliRunner()
with patch.object(install_tools, "_install_tool", return_value=True):
result = runner.invoke(install_tools.main, [])
assert result.exit_code == 0
assert "Add" in result.output
assert "PATH" in result.output
def test_no_path_reminder_when_in_path(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""When TARGET_DIR is in PATH, no reminder is printed."""
target_dir = str(install_tools.TARGET_DIR)
monkeypatch.setenv("PATH", f"/usr/bin:{target_dir}:/bin")
runner = CliRunner()
with patch.object(install_tools, "_install_tool", return_value=True):
result = runner.invoke(install_tools.main, [])
assert result.exit_code == 0
assert "Add" not in result.output