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
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.
286 lines
11 KiB
Go
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)
|
|
}
|
|
}
|