package main // Tests for meshstats.go: the band thresholds must match // deploy/status-publish.sh's pct_band/load_word bit-for-bit (both sides // compute the same word for the same box, whichever process does it), the // whimsical label algorithm must match status-publish.sh's box_label // bit-for-bit too (TestBoxLabelMatchesBash pins a real bash-computed // example), and computeBoxStats must derive asleep from awake (never probe // them separately, so the two counts can't disagree). import ( "os" "path/filepath" "strconv" "testing" "time" ) func TestPctBandThresholds(t *testing.T) { cases := []struct { pct int want int }{ {0, 0}, {69, 0}, {70, 1}, {90, 1}, {91, 2}, {100, 2}, } for _, c := range cases { if got := pctBand(c.pct); got != c.want { t.Errorf("pctBand(%d) = %d, want %d", c.pct, got, c.want) } } } func TestLoadWord(t *testing.T) { if loadWord(0) != "comfortable" || loadWord(1) != "busy" || loadWord(2) != "critical" { t.Fatal("loadWord vocabulary must match status-publish.sh's load_word exactly") } } func TestMemLoadWord(t *testing.T) { dir := t.TempDir() write := func(total, avail int64) { content := "" if total > 0 { content += "MemTotal: " + strconv.FormatInt(total, 10) + " kB\n" } if avail > 0 { content += "MemAvailable: " + strconv.FormatInt(avail, 10) + " kB\n" } if err := os.WriteFile(filepath.Join(dir, "meminfo"), []byte(content), 0o644); err != nil { t.Fatal(err) } } orig := meminfoPath meminfoPath = filepath.Join(dir, "meminfo") defer func() { meminfoPath = orig }() write(1000, 500) // 50% used -> comfortable if w, ok := memLoadWord(); !ok || w != "comfortable" { t.Fatalf("50%% used: got %q, %v", w, ok) } write(1000, 250) // 75% used -> busy if w, ok := memLoadWord(); !ok || w != "busy" { t.Fatalf("75%% used: got %q, %v", w, ok) } write(1000, 50) // 95% used -> critical if w, ok := memLoadWord(); !ok || w != "critical" { t.Fatalf("95%% used: got %q, %v", w, ok) } meminfoPath = filepath.Join(dir, "does-not-exist") if _, ok := memLoadWord(); ok { t.Fatal("unreadable meminfo should report ok=false, never guess") } } // Cross-checked against the real bash script: running status-publish.sh's own // box_label() function against the same seed input produces the identical // "phileas-juggling" — pinning that the two word lists and hash-index math // haven't drifted apart. func TestBoxLabelMatchesBash(t *testing.T) { dir := t.TempDir() idFile := filepath.Join(dir, "machine-id") if err := os.WriteFile(idFile, []byte("test-machine-id-fixture-12345"), 0o644); err != nil { t.Fatal(err) } orig := machineIDPath machineIDPath = idFile defer func() { machineIDPath = orig }() if got, want := boxLabel(), "phileas-juggling"; got != want { t.Fatalf("boxLabel() = %q, want %q (bash's box_label for the same seed)", got, want) } } // computeBoxStats must derive asleep = hibernating total - awake, never // probe them independently, and count only actAwake (not a mid-wake/mid-stop // transient) as "awake". func TestComputeBoxStats(t *testing.T) { reg, err := openRegistry(filepath.Join(t.TempDir(), "registry.db")) if err != nil { t.Fatalf("open registry: %v", err) } for _, h := range []string{"a.11ch.at", "b.11ch.at", "c.11ch.at"} { if err := reg.upsert(Route{Host: h, Kind: "instance", Socket: "/tmp/" + h + ".sock", Hibernate: true}); err != nil { t.Fatal(err) } } // A non-hibernating route must not count toward the hibernating total. if err := reg.upsert(Route{Host: "systemd.11ch.at", Kind: "instance", Socket: "/tmp/x.sock", Hibernate: false}); err != nil { t.Fatal(err) } act := newActivator(hibernateConfig{Bucket: "test-bucket"}, &fakeBackend{}, reg) act.entries["a.11ch.at"] = &actEntry{host: "a.11ch.at", state: actAwake} act.entries["b.11ch.at"] = &actEntry{host: "b.11ch.at", state: actWaking} // not yet awake stats := computeBoxStats(reg, act, nil) if stats.Label == "" { t.Fatal("Label must be populated") } if stats.Awake == nil || *stats.Awake != 1 { t.Fatalf("Awake = %v, want 1 (only actAwake counts)", stats.Awake) } if stats.Asleep == nil || *stats.Asleep != 2 { t.Fatalf("Asleep = %v, want 2 (3 hibernating - 1 awake)", stats.Asleep) } if stats.Region != "" { t.Fatalf("Region = %q, want empty (LCHAT_BOX_REGION unset)", stats.Region) } } // boxRegion is an explicit-override-only read of LCHAT_BOX_REGION — absent // env means empty (never guessed), and computeBoxStats must carry whatever // it resolves straight into BoxStats.Region. func TestBoxRegionExplicitOverride(t *testing.T) { t.Setenv("LCHAT_BOX_REGION", "") if got := boxRegion(); got != "" { t.Fatalf("boxRegion() = %q, want empty when LCHAT_BOX_REGION is unset", got) } t.Setenv("LCHAT_BOX_REGION", "UK · London") if got, want := boxRegion(), "UK · London"; got != want { t.Fatalf("boxRegion() = %q, want %q", got, want) } reg, err := openRegistry(filepath.Join(t.TempDir(), "registry.db")) if err != nil { t.Fatalf("open registry: %v", err) } act := newActivator(hibernateConfig{Bucket: "test-bucket"}, &fakeBackend{}, reg) if got := computeBoxStats(reg, act, nil).Region; got != "UK · London" { t.Fatalf("computeBoxStats().Region = %q, want %q", got, "UK · London") } } // Two boxes that both know about the same mesh-replicated routes must never // both count the same route as hibernating in their own published snapshot — // that's exactly the "same instance reads as asleep on two hosts" confusion // the Fleet page must not show. computeBoxStats filters to routes THIS box // currently HRW-owns, so each box's own count is a genuine, non-overlapping // slice of the fleet: every in-scope route is owned by exactly one live box, // and an out-of-scope route (never replicated anywhere else) always counts // locally regardless of ownership. func TestComputeBoxStatsPartitionsOwnershipAcrossBoxes(t *testing.T) { clk := &fakeClock{t: time.Now()} store := newFakeMeshStore(clk.now) domains := []string{"test"} meshA := newMesh("http://box-a", store, domains) meshB := newMesh("http://box-b", store, domains) meshA.now, meshB.now = clk.now, clk.now meshA.beat() meshB.beat() meshA.beat() // refresh A's view so it now also sees B as live boxes := []string{meshA.self, meshB.self} var ownedByA, ownedByB []string for i := 0; len(ownedByA) < 2 || len(ownedByB) < 2; i++ { h := "space-" + strconv.Itoa(i) + ".test" switch hrwOwner(h, boxes) { case meshA.self: if len(ownedByA) < 2 { ownedByA = append(ownedByA, h) } case meshB.self: if len(ownedByB) < 2 { ownedByB = append(ownedByB, h) } } } regA, err := openRegistry(filepath.Join(t.TempDir(), "a.db")) if err != nil { t.Fatalf("open registry A: %v", err) } regB, err := openRegistry(filepath.Join(t.TempDir(), "b.db")) if err != nil { t.Fatalf("open registry B: %v", err) } // Full replication: both boxes know about all 4 in-scope routes, exactly // like mesh-seed/fanOutMeshRoute leave them today. Plus one out-of-scope // route that only box A ever knew about (never replicated anywhere). for _, reg := range []*Registry{regA, regB} { for _, h := range append(append([]string{}, ownedByA...), ownedByB...) { if err := reg.upsert(Route{Host: h, Kind: "instance", Socket: "/tmp/" + h + ".sock", Hibernate: true}); err != nil { t.Fatal(err) } } } if err := regA.upsert(Route{Host: "local-only.example", Kind: "instance", Socket: "/tmp/local.sock", Hibernate: true}); err != nil { t.Fatal(err) } actA := newActivator(hibernateConfig{Bucket: "test-bucket"}, &fakeBackend{}, regA) actB := newActivator(hibernateConfig{Bucket: "test-bucket"}, &fakeBackend{}, regB) statsA := computeBoxStats(regA, actA, meshA) statsB := computeBoxStats(regB, actB, meshB) // A owns its 2 + the 1 out-of-scope local-only route = 3; B owns its 2. if statsA.Asleep == nil || *statsA.Asleep != 3 { t.Fatalf("box A asleep = %v, want 3 (its 2 owned in-scope + 1 out-of-scope, never B's)", statsA.Asleep) } if statsB.Asleep == nil || *statsB.Asleep != 2 { t.Fatalf("box B asleep = %v, want 2 (only its own 2 owned in-scope routes)", statsB.Asleep) } // The whole point: summed across both boxes' own reports, no route is // ever double-counted, even though both registries physically hold all 4 // in-scope rows. if got, want := *statsA.Asleep+*statsB.Asleep, 5; got != want { t.Fatalf("fleet-wide total = %d, want %d (5 unique routes, no double count)", got, want) } }