Files
mesh-controller/cmd/mesh-controller/plan_retry_stopped_test.go
T
jschoubben 55ba7f2cb8
mesh/merge-gate head 55ba7f2cb83c merged into trunk 296e11d499cb — pass: builds mesh-controller → novox; no bus step; the change touches no module of the m…
mesh/repo-check head 55ba7f2cb83c merged into trunk 296e11d499cb — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/check checking 55ba7f2cb83c on 219fdcd7f00c
Hold every refusal of the stopped path in the test ADR 0523 names: a newer open plan of the branch, a newer plan of a build not sent (issue 1886)
2026-10-23 02:44:58 +02:00

156 lines
8.6 KiB
Go

package main
import (
"slices"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/inventory"
)
// A walk stopped by hand while its tier waited — modules asked on the builder's keys, two built and never sent, the next
// tier not yet asked — is resumed by `plans retry` (issue 1886): set building again, its stop cleared, the asks it holds
// kept, so the next look carries it on as any walk. Nothing in it failed, so the retry of failed builds had nothing to do
// and refused it, leaving a stopped R4e with no verb to carry its 48 modules on.
func TestRetryResumesAWalkStoppedByHand(t *testing.T) {
at := time.Date(2026, 10, 23, 0, 1, 53, 0, time.UTC)
sent := at.Add(-time.Hour)
p := inventory.Plan{ID: "plan-r4e", Repository: "novox/mesh-sdk", Branch: "main", Commit: "7aacbc16",
Created: at.Add(-2 * time.Hour), State: inventory.PlanFailed, Tier: 1,
Note: "stopped by a person at tier 1: issue 1886",
Delivery: &inventory.PlanDelivery{Stopped: "a person (fix-1886@shanks)", StoppedWhy: "issue 1886",
Kept: []inventory.PlanKept{{Module: "mesh-controller"}}},
Modules: map[string]*inventory.PlanModule{}}
tier0 := []string{"mesh-controller", "mesh-sdk", "pacman", "route-proxy"}
for _, m := range tier0 {
p.Modules[m] = &inventory.PlanModule{State: "built", BuiltAt: &sent, SentAt: &sent}
}
var tier1 []string
for _, m := range []string{"build-agent", "mesh-tools"} {
tier1 = append(tier1, m)
p.Modules[m] = &inventory.PlanModule{State: "built", AskedAt: &sent, BuiltAt: &sent, Build: "build-" + m}
}
onKeys := []string{"mesh-build-go", "mesh-build-image", "mesh-build-node", "mesh-delivery-checker",
"mesh-delivery-forge-bridge", "mesh-delivery-gate", "mesh-delivery-indexer", "mesh-delivery-view"}
for _, m := range onKeys {
tier1 = append(tier1, m)
p.Modules[m] = &inventory.PlanModule{State: "asked", AskedAt: &sent, Key: true, Build: "build-" + m}
}
tier2 := []string{"gitea", "mesh-catalog", "mongodb"}
for _, m := range tier2 {
p.Modules[m] = &inventory.PlanModule{}
}
p.Tiers = [][]string{tier0, tier1, tier2}
if err := retryRefusal(p, []inventory.Plan{p}, ownPathOf(nil)); err != nil {
t.Fatalf("a walk stopped by hand with its tier waiting was refused a retry: %v", err)
}
said := resumeStopped(&p)
if p.State != inventory.PlanBuilding {
t.Errorf("resumed walk is %s, wanted building", p.State)
}
if p.Delivery.Stopped != "" || p.Delivery.StoppedWhy != "" || len(p.Delivery.Kept) > 0 {
t.Errorf("resumed walk still says it stopped: %+v", p.Delivery)
}
if !strings.Contains(p.Note, "resumed by hand") || !strings.Contains(p.Note, "a person (fix-1886@shanks)") {
t.Errorf("the note does not record the hand resume and the stop it undid: %q", p.Note)
}
for _, m := range onKeys {
s := p.Modules[m]
if isBuildersOwn(m) {
// Build-agent's own are asked of build-agent whatever the builder says (ADR 0522 §6): set unasked, for the look.
if s.State != "" || s.Key || s.Build != "" || s.AskedAt != nil {
t.Errorf("%s, build-agent's own, was left on the builder's key: %+v", m, s)
}
if !slices.Contains(unaskedOf(p), m) {
t.Errorf("%s is not among what the look asks next: %v", m, unaskedOf(p))
}
continue
}
if s.State != "asked" || !s.Key || s.Build != "build-"+m {
t.Errorf("%s's ask was not kept for the look to hand on: %+v", m, s)
}
}
for _, w := range []string{"plan-r4e", "tier 2 of 3", "5 asked", "2 built and not sent", "3 not yet asked in it",
"3 not yet asked in the tiers after", "mesh-build-go, mesh-build-image, mesh-build-node set unasked"} {
if !strings.Contains(said, w) {
t.Errorf("the answer %q does not say %q", said, w)
}
}
// What it still refuses: a walk stopped with nothing left in its tier, and one a related open walk runs beside.
done := p
done.State, done.Delivery = inventory.PlanFailed, &inventory.PlanDelivery{Stopped: "a person"}
done.Modules = map[string]*inventory.PlanModule{}
for _, tier := range done.Tiers {
for _, m := range tier {
done.Modules[m] = &inventory.PlanModule{State: "built", BuiltAt: &sent, SentAt: &sent}
}
}
if err := retryRefusal(done, nil, ownPathOf(nil)); err == nil || !strings.Contains(err.Error(), "nothing in tier 1") {
t.Errorf("a stopped walk with nothing left in its tier was retried: %v", err)
}
stopped := p
stopped.State, stopped.Delivery = inventory.PlanFailed, &inventory.PlanDelivery{Stopped: "a person"}
beside := inventory.Plan{ID: "plan-442", Repository: "novox/mesh-sdk", Branch: "release", Commit: "bbadbb2a",
Created: at, State: inventory.PlanBuilding, Tiers: [][]string{{"mesh-sdk"}},
Modules: map[string]*inventory.PlanModule{"mesh-sdk": {State: "asked"}}}
if err := retryRefusal(stopped, []inventory.Plan{beside}, ownPathOf(nil)); err == nil || !strings.Contains(err.Error(), "one walk at a time") {
t.Errorf("a stopped walk was resumed beside a related open walk: %v", err)
}
// A newer open plan of its repository and branch supersedes it: resuming would build what that one replaced.
newerOpen := inventory.Plan{ID: "plan-newer", Repository: "novox/mesh-sdk", Branch: "main", Commit: "d00dfeed",
Created: at, State: inventory.PlanBuilding, Tiers: [][]string{{"zsh"}},
Modules: map[string]*inventory.PlanModule{"zsh": {State: "asked"}}}
if err := retryRefusal(stopped, []inventory.Plan{stopped, newerOpen}, ownPathOf(nil)); err == nil ||
!strings.Contains(err.Error(), "plan-newer supersedes it") {
t.Errorf("a stopped walk was resumed under a newer open plan of its branch: %v", err)
}
// A newer plan holding a build of its tier not yet sent refuses it: resuming would send the older build over the newer.
newerBuilt := inventory.Plan{ID: "plan-agent", Repository: "novox/mesh-controller", Branch: "main", Commit: "d00dfeed",
Created: at, State: inventory.PlanDone, Tiers: [][]string{{"build-agent"}},
Modules: map[string]*inventory.PlanModule{"build-agent": {State: "built"}}}
if err := retryRefusal(stopped, []inventory.Plan{stopped, newerBuilt}, ownPathOf(nil)); err == nil ||
!strings.Contains(err.Error(), "older build") {
t.Errorf("a stopped walk was resumed over a newer plan of a build it has not sent: %v", err)
}
}
// A newer plan holding a module the stopped walk has not yet asked does not refuse its resume (review of issue 1886): that
// module is asked at its followed branch's head when the walk reaches it, so no older build goes over the newer. Open and
// related, the newer walk refuses it under one walk at a time; done, it does not. One holding a build the walk has not sent
// still refuses it.
func TestResumeIsNotRefusedByANewerPlanOfAModuleNotYetAsked(t *testing.T) {
at := time.Date(2026, 10, 23, 0, 1, 53, 0, time.UTC)
sent := at.Add(-time.Hour)
p := inventory.Plan{ID: "plan-r4e", Repository: "novox/mesh-sdk", Branch: "main", Commit: "7aacbc16",
Created: at.Add(-2 * time.Hour), State: inventory.PlanFailed, Tier: 1,
Delivery: &inventory.PlanDelivery{Stopped: "a person", StoppedWhy: "issue 1886"},
Tiers: [][]string{{"mesh-sdk"}, {"build-agent", "mesh-delivery-gate"}, {"mesh-delivery-builder", "gitea"}},
Modules: map[string]*inventory.PlanModule{
"mesh-sdk": {State: "built", BuiltAt: &sent, SentAt: &sent},
"build-agent": {State: "built", BuiltAt: &sent},
"mesh-delivery-gate": {State: "asked", AskedAt: &sent, Key: true},
"mesh-delivery-builder": {}, "gitea": {}}}
newer := func(state string, holds string) inventory.Plan {
return inventory.Plan{ID: "plan-1792715180015327197", Repository: "novox/mesh-delivery", Branch: "main",
Commit: "d00dfeed", Created: at, State: state, Tiers: [][]string{{holds}},
Modules: map[string]*inventory.PlanModule{holds: {State: "asked"}}}
}
if err := retryRefusal(p, []inventory.Plan{p, newer(inventory.PlanDone, "mesh-delivery-builder")}, ownPathOf(nil)); err != nil {
t.Errorf("a done newer plan of a module not yet asked refused the resume: %v", err)
}
if err := retryRefusal(p, []inventory.Plan{p, newer(inventory.PlanDone, "mesh-delivery-gate")}, ownPathOf(nil)); err != nil {
t.Errorf("a done newer plan of a module on the builder's key refused the resume: %v", err)
}
if err := retryRefusal(p, []inventory.Plan{p, newer(inventory.PlanBuilding, "mesh-delivery-builder")}, ownPathOf(nil)); err == nil ||
!strings.Contains(err.Error(), "one walk at a time") {
t.Errorf("an open newer walk related to it did not refuse the resume under one walk at a time: %v", err)
}
if err := retryRefusal(p, []inventory.Plan{p, newer(inventory.PlanDone, "build-agent")}, ownPathOf(nil)); err == nil ||
!strings.Contains(err.Error(), "older build") {
t.Errorf("a newer plan of a built module not yet sent did not refuse the resume: %v", err)
}
}