mesh/merge-gate head 2f5e97030a5c merged into trunk bc3b59227f46 — pass: builds mesh-delivery → novox; no bus step; every machine composes with the change …
mesh/repo-check head 2f5e97030a5c merged into trunk bc3b59227f46 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
mesh/delivery-group group fix/611-an-unchanged-build-is-delivered delivered: every member is delivered
A walk that moved nothing because every module's recorded build source held none of the changed files now says so per module (delivery.unchanged). Such a merge moves nothing (ADR 0270 §1): it is delivered, naming the modules, not stopped 20 minutes later with the operator asked about a change already on every machine. A module the walk neither built nor said unchanged still stops it (issue 474).
151 lines
6.6 KiB
Go
151 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// novox/hq issue 611: mesh-controller #293 changed one test file. Its check's plan, made while another walk built
|
|
// build-agent and route-proxy, read them whole and named them; at the cut their recorded build sources held none of
|
|
// its files, so its walk was done with no tier, "moved nothing the mesh holds: nothing to walk". The stranded rule
|
|
// (issue 474) read that as "ended without building build-agent, route-proxy", stopped it, and the operator was asked
|
|
// about a change already on every machine. The controller now says per module that the build matched what runs
|
|
// (the walk's delivery.unchanged), and such a delivery is delivered: its merge moves nothing (ADR 0270 §1). A walk
|
|
// that says neither — not built, not unchanged — still stops it.
|
|
|
|
const (
|
|
head293 = "94c928b4c7e1d2aa"
|
|
merge293 = "8f1aec0f2b9d6e11"
|
|
walk293 = "plan-1791867648768608552"
|
|
)
|
|
|
|
var at293 = time.Date(2026, 10, 13, 5, 10, 0, 0, time.UTC)
|
|
|
|
// matched is the why the controller gives a module it says unchanged.
|
|
const matched = "its build source in novox/mesh-controller holds none of the 1 changed file(s) " +
|
|
"(e.g. internal/builder/replay363_test.go): the build the mesh holds is its build"
|
|
|
|
// replay293 is #293 checked moving build-agent and route-proxy, merged, and its walk done with no tier, saying the
|
|
// modules named unchanged.
|
|
func replay293(t *testing.T, unchanged map[string]string) *world {
|
|
t.Helper()
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), at293.Add(-time.Minute))
|
|
p := pr(ctl474, 293, head293, "fix/363-replay", "internal/builder/replay363_test.go")
|
|
w.h.PullUpdated(p)
|
|
w.h.Checked(verdictMoving(ctl474, 293, head293, "build-agent", "route-proxy"))
|
|
w.later(time.Minute)
|
|
w.h.PullMerged(mergedAt(p, merge293, at293))
|
|
w.later(90 * time.Second)
|
|
note := nothingNote
|
|
if len(unchanged) > 0 {
|
|
note += "; unchanged, the build the mesh holds is theirs: " + strings.Join(sortedKeys(unchanged), ", ")
|
|
}
|
|
w.h.WalkMoved(w.ctl.put(Walk{ID: walk293, Repository: ctl474, Branch: "main", Commit: merge293, Created: w.now,
|
|
Updated: w.now, State: walkDone, Tiers: [][]string{}, Modules: map[string]*WalkModule{}, Note: note,
|
|
Delivery: &WalkDelivery{Awaits: "mesh-delivery", Merges: []WalkMerge{{Repository: ctl474, Commit: merge293}},
|
|
Unchanged: unchanged}}))
|
|
w.settleAll()
|
|
return w
|
|
}
|
|
|
|
// The live case: every module its plan named, the walk said matched what runs. Delivered at once, naming them, a
|
|
// success on the pull request, and nothing listed by stalled or asked of the operator, then or past the grace.
|
|
func TestAMergeWhoseModulesMatchWhatRunsIsDelivered(t *testing.T) {
|
|
w := replay293(t, map[string]string{"build-agent": matched, "route-proxy": matched})
|
|
id := IDOf(ctl474, head293)
|
|
d := w.delivery(id)
|
|
if d.State != Delivered {
|
|
t.Fatalf("#293, its modules said unchanged by its walk, is %s, waiting for %q", d.State, w.h.waitsFor(d))
|
|
}
|
|
why := d.Transitions[len(d.Transitions)-1].Why
|
|
for _, want := range []string{walk293, "build-agent, route-proxy", "matched what runs"} {
|
|
if !strings.Contains(why, want) {
|
|
t.Fatalf("delivered as %q, not saying %q", why, want)
|
|
}
|
|
}
|
|
if d.Stranded != "" {
|
|
t.Fatalf("it was read as stranded: %q", d.Stranded)
|
|
}
|
|
status := w.forge.statuses["novox/mesh-controller@"+head293+" mesh/delivery"]
|
|
if !strings.HasPrefix(status, "success ") {
|
|
t.Fatalf("mesh/delivery says %q", status)
|
|
}
|
|
w.later(strandedAfter + time.Minute)
|
|
w.settleAll()
|
|
if s := w.state(id); s != Delivered {
|
|
t.Fatalf("past the grace it is %s", s)
|
|
}
|
|
if lines := w.h.Stalled(); len(lines) != 0 {
|
|
t.Fatalf("stalled lists %+v", lines)
|
|
}
|
|
}
|
|
|
|
// A walk that said only one of its modules unchanged left the other unbuilt: it is still stopped past the grace,
|
|
// naming only the module nothing delivered.
|
|
func TestAMergeWithAModuleNeitherBuiltNorUnchangedIsStillStopped(t *testing.T) {
|
|
w := replay293(t, map[string]string{"build-agent": matched})
|
|
id := IDOf(ctl474, head293)
|
|
if s := w.state(id); s != Published {
|
|
t.Fatalf("within the grace it is %s", s)
|
|
}
|
|
w.later(strandedAfter + time.Minute)
|
|
w.settleAll()
|
|
d := w.delivery(id)
|
|
if d.State != Stopped {
|
|
t.Fatalf("route-proxy neither built nor unchanged, it is %s", d.State)
|
|
}
|
|
why := d.Transitions[len(d.Transitions)-1].Why
|
|
if !strings.Contains(why, "without building route-proxy (") {
|
|
t.Fatalf("its stop says %q", why)
|
|
}
|
|
}
|
|
|
|
// A walk that says nothing unchanged — a controller from before, or one that built nothing at all — stops it as
|
|
// before (issue 474).
|
|
func TestAMergeWhoseWalkSaysNothingUnchangedIsStillStopped(t *testing.T) {
|
|
w := replay293(t, nil)
|
|
w.later(strandedAfter + time.Minute)
|
|
w.settleAll()
|
|
if d := w.delivery(IDOf(ctl474, head293)); d.State != Stopped ||
|
|
!strings.Contains(d.Stranded, "without building build-agent, route-proxy") {
|
|
t.Fatalf("it is %s: %q", d.State, d.Stranded)
|
|
}
|
|
}
|
|
|
|
// Batched behind a later merge of its branch (issue 474's shape), a merge whose module the walk said unchanged moves
|
|
// nothing at either commit: delivered, not covered by a later merge that delivered nothing for it.
|
|
func TestABatchedMergeWhoseModuleMatchesWhatRunsIsDelivered(t *testing.T) {
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), at215.Add(-time.Minute))
|
|
p215 := pr(ctl474, 215, head215, "fix/431-make-check-runs-what-the-gate-runs", "Makefile")
|
|
p217 := pr(ctl474, 217, head217, "fix/432-a-test-reads-what-it-carries", "cmd/mesh-controller/check_test.go")
|
|
w.h.PullUpdated(p215)
|
|
w.h.PullUpdated(p217)
|
|
w.h.Checked(verdictMoving(ctl474, 215, head215, "build-agent"))
|
|
w.h.Checked(verdictMoving(ctl474, 217, head217))
|
|
w.later(time.Minute)
|
|
w.h.PullMerged(mergedAt(p215, merge215, at215))
|
|
w.later(time.Minute)
|
|
w.h.PullMerged(mergedAt(p217, merge217, at215.Add(56*time.Second)))
|
|
w.later(90 * time.Second)
|
|
w.h.WalkMoved(w.ctl.put(Walk{ID: batchWalk, Repository: ctl474, Branch: "main", Commit: merge217, Created: w.now,
|
|
Updated: w.now, State: walkDone, Tiers: [][]string{}, Modules: map[string]*WalkModule{}, Note: nothingNote,
|
|
Delivery: &WalkDelivery{Awaits: "mesh-delivery", Merges: []WalkMerge{
|
|
{Repository: ctl474, Commit: merge215, Carried: merge217}, {Repository: ctl474, Commit: merge217}},
|
|
Unchanged: map[string]string{"build-agent": "its build source holds none of the 2 changed file(s)"}}}))
|
|
w.settleAll()
|
|
if d := w.delivery(IDOf(ctl474, head215)); d.State != Delivered {
|
|
t.Fatalf("#215, build-agent said unchanged by its batch's walk, is %s, waiting for %q", d.State, w.h.waitsFor(d))
|
|
}
|
|
}
|
|
|
|
func sortedKeys(m map[string]string) []string {
|
|
var out []string
|
|
for k := range m {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|