//go:build integration package main import ( "strings" "testing" "time" "github.com/novox/mesh-controller/internal/inventory" ) // An unrelated closed batch is cut into a walk beside an open one, and that walk asks its first tier at once // (issue 973, novox/hq ADR 0407 decision 4): a hotfix is never held by an unrelated delivery. func TestAnUnrelatedBatchWalksBesideAnOpenWalk(t *testing.T) { open := windowed(t) asksWithPaths(t) ctx := t.Context() hear(t, open, repoMerge("one", "c1", t0), t0) cutAt(t, open, t0.Add(90*time.Second)) if ws, _ := walks(t, open); len(ws) != 1 || !ws[0].Open() { t.Fatalf("the first walk is not open: %+v", ws) } heard := t0.Add(100 * time.Second) hear(t, open, lone(repoMerge("two", "c2", heard)), heard) advanceHeld(ctx, open) ws, bs := walks(t, open) if len(ws) != 2 || len(bs) != 0 { t.Fatalf("%d walk(s) and %d batch(es); want the unrelated batch cut beside the open walk", len(ws), len(bs)) } for _, w := range ws { if !w.Open() || w.Waiting() || deferred(w) || !askedAny(w) { t.Errorf("%s (%s) is not walking: open %v, deferred %v, asked %v, note %q", w.ID, w.Named(), w.Open(), deferred(w), askedAny(w), w.Note) } } } // A closed batch related to an open walk is queued behind it with the reason, while one that shares nothing is // cut beside it; the walk that started is never superseded (ADR 0276 decision 4, issue 973). func TestARelatedBatchWaitsBehindTheOpenWalk(t *testing.T) { open := windowed(t) asksWithPaths(t) ctx := t.Context() hear(t, open, repoMerge("one", "c1", t0), t0) cutAt(t, open, t0.Add(90*time.Second)) first := func() inventory.Plan { ws, _ := walks(t, open); return ws[len(ws)-1] }() heard := t0.Add(100 * time.Second) hear(t, open, lone(repoMerge("one", "c2", heard)), heard) hear(t, open, lone(repoMerge("three", "c3", heard.Add(time.Second))), heard.Add(time.Second)) ws, bs := walks(t, open) if len(ws) != 2 || len(bs) != 1 { t.Fatalf("%d walk(s) and %d batch(es); want the unrelated merge walking and the related one queued", len(ws), len(bs)) } b := bs[0] words := batchWords(b, heard) if b.State != inventory.PlanQueued || b.Delivery.Batch.Behind != first.ID || b.CommitOf("novox/one") != "c2" || !strings.Contains(words, "queued behind "+first.ID) || !strings.Contains(words, "both change novox/one") { t.Fatalf("the related batch is %s, behind %q, reading %q", b.State, b.Delivery.Batch.Behind, words) } if got, _ := open.inventory.PlanByID(ctx, first.ID); !got.Open() { t.Fatalf("the started walk was %s by the related merge", got.State) } first.State = inventory.PlanDone if err := open.inventory.SavePlan(ctx, &first); err != nil { t.Fatal(err) } cutAt(t, open, heard.Add(2*time.Second)) if _, bs := walks(t, open); len(bs) != 0 { t.Fatalf("the queued batch was not cut once the walk it waited for ended: %+v", bs) } } // A batch on the controller's own path never walks beside another walk, and nothing walks beside it, whatever // the two share (issue 973): it replaces the controller, so it goes alone. func TestAnOwnPathBatchNeverWalksBeside(t *testing.T) { open := windowed(t) asksWithPaths(t) ctx := t.Context() hear(t, open, repoMerge("one", "c1", t0), t0) cutAt(t, open, t0.Add(90*time.Second)) ws, _ := walks(t, open) first := ws[0] heard := t0.Add(100 * time.Second) hear(t, open, lone(repoMerge("mesh-controller", "k1", heard)), heard) ws, bs := walks(t, open) if len(ws) != 1 || len(bs) != 1 || !bs[0].OwnPath() || bs[0].State != inventory.PlanQueued || bs[0].Delivery.Batch.Behind != first.ID || !strings.Contains(batchWords(bs[0], heard), "own path") { t.Fatalf("an own-path batch beside an open walk: %d walk(s), batches %+v", len(ws), bs) } first.State = inventory.PlanDone if err := open.inventory.SavePlan(ctx, &first); err != nil { t.Fatal(err) } cutAt(t, open, heard.Add(time.Second)) ws, _ = walks(t, open) if len(ws) != 2 || !ws[0].Open() || ws[0].CommitOf("novox/mesh-controller") != "k1" { t.Fatalf("the own-path batch was not cut once the walk ended: %+v", ws) } own := ws[0] // Unrelated to the own-path walk by every footprint, a merge still waits for it. later := heard.Add(time.Minute) hear(t, open, lone(repoMerge("two", "c2", later)), later) ws, bs = walks(t, open) if len(ws) != 2 || len(bs) != 1 || bs[0].State != inventory.PlanQueued || bs[0].Delivery.Batch.Behind != own.ID { t.Fatalf("a merge beside the own-path walk: %d walk(s), batches %+v", len(ws), bs) } }