96 comments across the two repos named records that no longer exist. Each now points at the consolidated record that holds its reasoning -- ADR 0034 (a test defends a decision) is 0017, the eight host records are 0005, the four lab records are 0016. Worth noting for next time: these are references from outside HQ, so renumbering there is not free. It cost 38 files here.
119 lines
4.1 KiB
Go
119 lines
4.1 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/profile"
|
|
)
|
|
|
|
func TestAnUnreadableFactIsRecordedNotFatal(t *testing.T) {
|
|
// An inventory missing one field is useful; an inventory that refused to be taken is not.
|
|
// And an absent fact and a fact that failed to read are different things — collapsing them
|
|
// loses the one worth acting on.
|
|
failing := func(string) ([]byte, error) { return nil, errors.New("permission denied") }
|
|
inv := Collect(context.Background(), failing, nil, time.Second)
|
|
|
|
if inv.Machine == "" && len(inv.Unreadable) == 0 {
|
|
t.Fatal("nothing was read and nothing was reported unreadable")
|
|
}
|
|
if len(inv.Unreadable) == 0 {
|
|
t.Fatal("every file failed and nothing was recorded as unreadable")
|
|
}
|
|
for _, u := range inv.Unreadable {
|
|
if !strings.Contains(u, ":") {
|
|
t.Errorf("unreadable entry does not say which fact failed: %q", u)
|
|
}
|
|
}
|
|
if inv.Architecture == "" || inv.CPUs == 0 {
|
|
t.Error("facts that need no file were lost along with the ones that did")
|
|
}
|
|
}
|
|
|
|
func TestDistributionIsReadFromTheMachineNotAssumed(t *testing.T) {
|
|
cases := map[string]string{
|
|
"PRETTY_NAME=\"Arch Linux\"\nID=arch\n": "Arch Linux",
|
|
"ID=arch\n": "arch",
|
|
"# a comment\n\nID=debian\nPRETTY_NAME='Debian 13'\n": "Debian 13",
|
|
"NAME=Weird\nPRETTY_NAME=\"Has=Equals\"\n": "Has=Equals",
|
|
"": "",
|
|
}
|
|
for input, want := range cases {
|
|
if got := distributionFrom(input); got != want {
|
|
t.Errorf("distributionFrom(%q) = %q, want %q", input, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMemoryIsReadOrReportedMissing(t *testing.T) {
|
|
if kb, ok := memoryFrom("MemTotal: 32762868 kB\nMemFree: 1 kB\n"); !ok || kb != 32762868 {
|
|
t.Errorf("memoryFrom returned %d, %v", kb, ok)
|
|
}
|
|
// A meminfo without MemTotal must not read as zero memory — a machine reporting no memory
|
|
// would be excluded from placement for a parsing failure.
|
|
if _, ok := memoryFrom("MemFree: 1 kB\n"); ok {
|
|
t.Error("a meminfo with no MemTotal was read as a successful measurement")
|
|
}
|
|
if _, ok := memoryFrom("MemTotal: not-a-number kB\n"); ok {
|
|
t.Error("an unparseable MemTotal was read as a successful measurement")
|
|
}
|
|
}
|
|
|
|
func TestTheInventorySaysWhenItWasTaken(t *testing.T) {
|
|
// A node unreachable for a week is an ordinary situation (novox/hq ADR 0004), so an
|
|
// inventory that cannot be told from a stale one is missing the fact that matters.
|
|
before := time.Now().UTC()
|
|
inv := Collect(context.Background(), nil, nil, time.Second)
|
|
|
|
if inv.ObservedAt.IsZero() {
|
|
t.Fatal("the inventory does not say when it was observed")
|
|
}
|
|
if inv.ObservedAt.Before(before.Add(-time.Second)) || inv.ObservedAt.After(time.Now().UTC().Add(time.Second)) {
|
|
t.Errorf("ObservedAt is not the moment of observation: %s", inv.ObservedAt)
|
|
}
|
|
}
|
|
|
|
func TestTheHostDoesNotNameTheNode(t *testing.T) {
|
|
// A node's name is assigned by the mesh. A host that named itself would be deciding
|
|
// something, which is precisely what novox/hq ADR 0005 forbids it to do.
|
|
inv := Collect(context.Background(), nil, nil, time.Second)
|
|
hostname, _ := os.Hostname()
|
|
|
|
if inv.Machine != hostname {
|
|
t.Errorf("Machine is %q, not the machine's own hostname %q", inv.Machine, hostname)
|
|
}
|
|
}
|
|
|
|
// --- against this machine ---------------------------------------------------------------
|
|
|
|
func TestAgainstThisMachine_inventoryIsTrue(t *testing.T) {
|
|
// novox/hq ADR 0017: behaviour against a real system is tested alongside, not mocked.
|
|
inv := Collect(context.Background(), nil, profile.Default(nil), 10*time.Second)
|
|
|
|
if inv.Machine == "" {
|
|
t.Error("this machine did not report a name")
|
|
}
|
|
if inv.CPUs < 1 {
|
|
t.Errorf("this machine reported %d cpus", inv.CPUs)
|
|
}
|
|
if inv.OS == "linux" {
|
|
if inv.Kernel == "" {
|
|
t.Error("a linux machine reported no kernel version")
|
|
}
|
|
if inv.MemoryKB <= 0 {
|
|
t.Error("a linux machine reported no memory")
|
|
}
|
|
}
|
|
if len(inv.Profile.Capabilities) == 0 {
|
|
t.Error("the inventory carries no profile")
|
|
}
|
|
|
|
t.Logf("machine=%s dist=%q kernel=%s arch=%s cpus=%d mem=%dMB unreadable=%v",
|
|
inv.Machine, inv.Distribution, inv.Kernel, inv.Architecture,
|
|
inv.CPUs, inv.MemoryKB/1024, inv.Unreadable)
|
|
}
|