package api
import (
"context"
"testing"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/work"
"craftdesign.group/quarry/internal/zitcli"
)
// The ops panel can only show the lanes it is handed, and the two this
// package can see are not the ones that shed. On zeitforge the ingest
// lane refused tcl 7.8 times an hour for a week while this panel — the
// panel for exactly that question — listed the store and archive lanes,
// both idle.
func TestTheOpsPanelListsEveryLaneTheProcessOwns(t *testing.T) {
ingest := work.NewLane("ingest", 3, 22)
d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{}),
Lanes: func() []zitcli.Lane {
st := ingest.Stats()
return []zitcli.Lane{{Name: st.Name, Bound: st.Limit,
Running: int(st.InFlight), Admitted: int(st.Admitted),
Refused: int(st.Shed), Waiting: st.Waiting, Queue: st.Queue}}
}}
r := ops.NewRegistry()
if err := RegisterAll(r, d); err != nil {
t.Fatal(err)
}
got, err := r.Invoke(ops.WithUser(context.Background(), "dan"), "ops.status", opsStatusIn{})
if err != nil {
t.Fatal(err)
}
names := map[string]bool{}
var ingestLane zitcli.Lane
for _, l := range got.(opsStatusOut).Lanes {
names[l.Name] = true
if l.Name == "ingest" {
ingestLane = l
}
}
for _, want := range []string{"store subprocesses", "archive builds", "ingest"} {
if !names[want] {
t.Errorf("FAIL: the panel does not list the %q lane: %v", want, names)
}
}
// The depth is what distinguishes "the queue is full" from "slots
// are held too long" — running and refused alone cannot.
if ingestLane.Queue != 22 || ingestLane.Bound != 3 {
t.Errorf("FAIL: the ingest lane reports bound %d queue %d, want 3 and 22",
ingestLane.Bound, ingestLane.Queue)
}
}
// A forge with no extra lanes still reports the two it has, rather than
// failing because nothing was injected.
func TestAForgeWithNoExtraLanesStillReportsItsOwn(t *testing.T) {
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{})}); err != nil {
t.Fatal(err)
}
got, err := r.Invoke(ops.WithUser(context.Background(), "dan"), "ops.status", opsStatusIn{})
if err != nil {
t.Fatal(err)
}
if n := len(got.(opsStatusOut).Lanes); n != 2 {
t.Errorf("FAIL: %d lanes with none injected, want the two internal ones", n)
}
}
// A lane that actually shed reports it, so the number the fault names
// and the number the panel shows are the same number.
func TestASaturatedLaneReportsWhatItRefused(t *testing.T) {
lane := work.NewLane("ingest", 1, 0)
release := make(chan struct{})
admitted := make(chan struct{})
go lane.Do(context.Background(), func() error {
close(admitted)
<-release
return nil
})
<-admitted
// The one slot is held, the queue is zero, so this is refused.
if err := lane.Do(context.Background(), func() error { return nil }); err == nil {
t.Fatal("FAIL: a full lane with no queue admitted a second caller")
}
close(release)
if st := lane.Stats(); st.Shed != 1 {
t.Errorf("FAIL: Stats reports %d shed, want 1", st.Shed)
}
}