mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
Two catalogue merges 18 s apart were walked by one walk at the later commit, which superseded the first; this owner tied that walk to the dunst delivery the supersession had ended and to no other, so nothing gave it its word and claude-code was held for a person with a reason naming neither cause. A delivery now follows the newest walk that answers its merge: one naming it (delivery.merges, the controller's batches), its own commit, the walk that took its own over (taken_over_by, or the superseded note), or, from a controller that names no merges, an open walk of its branch made for a later merge, moving all it moves, whose commit the forge says contains it. Only its own walk's record moves an ended delivery. The word is asked once per walk, when every delivery it carries is at its turn, a group's members on one walk together. An earlier merge carried by a later commit ends covered with the walk, and waits for a walk of its own when it fails. A merge no walk answers is held with the true reason and listed at once; a waiting walk no live delivery holds is listed as unheld; a merge in an assembling or queued batch is shown, not held. Replay R362 fails before.
435 lines
18 KiB
Go
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)
|
|
}
|
|
}
|