mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate out of date: judged against trunk 4dbd4066ca54, the trunk is now c0c379af2b3c; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 4dbd4066ca54, the trunk is now c0c379af2b3c; judged again on a recheck or when it is merged
A merged change's walk waited minutes on its build behind checks in a first-come queue. The queue now hands deliveries out first, a check that has waited ten minutes before deliveries asked after it, and says each ask's kind; the order is kept in the work queue itself.
125 lines
4.4 KiB
Go
125 lines
4.4 KiB
Go
package link
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// The order the build queue hands asks out in (novox/hq issue 558): a build a walk waits for before any
|
|
// pull request's check, a check that has waited ten minutes before every delivery asked after it, and
|
|
// first come, first served within a kind.
|
|
|
|
func anAsk(seq uint64, id string, asked time.Time, check bool) QueuedAsk {
|
|
r := BuildRequest{ID: id, Repository: "/r"}
|
|
if check {
|
|
r.Check = &CheckRequest{Owner: "novox", Repo: "r"}
|
|
}
|
|
return QueuedAsk{Seq: seq, ID: id, AskedAt: asked, Request: r, State: AskWaiting}
|
|
}
|
|
|
|
func idsOf(asks []QueuedAsk) []string {
|
|
out := []string{}
|
|
for _, a := range asks {
|
|
out = append(out, a.ID)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestADeliveryIsHandedOutBeforeTheChecksAskedBeforeIt(t *testing.T) {
|
|
now := time.Date(2026, 10, 12, 12, 0, 0, 0, time.UTC)
|
|
waiting := []QueuedAsk{
|
|
anAsk(1, "check-a", now.Add(-3*time.Minute), true),
|
|
anAsk(2, "check-b", now.Add(-2*time.Minute), true),
|
|
anAsk(3, "delivery-d", now.Add(-time.Minute), false),
|
|
}
|
|
want := []string{"delivery-d", "check-a", "check-b"}
|
|
if got := idsOf(QueueOrder(waiting, now)); !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("handed out as %v, want %v", got, want)
|
|
}
|
|
// Only the checks ahead of it move behind it; the delivery stays where it is.
|
|
if got := idsOf(toMove(waiting, QueueOrder(waiting, now))); !reflect.DeepEqual(got, []string{"check-a", "check-b"}) {
|
|
t.Fatalf("moved %v", got)
|
|
}
|
|
}
|
|
|
|
func TestACheckThatWaitedTenMinutesGoesBeforeALaterDelivery(t *testing.T) {
|
|
now := time.Date(2026, 10, 12, 12, 0, 0, 0, time.UTC)
|
|
// As the queue stands after a delivery put the young checks behind it, ten minutes ago and since.
|
|
waiting := []QueuedAsk{
|
|
anAsk(5, "delivery-early", now.Add(-12*time.Minute), false),
|
|
anAsk(6, "delivery-later", now.Add(-time.Minute), false),
|
|
anAsk(7, "check-old", now.Add(-11*time.Minute), true),
|
|
anAsk(8, "check-young", now.Add(-9*time.Minute), true),
|
|
}
|
|
want := []string{"delivery-early", "check-old", "delivery-later", "check-young"}
|
|
if got := idsOf(QueueOrder(waiting, now)); !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("handed out as %v, want %v", got, want)
|
|
}
|
|
if got := idsOf(toMove(waiting, QueueOrder(waiting, now))); !reflect.DeepEqual(got, []string{"delivery-later", "check-young"}) {
|
|
t.Fatalf("moved %v", got)
|
|
}
|
|
}
|
|
|
|
func TestAnAskWhoseKindCannotBeToldIsACheck(t *testing.T) {
|
|
for _, r := range []BuildRequest{
|
|
{}, // unreadable: nothing was read from it
|
|
{ID: "build-1", Check: &CheckRequest{}}, // a pull request's check, or a recheck the merge tool asked
|
|
{ID: "build-2", DryRun: true}, // looked at, never delivered
|
|
} {
|
|
if got := KindOf(r); got != KindCheck {
|
|
t.Errorf("%+v is a %s", r, got)
|
|
}
|
|
}
|
|
if got := KindOf(BuildRequest{ID: "build-3", Repository: "/r"}); got != KindDelivery {
|
|
t.Errorf("a build is a %s", got)
|
|
}
|
|
}
|
|
|
|
// Against a real server: two checks wait, a delivery is asked, and the next free holder takes the delivery.
|
|
func TestNatsADeliveryIsTakenBeforeTheChecksQueuedAheadOfIt(t *testing.T) {
|
|
js := aBusWithTheBuildRole(t)
|
|
ctx, stop := context.WithCancel(context.Background())
|
|
defer stop()
|
|
asks := BuildsOn(js, TheBuildMachine)
|
|
now := time.Now()
|
|
for i, r := range []BuildRequest{
|
|
{ID: NewBuildID(now.Add(-3 * time.Minute)), Repository: "/a", Check: &CheckRequest{Owner: "novox", Repo: "a"}},
|
|
{ID: NewBuildID(now.Add(-2 * time.Minute)), Repository: "/b", Check: &CheckRequest{Owner: "novox", Repo: "b"}},
|
|
{ID: NewBuildID(now), Repository: "/d"},
|
|
} {
|
|
if i == 2 {
|
|
// And one nobody can read, whose kind cannot be told: it waits with the checks.
|
|
if _, err := js.Context().Publish(BuildWork(), []byte("not an ask")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := asks.Ask(ctx, r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
took := make(chan string, 4)
|
|
machine := MachineOverNATS(js, "anchor")
|
|
defer machine.Close()
|
|
go func() {
|
|
_ = machine.Take(ctx, func(ctx context.Context, work Build) {
|
|
took <- work.Request().Repository
|
|
_ = work.Announce(ctx, BuildResult{ID: work.Request().ID, On: "anchor", Failed: "no"})
|
|
_ = work.Done()
|
|
})
|
|
}()
|
|
var order []string
|
|
for len(order) < 3 {
|
|
select {
|
|
case r := <-took:
|
|
order = append(order, r)
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatalf("taken so far: %v", order)
|
|
}
|
|
}
|
|
if want := []string{"/d", "/a", "/b"}; !reflect.DeepEqual(order, want) {
|
|
t.Fatalf("taken as %v, want %v", order, want)
|
|
}
|
|
}
|