Files
mesh-catalog/modules/pacman/cmd/pacman-tools/manifest_test.go
T
jschoubben 2b495c4818 pacman keeps one mirror list for the mesh, ranked in the write that opens each round and written by every node (ADR 0585, issue 1876)
Each node's reflector ranked its own list on its own timer, and on 2026-10-31 one node's
lagging first mirror left it a Node.js version behind the rest. The list is now the
module's state mirrors, key list: the node that opens a week's round ranks it with the
module's reflector options, keeps only mirrors whose last sync is no older than the
newest found, and the round's key names the list's revision. Every keeper writes
/etc/pacman.d/mirrorlist whole from the key through the root helper's new verb
mirrorlist, when the key changes and when the file differs, and never writes an empty
or unreadable list. reflector.timer is declared stopped and disabled, and reflector.conf
saves nothing. pacman_mirrors answers the shared list, its ranker and age and whether
this node's file matches; with refresh it asks this node's keeper to rank again,
refused while a round runs, naming it. The round's integration test proves the opening
write and both nodes' files.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 16:18:22 +01:00

136 lines
5.7 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)
}
}
// ADR 0585 §3: reflector's weekly timer is declared stopped and disabled, the mesh's list ranked in the round instead.
for id, unit := range map[string][3]string{"mirror-refresh": {"reflector.timer", "stopped", "disabled"},
"cache-cleaning": {"paccache.timer", "running", "enabled"}} {
r := m.resource(t, id)
if r["unit"] != unit[0] || r["state"] != unit[1] || r["boot"] != unit[2] {
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(provenConf(t, content, filepath.Dir(file))), 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)
}
}
// Every repository of the shipped file reads the mirror list reflector writes, and the file pacman-conf proves reads
// nothing of the machine: the unit level runs shut off (novox/hq ADR 0448), where /etc/pacman.d is not there
// (issue 1891).
func TestTheProvenConfReadsNothingOfTheMachine(t *testing.T) {
content := templateText(t, manifest(t).resource(t, "config"))
if n := strings.Count(content, "Include = "+Mirrorlist+"\n"); n != 3 || strings.Count(content, "Include") != n {
t.Fatalf("every repository reads %s, and nothing else is included: %d of 3\n%s", Mirrorlist, n, content)
}
dir := t.TempDir()
proven := provenConf(t, content, dir)
if strings.Contains(proven, "/etc/") {
t.Fatalf("the proven file still names the machine's /etc:\n%s", proven)
}
if _, err := os.Stat(filepath.Join(dir, "mirrorlist")); err != nil {
t.Fatalf("the proven file's mirror list is not in its directory: %v", err)
}
}
// provenConf is the shipped pacman.conf with its repositories reading a mirror list of its own in dir, made there,
// so pacman-conf proves the file the module ships and reads nothing of the machine.
func provenConf(t *testing.T, content, dir string) string {
t.Helper()
list := filepath.Join(dir, "mirrorlist")
if err := os.WriteFile(list, []byte("Server = https://mirror.example/$repo/os/$arch\n"), 0o644); err != nil {
t.Fatal(err)
}
return strings.ReplaceAll(content, "Include = "+Mirrorlist, "Include = "+list)
}
func TestReflectorRanksWithTheModulesOptionsAndSavesNoList(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
}
// ADR 0585 §3: nothing saves a list but the keeper, from the mesh's.
if _, saves := opts["--save"]; saves || 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)
}
}