190 lines
5.5 KiB
Python
190 lines
5.5 KiB
Python
"""Tests for the installer: pure planning logic and the run_step executor."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from sjaur.installer import (
|
|
InstallStep,
|
|
check_environment,
|
|
plan_install,
|
|
run_step,
|
|
)
|
|
from sjaur.errors import SjaurError
|
|
|
|
|
|
# -- plan_install (pure logic) ---------------------------------------------------
|
|
|
|
|
|
def test_plan_install_without_existing_clone(tmp_path):
|
|
steps = plan_install("yay", tmp_path)
|
|
assert steps == [
|
|
InstallStep("clone", "yay"),
|
|
InstallStep("makepkg", "yay"),
|
|
]
|
|
|
|
|
|
def test_plan_install_reuses_existing_clone(tmp_path):
|
|
(tmp_path / "yay").mkdir()
|
|
steps = plan_install("yay", tmp_path)
|
|
assert steps == [
|
|
InstallStep("reuse", "yay"),
|
|
InstallStep("makepkg", "yay"),
|
|
]
|
|
|
|
|
|
def test_plan_install_refresh_updates_existing_clone(tmp_path):
|
|
(tmp_path / "yay").mkdir()
|
|
steps = plan_install("yay", tmp_path, refresh=True)
|
|
assert steps == [
|
|
InstallStep("update", "yay"),
|
|
InstallStep("makepkg", "yay"),
|
|
]
|
|
|
|
|
|
def test_plan_install_refresh_clones_when_missing(tmp_path):
|
|
steps = plan_install("yay", tmp_path, refresh=True)
|
|
assert [step.kind for step in steps] == ["clone", "makepkg"]
|
|
|
|
|
|
def test_plan_install_repo_package_uses_pacman(tmp_path):
|
|
for repository in ("core", "extra", "multilib"):
|
|
steps = plan_install("firefox", tmp_path, repository=repository)
|
|
assert steps == [InstallStep("pacman", "firefox")]
|
|
|
|
|
|
def test_run_step_pacman_uses_sudo_for_non_root(tmp_path, monkeypatch):
|
|
import sjaur.installer as installer
|
|
|
|
recorded = {}
|
|
|
|
def fake_run(cmd, cwd=None, check=False):
|
|
recorded["cmd"] = cmd
|
|
|
|
class Result:
|
|
returncode = 0
|
|
|
|
return Result()
|
|
|
|
monkeypatch.setattr(installer.subprocess, "run", fake_run)
|
|
monkeypatch.setattr(installer.os, "geteuid", lambda: 1000)
|
|
|
|
run_step(InstallStep("pacman", "firefox"), tmp_path)
|
|
|
|
assert recorded["cmd"] == ["sudo", "pacman", "-S", "--needed", "firefox"]
|
|
|
|
|
|
def test_run_step_pacman_as_root_needs_no_sudo(tmp_path, monkeypatch):
|
|
import sjaur.installer as installer
|
|
|
|
recorded = {}
|
|
|
|
def fake_run(cmd, cwd=None, check=False):
|
|
recorded["cmd"] = cmd
|
|
|
|
class Result:
|
|
returncode = 0
|
|
|
|
return Result()
|
|
|
|
monkeypatch.setattr(installer.subprocess, "run", fake_run)
|
|
monkeypatch.setattr(installer.os, "geteuid", lambda: 0)
|
|
|
|
run_step(InstallStep("pacman", "firefox"), tmp_path)
|
|
|
|
assert recorded["cmd"] == ["pacman", "-S", "--needed", "firefox"]
|
|
|
|
|
|
def test_check_environment_repo_packages_only_need_pacman(monkeypatch):
|
|
import sjaur.installer as installer
|
|
|
|
monkeypatch.setattr(
|
|
installer.shutil,
|
|
"which",
|
|
lambda name: None if name in ("git", "makepkg") else "/usr/bin/pacman",
|
|
)
|
|
assert check_environment(include_build_tools=False) == []
|
|
assert len(check_environment(include_build_tools=True)) == 2
|
|
|
|
|
|
# -- run_step (subprocess execution) ----------------------------------------------
|
|
|
|
|
|
def test_run_step_clone_creates_directory(tmp_path, monkeypatch):
|
|
import subprocess
|
|
|
|
import sjaur.installer as installer
|
|
|
|
# Point the AUR URL template at a local bare repository: no network.
|
|
source = tmp_path / "aur-mock"
|
|
source.mkdir()
|
|
subprocess.run(["git", "init", "-q", "--bare", str(source / "yay.git")], check=True)
|
|
monkeypatch.setattr(installer, "AUR_GIT_URL", str(source / "{name}.git"))
|
|
|
|
target_root = tmp_path / "builds"
|
|
target_root.mkdir()
|
|
run_step(InstallStep("clone", "yay"), target_root)
|
|
|
|
assert (target_root / "yay" / ".git").is_dir()
|
|
|
|
|
|
def test_run_step_reuse_is_a_noop(tmp_path):
|
|
(tmp_path / "whatever").mkdir()
|
|
run_step(InstallStep("reuse", "whatever"), tmp_path) # must not raise
|
|
|
|
|
|
def test_run_step_makepkg_without_pkgbuild_raises(tmp_path):
|
|
(tmp_path / "pkg").mkdir()
|
|
with pytest.raises(SjaurError) as excinfo:
|
|
run_step(InstallStep("makepkg", "pkg"), tmp_path)
|
|
assert "PKGBUILD" in str(excinfo.value)
|
|
|
|
|
|
def test_run_step_makepkg_failure_raises(tmp_path):
|
|
pkg = tmp_path / "pkg"
|
|
pkg.mkdir()
|
|
(pkg / "PKGBUILD").write_text("pkgname=pkg\n")
|
|
with pytest.raises(SjaurError) as excinfo:
|
|
run_step(InstallStep("makepkg", "pkg"), tmp_path)
|
|
assert "makepkg" in str(excinfo.value)
|
|
|
|
|
|
def test_run_step_unknown_kind_raises(tmp_path):
|
|
with pytest.raises(SjaurError):
|
|
run_step(InstallStep("explode", "pkg"), tmp_path)
|
|
|
|
|
|
# -- check_environment -------------------------------------------------------------
|
|
|
|
|
|
def test_check_environment_finds_basic_tools():
|
|
# git/makepkg/pacman exist in CI-like Linux environments; if any is
|
|
# missing the message names the tool.
|
|
problems = check_environment()
|
|
assert isinstance(problems, list)
|
|
for problem in problems:
|
|
assert "not found in PATH" in problem
|
|
|
|
|
|
def test_check_environment_reports_missing_tool(monkeypatch):
|
|
import sjaur.installer as installer
|
|
|
|
monkeypatch.setattr(installer.shutil, "which", lambda name: None)
|
|
problems = check_environment()
|
|
assert len(problems) == 3
|
|
assert any("git" in problem for problem in problems)
|
|
|
|
|
|
def test_missing_binary_in_run_step_raises(tmp_path, monkeypatch):
|
|
import sjaur.installer as installer
|
|
|
|
def raise_missing(*args, **kwargs):
|
|
raise FileNotFoundError("git")
|
|
|
|
monkeypatch.setattr(installer.subprocess, "run", raise_missing)
|
|
with pytest.raises(SjaurError) as excinfo:
|
|
run_step(InstallStep("clone", "x"), tmp_path)
|
|
assert "git" in str(excinfo.value)
|