122 lines
5.6 KiB
Go
122 lines
5.6 KiB
Go
package main
|
|
|
|
// The after-upgrade check, its first part (novox/hq ADR 0508 §4; acceptance 1841/AC5): .pacnew and .pacsave
|
|
// files from the transaction's log lines, the news since the previous upgrade, units newly failed against the
|
|
// record, and a restart needed.
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const checkLog = "[2026-10-04T12:00:01+0000] [PACMAN] Running 'pacman -Syu --noconfirm'\n" +
|
|
"[2026-10-04T12:00:02+0000] [PACMAN] starting full system upgrade\n" +
|
|
"[2026-10-04T12:00:09+0000] [ALPM] transaction started\n" +
|
|
"[2026-10-04T12:00:10+0000] [ALPM] upgraded pacman-mirrorlist (20260901-1 -> 20261001-1)\n" +
|
|
"[2026-10-04T12:00:10+0000] [ALPM] warning: /etc/pacman.d/mirrorlist installed as /etc/pacman.d/mirrorlist.pacnew\n" +
|
|
"[2026-10-04T12:00:10+0000] [ALPM] warning: /etc/lemurs/config.toml saved as /etc/lemurs/config.toml.pacsave\n" +
|
|
"[2026-10-04T12:00:10+0000] [ALPM] removed lemurs-git (0.4.0.r17.g7151336-2)\n" +
|
|
"[2026-10-04T12:00:11+0000] [ALPM] transaction completed\n" +
|
|
"[2026-10-04T12:05:00+0000] [PACMAN] Running 'pacman -S --noconfirm made-up'\n" +
|
|
"[2026-10-04T12:05:01+0000] [ALPM] transaction started\n" +
|
|
"[2026-10-04T12:05:01+0000] [ALPM] warning: /etc/made-up.conf installed as /etc/made-up.conf.pacnew\n" +
|
|
"[2026-10-04T12:05:02+0000] [ALPM] transaction completed\n"
|
|
|
|
const newsFeed = `<?xml version="1.0"?><rss><channel>
|
|
<item><title>A manual intervention</title><link>https://example.org/news/2</link><pubDate>Fri, 02 Oct 2026 10:00:00 +0000</pubDate><description>x</description></item>
|
|
<item><title>An older post</title><link>https://example.org/news/1</link><pubDate>Mon, 21 Sep 2026 10:00:00 +0000</pubDate><description>y</description></item>
|
|
</channel></rss>`
|
|
|
|
// checkMachine is a machine after an upgrade: these units failed now, this kernel running, and the linux
|
|
// package now installing linuxNow.
|
|
func checkMachine(failed, linuxNow string) *Machine {
|
|
m := machine(byLine(map[string]Ran{
|
|
"systemctl list-units --state=failed --plain --no-legend --no-pager": {Stdout: failed},
|
|
"pacman -Qq": {Stdout: "bash\nlinux\nlinux-firmware\nlinux-lts\n"},
|
|
"dkms status": {Status: 127, Err: "ENOENT"},
|
|
"pacman -Qlq -- linux": {Stdout: "/usr/lib/modules/" + linuxNow + "/vmlinuz\n"},
|
|
"pacman -Qlq -- linux-firmware": {Stdout: "/usr/lib/firmware/\n"},
|
|
"pacman -Qlq -- linux-lts": {Stdout: "/usr/lib/modules/6.18.54-2-lts/vmlinuz\n"},
|
|
}, nil), 1000)
|
|
m.ReadFile = func(path string) ([]byte, error) {
|
|
switch path {
|
|
case OSRelease:
|
|
return []byte("7.2.8-arch1-2\n"), nil
|
|
case PacmanLog:
|
|
return []byte(checkLog), nil
|
|
}
|
|
return nil, errNoFile
|
|
}
|
|
return m
|
|
}
|
|
|
|
func checkedRun(steps ...string) Run {
|
|
r := Run{Node: "node-a", Run: "20261004T120000Z", Kind: RunUpgrade, From: FromAsk}
|
|
at := time.Date(2026, 10, 4, 12, 0, 0, 0, time.UTC)
|
|
for _, s := range steps {
|
|
r.Steps = append(r.Steps, Step{Step: s, At: at})
|
|
}
|
|
return r
|
|
}
|
|
|
|
func checkUpgrades(t *testing.T, m *Machine) *Upgrades {
|
|
runs := newBucket()
|
|
previous := Run{Node: "node-a", Run: "20260926T040000Z", Kind: RunUpgrade, From: FromRound,
|
|
Steps: []Step{{Step: StepStarted, At: time.Date(2026, 9, 26, 4, 0, 0, 0, time.UTC)}}}
|
|
if _, err := runs.Create(NodeKey("node-a", previous.Run), previous); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
saved := fetch
|
|
fetch = func(string) ([]byte, error) { return []byte(newsFeed), nil }
|
|
t.Cleanup(func() { fetch = saved })
|
|
return &Upgrades{Node: "node-a", Machine: m, Runs: runs}
|
|
}
|
|
|
|
func TestTheCheckNamesTheFilesTheTransactionLeftAndTheNewsSinceThePreviousUpgrade(t *testing.T) {
|
|
u := checkUpgrades(t, checkMachine("", "7.2.8-arch1-2"))
|
|
c := u.AfterCheck(checkedRun(StepStarted, StepFinished), &Record{})
|
|
if strings.Join(c.Pacnew, " ") != "/etc/pacman.d/mirrorlist.pacnew" || strings.Join(c.Pacsave, " ") != "/etc/lemurs/config.toml.pacsave" {
|
|
t.Errorf("only this transaction's files: %v %v", c.Pacnew, c.Pacsave)
|
|
}
|
|
if len(c.News) != 1 || c.News[0].Title != "A manual intervention" || c.NewsError != "" {
|
|
t.Errorf("the news since the previous upgrade (26 September): %+v %q", c.News, c.NewsError)
|
|
}
|
|
if !c.Passed || c.RestartNeeded || c.RunningKernel != "7.2.8-arch1-2" || c.InstalledKernel != "7.2.8-arch1-2" {
|
|
t.Errorf("%+v", c)
|
|
}
|
|
}
|
|
|
|
func TestNewsThatCannotBeReadIsSaidNotShownAsNone(t *testing.T) {
|
|
u := checkUpgrades(t, checkMachine("", "7.2.8-arch1-2"))
|
|
fetch = func(string) ([]byte, error) { return nil, errors.New("no route to host") }
|
|
c := u.AfterCheck(checkedRun(StepStarted, StepFinished), &Record{})
|
|
if !strings.Contains(c.NewsError, "no route to host") || !c.Passed {
|
|
t.Errorf("%+v", c)
|
|
}
|
|
}
|
|
|
|
func TestAUnitNewlyFailedFailsTheCheckAndOneFailedBeforeDoesNot(t *testing.T) {
|
|
u := checkUpgrades(t, checkMachine("backup.service loaded failed failed The backup\nweb.service loaded failed failed The web\n", "7.2.8-arch1-2"))
|
|
c := u.AfterCheck(checkedRun(StepStarted, StepFinished), &Record{FailedUnits: []string{"backup.service"}})
|
|
if strings.Join(c.NewlyFailed, " ") != "web.service" || c.Passed {
|
|
t.Errorf("%+v", c)
|
|
}
|
|
}
|
|
|
|
func TestARestartIsNeededWhenTheRunningKernelIsNoLongerTheInstalledOne(t *testing.T) {
|
|
u := checkUpgrades(t, checkMachine("", "7.2.9-arch1-1"))
|
|
c := u.AfterCheck(checkedRun(StepStarted, StepFinished), &Record{})
|
|
if !c.RestartNeeded || c.InstalledKernel != "7.2.9-arch1-1" || !c.Passed {
|
|
t.Errorf("a restart is said and does not fail the check: %+v", c)
|
|
}
|
|
}
|
|
|
|
func TestTheCheckPassesOnlyWhenTheTransactionSucceeded(t *testing.T) {
|
|
u := checkUpgrades(t, checkMachine("", "7.2.8-arch1-2"))
|
|
if c := u.AfterCheck(checkedRun(StepStarted, StepFailed), &Record{}); c.Passed {
|
|
t.Errorf("%+v", c)
|
|
}
|
|
}
|