ADR 0483: the controller keeps a key per plan in its build phase; holders read it and take no new check while one is there, but for the oldest check past 30 minutes, one at a time by a let-through key. The queue says why checks wait.
78 lines
3.1 KiB
Go
78 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// Issue 1688 / 1652 AC5 (ADR 0483 clause 5): the queue names, above the waiting checks, the plan they are held for, and says of a
|
|
// check let through that it waited past 30 minutes.
|
|
|
|
func aHeldQueue(now time.Time) link.Queue {
|
|
check := func(id string, ago time.Duration, n int) link.QueuedAsk {
|
|
return link.QueuedAsk{Seq: uint64(n), ID: id, Repository: "ssh://forge/novox/r", AskedAt: now.Add(-ago), State: link.AskWaiting,
|
|
Request: link.BuildRequest{ID: id, Check: &link.CheckRequest{Owner: "novox", Repo: "r", Number: n}}}
|
|
}
|
|
return link.Queue{Seat: link.TheBuildMachine, Asks: []link.QueuedAsk{
|
|
check("build-young", 5*time.Minute, 1),
|
|
check("build-old", 45*time.Minute, 2),
|
|
check("build-older", 50*time.Minute, 3),
|
|
}, Hold: link.CheckHold{Plans: []string{"plan-42"}}}
|
|
}
|
|
|
|
func TestTheQueueNamesThePlanTheChecksAreHeldFor(t *testing.T) {
|
|
now := time.Date(2026, 10, 21, 12, 0, 0, 0, time.UTC)
|
|
text := queueText(aHeldQueue(now), now)
|
|
heldAt := strings.Index(text, "held while delivery plan-42 builds")
|
|
firstAt := strings.Index(text, "build-young")
|
|
if heldAt < 0 || firstAt < 0 || heldAt > firstAt {
|
|
t.Fatalf("the plan the checks are held for is not named above the waiting checks:\n%s", text)
|
|
}
|
|
// Nothing building: nothing held.
|
|
q := aHeldQueue(now)
|
|
q.Hold = link.CheckHold{}
|
|
if strings.Contains(queueText(q, now), "held while") {
|
|
t.Fatal("checks are said held with no delivery building")
|
|
}
|
|
}
|
|
|
|
func TestACheckLetThroughIsSaidToHaveWaitedPastThirtyMinutes(t *testing.T) {
|
|
now := time.Date(2026, 10, 21, 12, 0, 0, 0, time.UTC)
|
|
q := aHeldQueue(now)
|
|
text := queueText(q, now)
|
|
var saidOf []string
|
|
for _, l := range strings.Split(text, "\n") {
|
|
if strings.Contains(l, "waited past 30 minutes, let through") {
|
|
saidOf = append(saidOf, strings.Fields(l)[0])
|
|
}
|
|
}
|
|
if len(saidOf) != 1 || saidOf[0] != "build-older" {
|
|
t.Fatalf("the check let through next (the oldest) is not the one said: %v\n%s", saidOf, text)
|
|
}
|
|
answer := queueAnswerOf(q, now)
|
|
if len(answer.HeldFor) != 1 || answer.HeldFor[0] != "plan-42" {
|
|
t.Errorf("held-for = %v", answer.HeldFor)
|
|
}
|
|
for _, a := range answer.Asks {
|
|
if a.LetThrough != (a.ID == "build-older") {
|
|
t.Errorf("%s let-through = %v", a.ID, a.LetThrough)
|
|
}
|
|
}
|
|
// A check in flight under the let-through key is said too.
|
|
q.Asks = append(q.Asks, link.QueuedAsk{Seq: 9, ID: "build-running", State: link.AskInFlight, On: "ace", AskedAt: now.Add(-time.Hour),
|
|
Started: now.Add(-time.Minute), Request: link.BuildRequest{ID: "build-running", Check: &link.CheckRequest{Owner: "novox", Repo: "r", Number: 9}}})
|
|
q.Hold.LetThrough = &link.LetThrough{Builder: "ace"}
|
|
if !strings.Contains(queueText(q, now), "build-running") || !strings.Contains(queueText(q, now), "waited past 30 minutes, let through\n") {
|
|
t.Errorf("the check in flight let through is not said:\n%s", queueText(q, now))
|
|
}
|
|
// Younger than the bound: none is said.
|
|
young := aHeldQueue(now)
|
|
young.Asks = young.Asks[:1]
|
|
if strings.Contains(queueText(young, now), "waited past") {
|
|
t.Error("a check of 5 minutes is said to have waited past 30")
|
|
}
|
|
}
|