Files
mesh-catalog/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue362_test.go
T

435 lines
18 KiB
Go

package main
import (
"strings"
"testing"
"time"
)
// novox/hq issue 362, ADR 0276: two merges of one repository 18 seconds apart, walked by one walk at the later
// merge's commit, which superseded the first. Every delivery the walk answers follows it and ends with it; no
// walk is tied to a delivery that ended; and what cannot move is said at once.
// A controller that names the merges a walk answers (ADR 0276): the forge is not asked, and the earlier merge is
// tied at once.
func TestAWalkThatNamesItsMergesIsFollowedByEachOfThem(t *testing.T) {
w := twoMerges(t)
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
for _, id := range []string{IDOf(cat, headCC), IDOf(cat, headDu)} {
if d := w.delivery(id); d.State != Published || d.Walk == nil || d.Walk.ID != "plan-9" {
t.Fatalf("%s is %s on %v", id, d.State, d.Walk)
}
}
w.settleAll()
if len(w.forge.containsAsks) != 0 {
t.Fatalf("the forge was asked %v for a walk that names its merges", w.forge.containsAsks)
}
if len(w.ctl.delivers) != 1 {
t.Fatalf("the word was asked %v", w.ctl.delivers)
}
}
// A walk that failed marks no merge delivered: the merge it carried its own commit for fails, and the earlier
// merge it carried a later commit for waits for a walk of its own.
func TestAFailedWalkMarksNoMergeItCarriedDelivered(t *testing.T) {
w := twoMerges(t)
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
walk = w.ctl.walk("plan-9")
walk.State, walk.Note = walkFailed, "dunst failed its gate on anchor"
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if s := w.state(IDOf(cat, headDu)); s != Failed {
t.Fatalf("dunst is %s", s)
}
cc := w.delivery(IDOf(cat, headCC))
if cc.State != Published || cc.State.Reached() {
t.Fatalf("claude-code is %s", cc.State)
}
// Its own walk, when the controller opens one, is followed.
own := catWalk("plan-10", mergeCC, atDu.Add(time.Hour), "claude-code")
w.h.WalkMoved(w.ctl.put(own))
w.settleAll()
w.h.WalkMoved(w.ctl.put(done(w.ctl.walk("plan-10"), w.now)))
w.settleAll()
if s := w.state(IDOf(cat, headCC)); s != Delivered {
t.Fatalf("claude-code on its own walk is %s", s)
}
}
// Never revived: a walk at the commit of a delivery that ended is not tied to it, not adopted, and not let go —
// it is listed by `stalled` as a walk no live delivery holds.
func TestAWalkIsNeverTiedToADeliveryThatEnded(t *testing.T) {
w := twoMerges(t)
first := w.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w.h.WalkMoved(first)
w.h.WalkMoved(w.ctl.put(supersededBy(first, "")))
// A record from before ADR 0276: superseded, naming no walk — the delivery ends superseded.
sup := w.ctl.walk("plan-1")
sup.Note = "superseded"
w.h.WalkMoved(w.ctl.put(sup))
idDu := IDOf(cat, headDu)
if s := w.state(idDu); s != Superseded {
t.Fatalf("dunst is %s", s)
}
orphan := catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "dunst")
w.h.WalkMoved(w.ctl.put(orphan))
w.settleAll()
w.later(3 * time.Minute)
w.settleAll()
if d := w.delivery(idDu); d.State != Superseded || d.Walk.ID != "plan-1" {
t.Fatalf("the ended delivery is %s on %s", d.State, d.Walk.ID)
}
for _, asked := range w.ctl.delivers {
if asked == "plan-2" {
t.Fatal("a walk no live delivery holds was let go")
}
}
var found bool
for _, l := range w.h.Stalled() {
if l.State == Unheld && l.Walk == "plan-2" {
found = true
}
}
if !found {
t.Fatalf("stalled does not list the walk no delivery holds: %+v", w.h.Stalled())
}
}
// A merge no walk answers is held after ten minutes with the true reason, and listed by `stalled` at once; a walk
// that answers it later lifts it.
func TestAMergeNoWalkAnswersIsHeldTrulyAndSaidAtOnce(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
// An open walk of the repository that moves something else: it does not answer this merge.
w.h.WalkMoved(w.ctl.put(catWalk("plan-5", "eeeeeeeeeeee5555", atCC.Add(time.Minute), "dunst")))
w.later(11 * time.Minute)
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
id := IDOf(cat, headCC)
d := w.delivery(id)
if d.State != Held || !strings.Contains(d.HeldWhy, "plan-5") || !strings.Contains(d.HeldWhy, "name no merge") {
t.Fatalf("it is %s: %q", d.State, d.HeldWhy)
}
lines := w.h.Stalled()
if len(lines) != 1 || lines[0].ID != id || lines[0].Bound != "0s" {
t.Fatalf("stalled lists %+v", lines)
}
// The controller opens a walk that names it.
walk := catWalk("plan-6", mergeCC, w.now, "claude-code")
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if s := w.state(id); s != Delivering || len(w.ctl.delivers) != 1 || w.ctl.delivers[0] != "plan-6" {
t.Fatalf("with its walk it is %s, let go %v", s, w.ctl.delivers)
}
}
// One word per walk, and none while a delivery it carries is not at its turn.
func TestAWalkIsLetGoOnlyWhenEveryDeliveryItCarriesIsAtItsTurn(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atDu.Add(5*time.Second))
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
du := pr(cat, 102, headDu, "feat/dunst", "modules/dunst/module.json")
w.h.PullUpdated(cc)
w.h.PullUpdated(du)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
// dunst merges unchecked: held for a person.
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
w.h.PullMerged(mergedAt(du, mergeDu, atDu))
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if s := w.state(IDOf(cat, headDu)); s != Held {
t.Fatalf("dunst is %s", s)
}
if len(w.ctl.delivers) != 0 {
t.Fatalf("the walk was let go %v while it carries a held delivery", w.ctl.delivers)
}
if _, err := w.h.Release(IDOf(cat, headDu), "checked by hand", "jochen"); err != nil {
t.Fatal(err)
}
w.settleAll()
if len(w.ctl.delivers) != 1 {
t.Fatalf("released, the word was asked %v", w.ctl.delivers)
}
}
// A merge in a batch still assembling is said so, is not held for a person, and nothing is let go.
func TestAMergeInAnAssemblingBatchIsShownAndNotHeld(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
closes, atMost := atCC.Add(90*time.Second), atCC.Add(10*time.Minute)
batch := Walk{ID: "plan-b1", Repository: cat, Branch: "main", Commit: mergeCC, Created: atCC, State: walkAssembling,
Delivery: &WalkDelivery{Awaits: "mesh-delivery", Merges: []WalkMerge{{Repository: cat, Commit: mergeCC}},
Batch: &WalkBatch{ClosesAt: &closes, AtMost: &atMost}}}
w.h.WalkMoved(w.ctl.put(batch))
w.later(48 * time.Second)
w.settleAll()
id := IDOf(cat, headCC)
var line Line
for _, l := range w.h.Deliveries("", "", "", true) {
if l.ID == id {
line = l
}
}
if !strings.Contains(line.Waits, "plan-b1: assembling, 42s left") || !strings.Contains(line.Waits, "plan not yet calculated") {
t.Fatalf("it waits for %q", line.Waits)
}
// Queued behind a walk for longer than a walk opens within: still not held.
batch = w.ctl.walk("plan-b1")
batch.State, batch.Delivery.Batch.Behind = walkQueued, "plan-7"
w.h.WalkMoved(w.ctl.put(batch))
w.later(20 * time.Minute)
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
if d := w.delivery(id); d.State != Ready || d.Walk != nil {
t.Fatalf("a merge in a queued batch is %s on %v", d.State, d.Walk)
}
if len(w.ctl.delivers) != 0 {
t.Fatalf("a batch was let go: %v", w.ctl.delivers)
}
// Cut: the batch is a walk now, under its id.
batch.State, batch.Delivery.Batch = walkBuilding, nil
batch.Tiers, batch.Modules = [][]string{{"claude-code"}}, map[string]*WalkModule{"claude-code": {}}
w.h.WalkMoved(w.ctl.put(batch))
w.settleAll()
if d := w.delivery(id); d.State != Delivering || d.Walk.ID != "plan-b1" {
t.Fatalf("cut, it is %s on %v", d.State, d.Walk)
}
}
// An older record of the walk a delivery no longer follows, arriving late, does not take it back.
func TestALateRecordOfAnOlderWalkDoesNotTakeADeliveryBack(t *testing.T) {
w := twoMerges(t)
w.forge.contains[mergeCC+" in "+mergeDu] = true
first := w.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w.h.WalkMoved(first)
w.h.WalkMoved(w.ctl.put(catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "claude-code", "dunst")))
w.h.WalkMoved(first) // the older record, again
if d := w.delivery(IDOf(cat, headDu)); d.Walk.ID != "plan-2" {
t.Fatalf("dunst went back to %s", d.Walk.ID)
}
}
// A walk no merge was heard for, at the commit of a delivery that ended, is not adopted by that delivery: it is
// listed by `stalled` as a walk no live delivery holds.
func TestAnUnheardWalkIsNotAdoptedByADeliveryThatEnded(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.PullClosed(cc)
id := IDOf(cat, headCC)
if s := w.state(id); s != Stopped {
t.Fatalf("closed unmerged it is %s", s)
}
w.h.WalkMoved(w.ctl.put(catWalk("plan-3", headCC, atCC.Add(time.Minute), "claude-code")))
w.later(3 * time.Minute)
w.settleAll()
w.later(3 * time.Minute)
w.settleAll()
if d := w.delivery(id); d.State != Stopped || d.Walk != nil {
t.Fatalf("the stopped delivery is %s on %v", d.State, d.Walk)
}
var found bool
for _, l := range w.h.Stalled() {
found = found || l.State == Unheld && l.Walk == "plan-3"
}
if !found || len(w.ctl.delivers) != 0 {
t.Fatalf("stalled lists %+v; let go %v", w.h.Stalled(), w.ctl.delivers)
}
}
// Two members of one delivery group carried by one walk: let go once, for both (review of #171: the group's turn
// stopped at its first member, and the walk waited for the second for ever).
func TestAWalkCarryingTwoMembersOfAGroupIsLetGoOnce(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atDu.Add(5*time.Second))
a := pr("novox/a", 1, "aaaaaaaaaaaa0001", "feat/y", "x.go")
b := pr("novox/b", 2, "bbbbbbbbbbbb0002", "feat/y", "y.go")
w.h.PullUpdated(a)
w.h.PullUpdated(b)
w.h.Checked(verdictMoving("novox/a", 1, "aaaaaaaaaaaa0001", "app-a"))
w.h.Checked(verdictMoving("novox/b", 2, "bbbbbbbbbbbb0002", "app-b"))
w.settleAll()
g := w.h.groups["feat/y"]
if g == nil || g.Check == nil {
t.Fatalf("the group is %+v", g)
}
w.h.Checked(CheckedEvent{Group: "feat/y", ID: g.Check.Asked, Verdict: "pass", Summary: "composed"})
w.h.PullMerged(mergedAt(a, "aaaaaaaaaaaa1111", atCC))
w.h.PullMerged(mergedAt(b, "bbbbbbbbbbbb2222", atDu))
batch := Walk{ID: "plan-batch", Repository: "novox/a", Branch: "main", Commit: "aaaaaaaaaaaa1111",
Created: atDu.Add(time.Minute), State: walkBuilding, Tiers: [][]string{{"app-a"}, {"app-b"}},
Modules: map[string]*WalkModule{"app-a": {}, "app-b": {}},
Delivery: &WalkDelivery{Awaits: "mesh-delivery", Merges: []WalkMerge{
{Repository: "novox/a", Commit: "aaaaaaaaaaaa1111"}, {Repository: "novox/b", Commit: "bbbbbbbbbbbb2222"}}}}
w.h.WalkMoved(w.ctl.put(batch))
w.settleAll()
if got := w.ctl.delivers; len(got) != 1 || got[0] != "plan-batch" {
t.Fatalf("the word was asked %v, want once for plan-batch", got)
}
for _, id := range []string{IDOf("novox/a", "aaaaaaaaaaaa0001"), IDOf("novox/b", "bbbbbbbbbbbb0002")} {
if s := w.state(id); s != Delivering {
t.Fatalf("%s is %s", id, s)
}
}
}
// A walk that names its merges answers only those: a delivery whose walk it took over and which it does not name
// is not tied to it, and ends superseded once that is known.
func TestAWalkThatNamesItsMergesAnswersOnlyThose(t *testing.T) {
w := twoMerges(t)
first := w.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w.h.WalkMoved(first)
other := catWalk("plan-2", "ffffffffffff9999", atDu.Add(6*time.Second), "dunst")
other.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: "ffffffffffff9999"}}
w.h.WalkMoved(w.ctl.put(other))
w.h.WalkMoved(w.ctl.put(supersededBy(first, "plan-2")))
w.settleAll()
d := w.delivery(IDOf(cat, headDu))
if d.State != Superseded || d.Walk.ID != "plan-1" {
t.Fatalf("dunst is %s on %s", d.State, d.Walk.ID)
}
for _, asked := range w.ctl.delivers {
if asked == "plan-2" {
t.Fatal("a walk that does not name dunst was let go for it")
}
}
}
// The forge's containment ties a merge to a walk only when the walk moves everything the merge moves and was made
// for a later merge.
func TestContainmentAloneDoesNotTieAMerge(t *testing.T) {
for _, c := range []struct {
name string
modules []string
created time.Time
names bool
}{
{"the later walk moves only its own module", []string{"dunst"}, atDu.Add(5 * time.Second), false},
{"the walk was made for an earlier merge", []string{"claude-code", "dunst"}, atCC.Add(-time.Minute), false},
{"the walk names its merges, and not this one", []string{"claude-code", "dunst"}, atDu.Add(5 * time.Second), true},
} {
t.Run(c.name, func(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atDu.Add(5*time.Second))
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
w.forge.contains[mergeCC+" in "+mergeDu] = true
walk := catWalk("plan-2", mergeDu, c.created, c.modules...)
if c.names {
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeDu}}
}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if d := w.delivery(IDOf(cat, headCC)); d.Walk != nil || len(w.forge.containsAsks) != 0 {
t.Fatalf("it follows %v; the forge was asked %v", d.Walk, w.forge.containsAsks)
}
})
}
}
// After the walk that carried a later commit for it failed, a merge stays published — once — and is said within
// the ten minutes a walk opens within, naming what happened.
func TestAMergeWhoseCarryingWalkFailedWaitsOnceAndIsSaid(t *testing.T) {
w := twoMerges(t)
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
walk = w.ctl.walk("plan-9")
walk.State = walkFailed
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
id := IDOf(cat, headCC)
d := w.delivery(id)
rewalks := 0
for _, tr := range d.Transitions {
if tr.Event == EvRewalk {
rewalks++
}
}
if d.State != Published || rewalks != 1 {
t.Fatalf("it is %s after %d rewalk(s)", d.State, rewalks)
}
w.later(11 * time.Minute)
var line *StalledLine
for _, l := range w.h.Stalled() {
if l.ID == id {
l := l
line = &l
}
}
if line == nil || !strings.Contains(line.Says, "no walk of its own commit") {
t.Fatalf("stalled says %+v", line)
}
}
// A delivery that ended is never tied to a walk after: not one that answers its merge, not the walk that took its
// own over.
func TestADeliveryThatEndedFollowsNoWalk(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
w.ctl.planned = planMoving(cat)
p := pr(cat, 103, "eeeeeeeeeeee0003", "docs/x", "README.md")
w.h.PullUpdated(p)
w.h.PullMerged(mergedAt(p, "eeeeeeeeeeee3333", atCC))
w.settleAll()
nothing := IDOf(cat, "eeeeeeeeeeee0003")
if s := w.state(nothing); s != Delivered {
t.Fatalf("a merge moving nothing is %s", s)
}
w.h.WalkMoved(w.ctl.put(catWalk("plan-4", "eeeeeeeeeeee3333", atCC.Add(time.Minute), "app")))
w.settleAll()
if d := w.delivery(nothing); d.Walk != nil {
t.Fatalf("a delivered merge was tied to %s", d.Walk.ID)
}
w2 := twoMerges(t)
first := w2.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w2.h.WalkMoved(first)
idDu := IDOf(cat, headDu)
if _, err := w2.h.Stop(idDu, "not now", "jochen"); err != nil {
t.Fatal(err)
}
w2.ctl.put(catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "claude-code", "dunst"))
w2.h.WalkMoved(w2.ctl.put(supersededBy(w2.ctl.walk("plan-1"), "plan-2")))
w2.settleAll()
if d := w2.delivery(idDu); d.State != Stopped || d.Walk.ID != "plan-1" {
t.Fatalf("the stopped delivery is %s on %s", d.State, d.Walk.ID)
}
}
// Today's controller, merges A then B minutes apart: B's walk took A's over before it started. A follows it and
// ends covered by B, rather than superseded (ADR 0276 decision 4).
func TestAnEarlierMergeFollowsTheWalkThatTookItsOver(t *testing.T) {
w := twoMerges(t)
first := w.ctl.put(catWalk("plan-1", mergeCC, atCC.Add(5*time.Second), "claude-code"))
w.h.WalkMoved(first)
w.h.WalkMoved(w.ctl.put(catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "claude-code", "dunst")))
w.h.WalkMoved(w.ctl.put(supersededBy(w.ctl.walk("plan-1"), "plan-2")))
w.settleAll()
idCC := IDOf(cat, headCC)
if d := w.delivery(idCC); d.Walk.ID != "plan-2" || len(w.ctl.delivers) != 1 {
t.Fatalf("claude-code follows %s; let go %v", d.Walk.ID, w.ctl.delivers)
}
w.h.WalkMoved(w.ctl.put(done(w.ctl.walk("plan-2"), w.now)))
w.settleAll()
if s := w.state(idCC); s != Covered {
t.Fatalf("claude-code is %s", s)
}
}