Files
jschoubben ee2648188d Repoint ADR references after HQ consolidated 65 records to 23
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.
2026-08-28 23:33:44 +02:00

196 lines
7.5 KiB
Go

package profile
import (
"context"
"errors"
"strings"
"testing"
"time"
)
// The decision each test defends is named in the test, per novox/hq ADR 0017. These cover
// structure and logic; profile_system_test.go covers the same detectors against the real
// machine, because a test that fakes the system under detection asserts only that the fake
// behaves as expected.
func TestIssue007_installedIsNotUsable(t *testing.T) {
// 04-ISSUES/007 — an installed package is not a capability. The client was present and the
// daemon was down, and the node took work it could not do. A detector whose command fails
// must report ABSENT, however installed the thing looks.
failing := func(context.Context, string, ...string) (string, error) {
return "", errors.New("docker exited 1: Cannot connect to the Docker daemon")
}
got := Detect(context.Background(), Default(failing), time.Second)
if got.Has(CapContainerRuntime) {
t.Fatal("a runtime whose daemon refuses the connection was reported present")
}
for _, v := range got.Capabilities {
if v.Name != CapContainerRuntime {
continue
}
if !strings.Contains(v.Detail, "Cannot connect") {
t.Fatalf("the reason was lost; detail was %q", v.Detail)
}
if v.How == "" {
t.Fatal("a verdict with no stated method cannot be checked when it is wrong")
}
}
}
func TestEveryVerdictSaysHowItKnows(t *testing.T) {
// A capability reported absent with no reason is the fault in a new place: something
// nobody can act on. Both outcomes must carry a method.
for _, runner := range []Runner{
func(context.Context, string, ...string) (string, error) { return "ok", nil },
func(context.Context, string, ...string) (string, error) { return "", errors.New("nope") },
} {
for _, v := range Detect(context.Background(), Default(runner), time.Second).Capabilities {
if strings.TrimSpace(v.How) == "" {
t.Errorf("%s reports present=%v with no stated method", v.Name, v.Present)
}
if strings.TrimSpace(v.Detail) == "" {
t.Errorf("%s reports present=%v with no detail", v.Name, v.Present)
}
}
}
}
func TestDetectionSurvivesAFailingProbe(t *testing.T) {
// Deliberately NOT ADR 0010. That rule governs APPLYING state, where a failed step means
// the machine is not what was asked for. A failed probe is a finding, and aborting would
// replace one legible absence with total ignorance of the rest.
only := func(ctx context.Context, name string, args ...string) (string, error) {
if name == "pacman" {
return "pacman 7.0.0", nil
}
return "", errors.New("not here")
}
got := Detect(context.Background(), Default(only), time.Second)
if len(got.Capabilities) != len(Default(nil)) {
t.Fatalf("expected every detector to report, got %d of %d",
len(got.Capabilities), len(Default(nil)))
}
if !got.Has(CapPackageManager) {
t.Error("the one working capability was lost among the failures")
}
}
func TestAProbeCannotHangTheHost(t *testing.T) {
// A host that blocks forever on a wedged command reports nothing at all, which is worse
// than reporting the capability absent.
blocking := func(ctx context.Context, name string, args ...string) (string, error) {
<-ctx.Done()
return "", ctx.Err()
}
done := make(chan Profile, 1)
go func() { done <- Detect(context.Background(), Default(blocking), 50*time.Millisecond) }()
select {
case got := <-done:
if got.Has(CapFirewall) {
t.Error("a probe that never answered was reported present")
}
case <-time.After(5 * time.Second):
t.Fatal("detection did not return — a wedged probe hung the host")
}
}
func TestUnknownCapabilityIsAbsentNotAnError(t *testing.T) {
// Asking about a capability nothing detects is a question with a true answer.
p := Detect(context.Background(), nil, time.Second)
if p.Has("something-nothing-detects") {
t.Error("an undetected capability reported present")
}
}
func TestMissingListsWhatCannotBeAskedOf(t *testing.T) {
// What a node CANNOT do is the half that decides whether work may be placed on it.
none := func(context.Context, string, ...string) (string, error) { return "", errors.New("no") }
missing := Detect(context.Background(), Default(none), time.Second).Missing()
if len(missing) == 0 {
t.Fatal("everything failed and nothing was reported missing")
}
for i := 1; i < len(missing); i++ {
if missing[i-1] > missing[i] {
t.Fatalf("Missing() is not ordered: %v", missing)
}
}
}
func TestTheProfileIsOrdered(t *testing.T) {
// Two runs on an unchanged machine produce the same profile. Without this, comparing what
// a node was against what it is would report differences that are only ordering.
ok := func(context.Context, string, ...string) (string, error) { return "fine", nil }
first := Detect(context.Background(), Default(ok), time.Second)
second := Detect(context.Background(), Default(ok), time.Second)
if len(first.Capabilities) != len(second.Capabilities) {
t.Fatal("two runs disagreed on how many capabilities exist")
}
for i := range first.Capabilities {
if first.Capabilities[i].Name != second.Capabilities[i].Name {
t.Fatalf("ordering is not stable at %d: %q then %q",
i, first.Capabilities[i].Name, second.Capabilities[i].Name)
}
}
}
func TestADegradedInitIsStillAnInit(t *testing.T) {
// Found by running against a real machine, not by reasoning. `systemctl is-system-running`
// exits non-zero for every state except `running` — including `degraded`, which means some
// units failed and the init is emphatically present. Reading the exit code declared no
// service manager on a machine whose init it was.
//
// This is 04-ISSUES/007 in the mirror: 007 is installed-but-broken reported present; this
// is working-but-imperfect reported absent. Both make the mesh place work wrongly.
degraded := func(ctx context.Context, name string, args ...string) (string, error) {
if name == "systemctl" {
return "degraded\n", errors.New("systemctl exited 1: ")
}
return "", errors.New("not here")
}
got := Detect(context.Background(), Default(degraded), time.Second)
if !got.Has(CapServiceManager) {
t.Fatal("a degraded init was reported absent — the machine would refuse work it can do")
}
for _, v := range got.Capabilities {
if v.Name == CapServiceManager && v.Detail != "degraded" {
t.Errorf("the state was lost; detail was %q", v.Detail)
}
}
}
func TestAnInitThatIsNotManagingThisMachineIsAbsent(t *testing.T) {
// The other side of the same rule. `offline` means it is installed and not in charge —
// which must not be read as present just because a state came back.
for _, state := range []string{"offline", "unknown"} {
runner := func(ctx context.Context, name string, args ...string) (string, error) {
if name == "systemctl" {
return state + "\n", errors.New("systemctl exited 1: ")
}
return "", errors.New("not here")
}
if Detect(context.Background(), Default(runner), time.Second).Has(CapServiceManager) {
t.Errorf("state %q was reported as a working service manager", state)
}
}
}
func TestAFailureWithNoMessageStillCarriesAReason(t *testing.T) {
// systemctl fails with empty stderr, which produced a verdict reading "exited 1:" — absent,
// with nothing anyone could act on.
silent := func(context.Context, string, ...string) (string, error) { return "", errors.New("") }
for _, v := range Detect(context.Background(), Default(silent), time.Second).Capabilities {
if v.Present {
continue
}
if strings.TrimSpace(v.Detail) == "" || strings.HasSuffix(strings.TrimSpace(v.Detail), ":") {
t.Errorf("%s is absent for no stated reason: %q", v.Name, v.Detail)
}
}
}