So the release branch carries what main gained; parts 2 and 3 follow on this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
3.5 KiB
Go
88 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// A tier whose module is no move — made from the source every machine running it runs, so nothing is
|
|
// sent — is not waited on for a report after its marking (novox/hq issue 648). Its machines answered the
|
|
// send that last carried that build before the walk got there, and nothing will make them report again:
|
|
// the walk asks the next tier in the same step, rather than "waiting for app on anchor, laptop" for ever.
|
|
func TestANoMoveTierWaitsForNoNewReport(t *testing.T) {
|
|
judgeLeavesAsTheCore(t)
|
|
g := aGateMesh(t)
|
|
ctx := t.Context()
|
|
inv := g.open.inventory
|
|
|
|
// app is the build machine, so a module of the next tier is built by it: the tier gates the next on app
|
|
// running on its machines.
|
|
for _, m := range []catalogue.Manifest{
|
|
{Module: "app", Version: "c2", Claims: []catalogue.Claim{{Name: "node-build-agent", Scope: catalogue.ScopeNode}}},
|
|
{Module: "web", Version: "w1"},
|
|
} {
|
|
if err := inv.RegisterModule(ctx, m, inventory.Source{Repository: "novox/mesh-catalog", Seat: "git",
|
|
Path: "modules/" + m.Module, BuiltFrom: m.Version, Head: m.Version, Asked: time.Now().Add(-time.Minute)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// What anchor and laptop were last sent (c1) was made from the source the plan's build of c2 is.
|
|
for _, b := range []inventory.Build{
|
|
{ID: "build-1s", Module: "app", Commit: "c1", SourceFingerprint: "src1:app",
|
|
Asked: time.Now().Add(-30 * time.Second), At: time.Now().Add(-30 * time.Second)},
|
|
{ID: "build-2s", Module: "app", Commit: "c2", SourceFingerprint: "src1:app", Asked: time.Now(), At: time.Now()},
|
|
} {
|
|
b.Manifest, _ = json.Marshal(catalogue.Manifest{Module: "app", Version: "1"})
|
|
if err := inv.RecordBuild(ctx, b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Each machine answered that send, applied, before the walk's tier was built.
|
|
for _, n := range []string{"anchor", "laptop"} {
|
|
if _, err := inv.RecordDoing(ctx, nodeID(t, g.open, n), inventory.Doing{Node: n,
|
|
Outcome: inventory.OutcomeApplied, Declared: "d-" + n + "-c1", Applied: 1,
|
|
At: time.Now().Add(-10 * time.Second)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
built := time.Now().UTC()
|
|
p := g.plan(t)
|
|
p.Tiers = [][]string{{"app"}, {"web"}}
|
|
p.Modules["app"].Build, p.Modules["app"].BuiltAt = "build-2s", &built
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
advancePlans(ctx, g.open)
|
|
|
|
if len(g.sent) != 0 {
|
|
t.Fatalf("a no-move build was sent: %v", g.sent)
|
|
}
|
|
p = g.plan(t)
|
|
if s := p.Modules["app"]; s == nil || !strings.HasPrefix(s.Why, "no move") {
|
|
t.Fatalf("the build was not marked no move: %+v", s)
|
|
}
|
|
if p.Tier < 1 || strings.Contains(p.Note, "waiting for app") {
|
|
t.Fatalf("the walk waits on a no-move tier for a report nothing will cause: tier %d, %s %q", p.Tier, p.State, p.Note)
|
|
}
|
|
|
|
// A machine that has not answered its last send is still waited on: no move is not "never sent".
|
|
p.Tier, p.State, p.Note = 0, inventory.PlanRolling, ""
|
|
if err := inv.RecordSent(ctx, nodeID(t, g.open, "laptop"), "d-laptop-later", map[string]string{"app": "c1"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
advancePlans(ctx, g.open)
|
|
p = g.plan(t)
|
|
if p.Tier != 0 || !strings.Contains(p.Note, "waiting for app on laptop") {
|
|
t.Fatalf("a machine that has not answered its last send was not waited on: tier %d, %q", p.Tier, p.Note)
|
|
}
|
|
}
|