Files
mesh-host/internal/units/units_test.go
T
jochen 14e5d91c9a
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/315-a-failed-unit-is-a-condition delivered: every member is delivered
Say every failed unit, the module's and the machine's (hq issue 315)
Liveness judged only what a module runs long-lived, so a module whose
daemon is a package's unit started by D-Bus activation failed at every
start while its machine read healthy, and a degraded service manager was
said by nothing but the profile.

The engine now reads the failed units of the machine's manager and of
every account manager the declaration names, on the two-look rule, and
says whose each is: a declared service or process, a unit file the mesh
writes, or a package the mesh installs makes it that module's, said as
an unhealthy resource of kind unit; anything else is the machine's own,
said in the statement's new units field. It reads; it never acts.
2026-10-08 11:28:51 +02:00

286 lines
11 KiB
Go

package units
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/novox/mesh-host/internal/declaration"
)
// The reading of failed units (novox/hq issue 315, "how it is checked"): a failed unit a module states,
// writes or installs is that module's, in either manager; one no module places is the machine's; a unit
// liveness judges is left to it; the two-look rule holds both ways; a manager that cannot be read is
// unread, never healthy; and only reads are asked.
type fakeReader struct {
failed map[string][]Listed // by "system" or "user:<account>"
shown map[string]Shown // by unit
owners map[string]string // by path
unread map[string]error
ownerQs int
}
func mgr(scope, user string) string {
if scope == ScopeUser {
return "user:" + user
}
return "system"
}
func (f *fakeReader) Failed(_ context.Context, scope, user string) ([]Listed, error) {
if err := f.unread[mgr(scope, user)]; err != nil {
return nil, err
}
return f.failed[mgr(scope, user)], nil
}
func (f *fakeReader) Show(_ context.Context, _, _ string, units []string) (map[string]Shown, error) {
out := map[string]Shown{}
for _, u := range units {
out[u] = f.shown[u]
}
return out, nil
}
func (f *fakeReader) PackageOwning(_ context.Context, path string) (string, bool, error) {
f.ownerQs++
p, ok := f.owners[path]
return p, ok, nil
}
// shanks is the desktop as found on 2026-10-08: two mounts of the machine's own mount table, the
// Razer daemon's packaged user unit, and a unit a removed package left behind.
func shanks() *fakeReader {
return &fakeReader{
failed: map[string][]Listed{
"system": {{Unit: "mnt-recalbox.mount", Load: "loaded"}, {Unit: "storage-media.mount", Load: "loaded"}},
"user:operator": {{Unit: "greenclip.service", Load: "not-found"},
{Unit: "openrazer-daemon.service", Load: "loaded"}},
},
shown: map[string]Shown{
"mnt-recalbox.mount": {FragmentPath: "/run/systemd/generator/mnt-recalbox.mount", Result: "exit-code"},
"storage-media.mount": {FragmentPath: "/run/systemd/generator/storage-media.mount", Result: "timeout"},
"greenclip.service": {},
"openrazer-daemon.service": {FragmentPath: "/usr/lib/systemd/user/openrazer-daemon.service", Result: "exit-code"},
},
owners: map[string]string{"/usr/lib/systemd/user/openrazer-daemon.service": "openrazer-daemon"},
}
}
func declared() *declaration.Declaration {
return &declaration.Declaration{Resources: []declaration.Resource{
&declaration.Package{ID: "openrazer.daemon", Type: declaration.TypePackage, Package: "openrazer-daemon"},
&declaration.Package{ID: "clipmenu.greenclip", Type: declaration.TypePackage, Package: "rofi-greenclip", Absent: true},
&declaration.User{ID: "zsh.account", Type: declaration.TypeUser, Name: "operator"},
&declaration.Service{ID: "sshd.run", Type: declaration.TypeService, Unit: "sshd.service", State: "running"},
&declaration.Service{ID: "power.after-boot", Type: declaration.TypeService, Unit: "mesh-power-after-boot.service"},
&declaration.File{ID: "watcher.unit", Type: declaration.TypeFile, Path: "/home/operator/.config/systemd/user/watcher.service"},
}}
}
var t0 = time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
func lookTwice(t *testing.T, j *Judge) Statement {
t.Helper()
now := t0
j.Now = func() time.Time { return now }
first, _ := j.Look(context.Background())
if len(first.Failed) != 0 {
t.Fatalf("a unit was said failed on its first look: %+v", first.Failed)
}
now = now.Add(15 * time.Second)
st, _ := j.Look(context.Background())
return st
}
func TestTheModulesAndTheMachinesFailures(t *testing.T) {
r := shanks()
j := New(r)
j.Set(OwnedBy(declared(), nil))
st := lookTwice(t, j)
if st.State != Degraded {
t.Fatalf("state %q, want degraded", st.State)
}
owned := st.Owned()
if len(owned) != 1 || owned[0].Unit != "openrazer-daemon.service" || owned[0].Module != "openrazer" ||
owned[0].Resource != "openrazer.daemon" || owned[0].Via != ViaPackage || owned[0].Scope != ScopeUser {
t.Fatalf("the Razer daemon's packaged user unit is openrazer's: %+v", owned)
}
if owned[0].Since != t0 || owned[0].Streak != 2 || owned[0].Result != "exit-code" {
t.Fatalf("since the first look that found it, two in a row, how it failed: %+v", owned[0])
}
var machine []string
for _, f := range st.Unowned() {
machine = append(machine, f.Scope+"/"+f.Unit)
}
want := "system/mnt-recalbox.mount system/storage-media.mount user/greenclip.service"
if strings.Join(machine, " ") != want {
t.Fatalf("the machine's own: %v, want %s", machine, want)
}
}
func TestADeclaredUnitAndAWrittenFileAreTheirModules(t *testing.T) {
r := &fakeReader{
failed: map[string][]Listed{
"system": {{Unit: "mesh-power-after-boot.service", Load: "loaded"}, {Unit: "sshd.service", Load: "loaded"}},
"user:operator": {{Unit: "watcher.service", Load: "loaded"}},
},
shown: map[string]Shown{
"watcher.service": {FragmentPath: "/home/operator/.config/systemd/user/watcher.service"},
},
}
j := New(r)
j.Set(OwnedBy(declared(), nil))
st := lookTwice(t, j)
got := map[string]string{}
for _, f := range st.Failed {
got[f.Unit] = f.Module + " " + f.Via
}
if got["mesh-power-after-boot.service"] != "power unit" {
t.Errorf("a stateless service the declaration names is its module's: %q", got["mesh-power-after-boot.service"])
}
if got["watcher.service"] != "watcher file" {
t.Errorf("a user unit the mesh wrote is its module's: %q", got["watcher.service"])
}
if _, said := got["sshd.service"]; said {
t.Errorf("a service stated running is liveness's to judge, and said once: %v", got)
}
if r.ownerQs != 0 {
t.Errorf("the package database was asked %d time(s) for units the declaration already names", r.ownerQs)
}
}
func TestTwoLooksEachWay(t *testing.T) {
r := shanks()
j := New(r)
j.Set(OwnedBy(declared(), nil))
now := t0
j.Now = func() time.Time { return now }
st, changed := j.Look(context.Background())
if len(st.Failed) != 0 || !changed {
t.Fatalf("first look: nothing said failed, the state said: %+v %v", st.Failed, changed)
}
now = now.Add(15 * time.Second)
if st, changed = j.Look(context.Background()); len(st.Failed) != 4 || !changed {
t.Fatalf("second look: four said failed: %+v %v", st.Failed, changed)
}
now = now.Add(15 * time.Second)
if _, changed = j.Look(context.Background()); changed {
t.Fatal("a third look the same is no change")
}
// The operator mounts the media library again: no longer failed on the first look that says so.
r.failed["system"] = r.failed["system"][:1]
now = now.Add(15 * time.Second)
st, changed = j.Look(context.Background())
if len(st.Failed) != 3 || !changed {
t.Fatalf("a unit no longer failed is no longer said: %+v", st.Failed)
}
// Every manager clean: running.
r.failed = map[string][]Listed{}
now = now.Add(15 * time.Second)
if st, _ = j.Look(context.Background()); st.State != Running || len(st.Failed) != 0 {
t.Fatalf("no failed unit is running: %+v", st)
}
}
func TestAManagerThatCannotBeReadIsUnread(t *testing.T) {
r := shanks()
j := New(r)
j.Set(OwnedBy(declared(), nil))
lookTwice(t, j)
r.unread = map[string]error{"user:operator": errors.New("Failed to connect to bus: No medium found")}
st, _ := j.Look(context.Background())
if len(st.Unread) != 1 || !strings.Contains(st.Unread[0], "operator") {
t.Fatalf("the account manager is said unread: %v", st.Unread)
}
// Its failures are not counted while unread, and not forgotten either: back on the next read, said
// at once, from when they were first found.
r.unread = nil
st, _ = j.Look(context.Background())
for _, f := range st.Failed {
if f.Unit == "openrazer-daemon.service" && f.Since == t0 {
return
}
}
t.Fatalf("a manager read again says what it held, from when it was first found: %+v", st.Failed)
}
func TestNoManagerReadIsUnknown(t *testing.T) {
r := &fakeReader{unread: map[string]error{"system": errors.New("systemctl: not found"),
"user:operator": errors.New("systemctl: not found")}}
j := New(r)
j.Set(OwnedBy(declared(), nil))
if st, _ := j.Look(context.Background()); st.State != Unknown {
t.Fatalf("no manager read is unknown, never running: %q", st.State)
}
}
func TestAnAdoptedMachinesHoldingIsTheMachines(t *testing.T) {
d := &declaration.Declaration{Resources: []declaration.Resource{
&declaration.Service{ID: "found.cups", Type: declaration.TypeService, Unit: "cups.service"},
}}
o := OwnedBy(d, map[string]bool{"found.cups": true})
if _, ok := o.Units["system/cups.service"]; ok {
t.Fatal("a unit an adopted machine holds as found is the machine's, not a module's")
}
}
func TestTheReaderOnlyReads(t *testing.T) {
var asked []string
run := func(_ context.Context, name string, args ...string) (string, error) {
asked = append(asked, name+" "+strings.Join(args, " "))
switch {
case name == "systemctl" && contains(args, "list-units"):
return "● greenclip.service not-found failed failed greenclip.service\n" +
"openrazer-daemon.service loaded failed failed Daemon to manage razer devices in userspace\n", nil
case name == "systemctl" && contains(args, "show"):
return "Id=greenclip.service\nFragmentPath=\nResult=start-limit-hit\n\n" +
"Id=openrazer-daemon.service\nFragmentPath=/usr/lib/systemd/user/openrazer-daemon.service\nResult=exit-code\n", nil
case name == "pacman" && contains(args, "-Qqo"):
return "openrazer-daemon\n", nil
}
return "", nil
}
e := Exec{Run: run, System: "arch"}
listed, err := e.Failed(context.Background(), ScopeUser, "operator")
if err != nil || len(listed) != 2 || listed[0].Unit != "greenclip.service" || listed[0].Load != "not-found" {
t.Fatalf("the list, its mark skipped: %+v %v", listed, err)
}
shown, err := e.Show(context.Background(), ScopeUser, "operator", []string{"greenclip.service", "openrazer-daemon.service"})
if err != nil || shown["openrazer-daemon.service"].FragmentPath == "" || shown["greenclip.service"].Result != "start-limit-hit" {
t.Fatalf("the show: %+v %v", shown, err)
}
if pkg, ok, err := e.PackageOwning(context.Background(), "/usr/lib/systemd/user/openrazer-daemon.service"); !ok ||
pkg != "openrazer-daemon" || err != nil {
t.Fatalf("the owner: %q %v %v", pkg, ok, err)
}
for _, a := range asked {
if !strings.Contains(a, "list-units") && !strings.Contains(a, " show ") && !strings.HasPrefix(a, "pacman -Q") {
t.Errorf("asked something that is not a read: %q", a)
}
if !strings.HasPrefix(a, "pacman") && !strings.Contains(a, "--machine=operator@") {
t.Errorf("an account's manager is asked as that account's: %q", a)
}
}
}
func contains(args []string, s string) bool {
for _, a := range args {
if a == s {
return true
}
}
return false
}
func TestNothingIsReadBeforeADeclaration(t *testing.T) {
r := shanks()
j := New(r)
if st, changed := j.Look(context.Background()); st.State != "" || changed {
t.Fatalf("before a declaration every unit would read as the machine's: %+v", st)
}
}