154 lines
6.4 KiB
Go
154 lines
6.4 KiB
Go
package main
|
|
|
|
// The module's shape (novox/hq to-be 42 Phase 1, ADR 0207, research 027): it holds the
|
|
// node-package-manager seat and declares the package manager's own package; it owns pacman.conf
|
|
// whole — proven by pacman-conf on the rendered file, because a pacman.conf pacman cannot read is a
|
|
// machine that can neither install nor upgrade — and reflector's configuration, with the refresher
|
|
// and the cache cleaner on their timers.
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestItHoldsThePackageManagerSeatAndDeclaresItsPackage(t *testing.T) {
|
|
m := manifest(t)
|
|
if len(m.Claims) != 1 || m.Claims[0]["name"] != "node-package-manager" || m.Claims[0]["scope"] != "node" || m.Claims[0]["serves"] != nil {
|
|
t.Fatalf("claims: %v", m.Claims)
|
|
}
|
|
for id, pkg := range map[string]string{"package": "pacman", "contrib": "pacman-contrib", "reflector": "reflector"} {
|
|
if r := m.resource(t, id); r["package"] != pkg || r["absent"] != nil {
|
|
t.Errorf("%s: %v", id, r)
|
|
}
|
|
}
|
|
for id, unit := range map[string]string{"mirror-refresh": "reflector.timer", "cache-cleaning": "paccache.timer"} {
|
|
r := m.resource(t, id)
|
|
if r["unit"] != unit || r["state"] != "running" || r["boot"] != "enabled" {
|
|
t.Errorf("%s: %v", id, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPacmanConfIsWholeTheUnionOfRepositoriesAndTheImprovedOptions(t *testing.T) {
|
|
f := manifest(t).resource(t, "config")
|
|
content := templateText(t, f)
|
|
if f["path"] != "/etc/pacman.conf" || f["into"] != nil || !strings.HasPrefix(content, MeshHeader) {
|
|
t.Fatalf("%v", f)
|
|
}
|
|
var sections []string
|
|
for _, l := range strings.Split(content, "\n") {
|
|
if strings.HasPrefix(l, "[") {
|
|
sections = append(sections, l)
|
|
}
|
|
}
|
|
if strings.Join(sections, " ") != "[options] [core] [extra] [multilib]" {
|
|
t.Fatalf("sections: %v", sections)
|
|
}
|
|
for _, want := range []string{"\nColor\n", "\nCheckSpace\n", "\nVerbosePkgLists\n", "\nParallelDownloads = 5\n", "\nDownloadUser = alpm\n", "\nSigLevel = Required DatabaseOptional\n"} {
|
|
if !strings.Contains(content, want) {
|
|
t.Errorf("missing %q", strings.TrimSpace(want))
|
|
}
|
|
}
|
|
conf, err := exec.LookPath("pacman-conf")
|
|
if err != nil {
|
|
t.Skip("pacman-conf is not installed here; the rendered file is not proven")
|
|
}
|
|
file := filepath.Join(t.TempDir(), "pacman.conf")
|
|
if err := os.WriteFile(file, []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repos, err := exec.Command(conf, "--config", file, "--repo-list").CombinedOutput()
|
|
if err != nil || strings.Join(strings.Fields(string(repos)), " ") != "core extra multilib" {
|
|
t.Fatalf("pacman-conf --repo-list: %v\n%s", err, repos)
|
|
}
|
|
out, err := exec.Command(conf, "--config", file).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("pacman-conf refuses the file: %v\n%s", err, out)
|
|
}
|
|
c := ParseConf(string(out))
|
|
if c.Options["ParallelDownloads"][0] != "5" || c.Options["DownloadUser"] == nil || c.Options["Color"] == nil || c.Options["VerbosePkgLists"] == nil {
|
|
t.Fatalf("pacman-conf does not read the options as written: %v", c.Options)
|
|
}
|
|
}
|
|
|
|
func TestReflectorWritesTheListPacmanReads(t *testing.T) {
|
|
content := templateText(t, manifest(t).resource(t, "mirrors"))
|
|
opts := map[string]string{}
|
|
for _, l := range strings.Split(content, "\n") {
|
|
if l == "" || strings.HasPrefix(l, "#") {
|
|
continue
|
|
}
|
|
k, v, _ := strings.Cut(l, " ")
|
|
opts[k] = v
|
|
}
|
|
if opts["--save"] != Mirrorlist || opts["--protocol"] != "https" || opts["--latest"] != "20" || opts["--sort"] != "rate" || opts["--country"] == "" {
|
|
t.Fatalf("%v", opts)
|
|
}
|
|
if manifest(t).resource(t, "mirrors")["path"] != ReflectorConf {
|
|
t.Fatal("reflector reads its options from " + ReflectorConf)
|
|
}
|
|
}
|
|
|
|
func TestTheReflectorPackageIsDeclaredBeforeTheFileItShips(t *testing.T) {
|
|
order := map[string]int{}
|
|
for i, r := range manifest(t).Resources {
|
|
order[r["id"].(string)] = i
|
|
}
|
|
if order["reflector"] > order["mirrors"] || order["contrib"] > order["cache-cleaning"] || order["reflector"] > order["mirror-refresh"] {
|
|
t.Fatalf("a package's file and timer come after the package: %v", order)
|
|
}
|
|
}
|
|
|
|
// The weekly upgrade (issue 1574, ADR 0477 clause 5): a timer on Saturday 04:00 local time, persistent so a
|
|
// node that was off upgrades at its next start, the same moment on every node (no random delay), starting a
|
|
// oneshot service that runs the very upgrade pacman_upgrade runs.
|
|
func TestTheWeeklyUpgradeIsATimerOnSaturdayFourPersistentRunningTheToolsUpgrade(t *testing.T) {
|
|
m := manifest(t)
|
|
timer := m.resource(t, "upgrade-timer")
|
|
service := m.resource(t, "upgrade-service")
|
|
if timer["path"] != "/etc/systemd/system/mesh-pacman-weekly-upgrade.timer" || service["path"] != "/etc/systemd/system/"+WeeklyUnit {
|
|
t.Fatalf("paths: %v %v", timer["path"], service["path"])
|
|
}
|
|
tt, st := templateText(t, timer), templateText(t, service)
|
|
for _, want := range []string{"\nOnCalendar=Sat *-*-* 04:00:00\n", "\nPersistent=true\n", "\nWantedBy=timers.target\n"} {
|
|
if !strings.Contains(tt, want) {
|
|
t.Errorf("the timer lacks %q:\n%s", strings.TrimSpace(want), tt)
|
|
}
|
|
}
|
|
for _, not := range []string{"RandomizedDelaySec", "OnBootSec", "OnUnitActiveSec", "UTC"} {
|
|
if strings.Contains(tt, not) {
|
|
t.Errorf("the timer says %s: the same local moment on every node, and no other moment", not)
|
|
}
|
|
}
|
|
if strings.Count(tt, "OnCalendar=") != 1 {
|
|
t.Errorf("one moment only:\n%s", tt)
|
|
}
|
|
if !strings.Contains(st, "\nType=oneshot\n") {
|
|
t.Errorf("the service is a oneshot:\n%s", st)
|
|
}
|
|
if want := "\nExecStart=/usr/bin/pacman " + strings.Join(UpgradeArgs, " ") + "\n"; !strings.Contains(st, want) {
|
|
t.Errorf("the service does not run the upgrade pacman_upgrade runs (%q):\n%s", want, st)
|
|
}
|
|
if strings.Contains(st, "reboot") || strings.Contains(tt, "reboot") || strings.Contains(st, "systemctl") {
|
|
t.Error("the mesh restarts no machine for an upgrade")
|
|
}
|
|
r := m.resource(t, "weekly-upgrade")
|
|
if r["type"] != "service" || r["unit"] != "mesh-pacman-weekly-upgrade.timer" || r["state"] != "running" || r["boot"] != "enabled" {
|
|
t.Fatalf("the timer runs and is enabled: %v", r)
|
|
}
|
|
h, _ := r["health"].(map[string]any)
|
|
if h["kind"] != "tool" || h["tool"] != "pacman_upgrade_health" {
|
|
t.Errorf("the upgrade's outcome is the resource's health: %v", r["health"])
|
|
}
|
|
order := map[string]int{}
|
|
for i, x := range m.Resources {
|
|
order[x["id"].(string)] = i
|
|
}
|
|
if order["upgrade-service"] > order["weekly-upgrade"] || order["upgrade-timer"] > order["weekly-upgrade"] || order["package"] > order["upgrade-service"] {
|
|
t.Errorf("the units are placed before the timer is started: %v", order)
|
|
}
|
|
}
|