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)
|
|
}
|
|
}
|