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