Files
mesh-catalog/modules/mesh-delivery/cmd/mesh-delivery/checkask_test.go
T
jochen d1de0edd4a
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
mesh/delivery delivered
Ask the check of a delivery that waits for one nobody asked (hq issue 290)
A pull request's head announced again from the bus's history at
mesh-delivery's first start was proposed with no verdict and nothing
ever asked its check: the controller had taken that announcement long
before, and stalled raised it after an hour for the operator.

A proposed delivery with no verdict and no check asked is now asked
through delivery-check once it has waited past a grace longer than a
check takes; an announcement carrying its head's decided gate status
takes it. The proposed bound runs from the ask, and H2's close may
re-ask once (a table row) before the delivery is the operator's.
2026-10-07 11:44:21 +02:00

171 lines
6.7 KiB
Go

package main
import (
"strings"
"testing"
"time"
)
// A delivery that waits for its check gets one (novox/hq issue 290): a head adopted while it already waited —
// its announcement heard again from the bus's history, which the controller took long ago — has its check asked
// by this owner; so does one whose announcement the controller missed. Once asked, the proposed bound means
// "asked and not answered", and healer H2 may ask it once more before it is the operator's.
// The live case: a pull request opened before merge checks existed, announced again at this owner's first
// start, read back proposed after a restart. Its check is asked at the first tick, once, for that head alone.
func TestAProposedDeliveryAdoptedWithNoVerdictHasItsCheckAsked(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/hq", head)
w.h.PullUpdated(pr("novox/hq", 94, head, "issues/245-246", "04-ISSUES/245/00-report.md"))
w.settleAll()
if len(w.ctl.checks) != 0 {
t.Fatalf("a head just announced was asked at once, before the controller's own check had time: %v", w.ctl.checks)
}
// A restart, hours later: read back proposed, no verdict, nothing asked.
again := newWorldOver(t, w.store, w.ctl, w.forge, w.now.Add(10*time.Hour))
if s := again.state(id); s != Proposed {
t.Fatalf("read back, it is %s", s)
}
again.settleAll()
if len(w.ctl.checks) != 1 || w.ctl.checks[0] != ":"+id {
t.Fatalf("its check was asked %v, wanted once for its head alone", w.ctl.checks)
}
d := again.delivery(id)
if d.CheckAsk == nil || d.CheckAsk.ID != "check-1" || d.CheckAsk.By != byOwner || d.State != Proposed {
t.Fatalf("the ask is not kept: %+v (%s)", d.CheckAsk, d.State)
}
if notes := w.forge.notesOn(head); len(notes) == 0 || !strings.Contains(notes[len(notes)-1], "its check asked") {
t.Fatalf("the ask is not noted on the head: %q", notes)
}
// Asked, it is not stalled: the bound runs from the ask.
if st := again.h.Stalled(); len(st) != 0 {
t.Fatalf("a check just asked is stalled: %+v", st)
}
// Asked once: the ticks after do not ask again.
again.later(30 * time.Minute)
again.settleAll()
if len(w.ctl.checks) != 1 {
t.Fatalf("a check asked and in flight was asked again: %v", w.ctl.checks)
}
// The verdict arrives: checked, then ready, by itself.
again.h.Checked(verdict("novox/hq", 94, head, "pass"))
if s := again.state(id); s != Ready {
t.Fatalf("its verdict left it %s", s)
}
}
// The same head adopted at a first start with no restart: the grace passes, and its check is asked.
func TestAMissedAnnouncementHasItsCheckAskedAfterTheGrace(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.later(checkGrace - time.Minute)
w.settleAll()
if len(w.ctl.checks) != 0 {
t.Fatalf("asked within the grace: %v", w.ctl.checks)
}
// The controller does not answer: nothing is kept as asked, and it is tried again a minute on.
w.later(2 * time.Minute)
w.ctl.down = true
w.settleAll()
if d := w.delivery(id); d.CheckAsk != nil {
t.Fatalf("an ask the controller did not take is kept as asked: %+v", d.CheckAsk)
}
w.ctl.down = false
w.settleAll()
if len(w.ctl.checks) != 0 {
t.Fatalf("asked again within the minute: %v", w.ctl.checks)
}
w.later(time.Minute)
w.settleAll()
if len(w.ctl.checks) != 1 || w.delivery(id).CheckAsk == nil {
t.Fatalf("a missed announcement was not asked once the grace passed: %v", w.ctl.checks)
}
// A verdict that arrived in the meantime, or a delivery past its proposal, is never asked.
other := IDOf("novox/app", "bbbbbbbbbbbb2222")
w.h.PullUpdated(pr("novox/app", 8, "bbbbbbbbbbbb2222", "feat/y"))
w.h.Checked(verdict("novox/app", 8, "bbbbbbbbbbbb2222", "fail"))
w.later(time.Hour)
w.settleAll()
if w.state(other) != Rejected || len(w.ctl.checks) != 1 {
t.Fatalf("a head with a verdict was asked: %s %v", w.state(other), w.ctl.checks)
}
}
// An announcement heard again carrying its head's decided verdict takes it; a pending one does not.
func TestAnAnnouncementCarryingItsHeadsVerdictTakesIt(t *testing.T) {
w := newWorld(t)
p := pr("novox/app", 7, head, "feat/x", "src/a.go")
p.HeadChecks = map[string]string{"mesh/merge-gate": "success", "mesh/repo-check": "success"}
w.h.PullUpdated(p)
if s := w.state(IDOf("novox/app", head)); s != Ready {
t.Fatalf("a head whose gate passed on the forge is %s", s)
}
q := pr("novox/app", 8, "bbbbbbbbbbbb2222", "feat/y")
q.HeadChecks = map[string]string{"mesh/merge-gate": "pending"}
w.h.PullUpdated(q)
if s := w.state(IDOf("novox/app", "bbbbbbbbbbbb2222")); s != Proposed {
t.Fatalf("a pending gate on the forge left it %s", s)
}
}
// Asked and not answered past the bound: stalled, and H2's close re-asks once — the second close is refused
// by the table, and the delivery is the operator's.
func TestH2ReasksAStalledCheckOnceThenItIsTheOperators(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.later(checkGrace + time.Minute)
w.settleAll()
if len(w.ctl.checks) != 1 {
t.Fatalf("asked %v", w.ctl.checks)
}
if _, err := w.h.Close(id, "too early"); err == nil {
t.Fatal("re-asked within the bound")
}
w.later(Bounds[Proposed].For + time.Minute)
st := w.h.Stalled()
if len(st) != 1 || st[0].ID != id || !strings.Contains(st[0].H2, "re-ask") {
t.Fatalf("stalled %+v", st)
}
// The controller does not take the re-ask: nothing is spent.
w.ctl.down = true
if _, err := w.h.Close(id, "delivery stalled"); err == nil {
t.Fatal("a re-ask the controller did not take was said done")
}
if d := w.delivery(id); d.CheckAsk.Reasked != 0 {
t.Fatal("a re-ask that was not made was spent")
}
w.ctl.down = false
said, err := w.h.Close(id, "delivery stalled")
if err != nil || !strings.Contains(said, "re-asked") {
t.Fatalf("H2's close: %q %v", said, err)
}
d := w.delivery(id)
if len(w.ctl.checks) != 2 || d.State != Proposed || d.CheckAsk.Reasked != 1 || d.CheckAsk.By != byH2 ||
d.Transitions[len(d.Transitions)-1].Event != EvReask {
t.Fatalf("after the re-ask: %v %s %+v", w.ctl.checks, d.State, d.CheckAsk)
}
if len(w.h.Stalled()) != 0 {
t.Fatal("a check just re-asked is stalled")
}
// Still not answered: stalled again, and the operator's.
w.later(Bounds[Proposed].For + time.Minute)
st = w.h.Stalled()
if len(st) != 1 || !strings.Contains(st[0].H2, "operator's") {
t.Fatalf("stalled after the re-ask %+v", st)
}
if _, err := w.h.Close(id, "delivery stalled"); err == nil || !strings.Contains(err.Error(), "once already") {
t.Fatalf("a second re-ask: %v", err)
}
if len(w.ctl.checks) != 2 {
t.Fatalf("a second re-ask reached the controller: %v", w.ctl.checks)
}
// The verdict, late, still settles it.
w.h.Checked(verdict("novox/app", 7, head, "pass"))
if s := w.state(id); s != Ready {
t.Fatalf("a late verdict left it %s", s)
}
}