package main import ( "encoding/json" "errors" "sort" "strings" "sync" "testing" ) // The tracker hands out decision and research numbers too (novox/hq ADR 0275 rule 8, ADR 0290 rule 5, issue // 424): one counter per sequence, advanced like the issue counter, started above every number the knowledge base // holds (hq issue 488) and the operator's floor, and a reserved number is spent forever. // held is the knowledge base as reserve asks it (knowledge_highest, hq issue 488): the highest number it holds // of each kind, or an error. type held struct { mu sync.Mutex highest map[string]uint64 err error asks int } func (k *held) Highest() (map[string]uint64, error) { k.mu.Lock() defer k.mu.Unlock() k.asks++ if k.err != nil { return nil, k.err } out := map[string]uint64{} for kind, n := range k.highest { out[kind] = n } return out, nil } func (k *held) set(kind string, n uint64) { k.mu.Lock() defer k.mu.Unlock() k.highest[kind] = n } // unread is hq's folders, which reserve never reads for a number: the records leave hq (hq issue 426). type unread struct{ lists int } func (u *unread) Standing() (string, error) { return "1ea678c", nil } func (u *unread) Read(string) (string, string, error) { return "", "", errors.New("not read") } func (u *unread) List(string) ([]string, []string, error) { u.lists++ return nil, nil, errors.New("reserve read hq's folders") } // heldToday is the knowledge base as it stands: decisions to 0291, research to 045. func heldToday() *held { return &held{highest: map[string]uint64{KindDecision: 291, KindResearch: 45}} } func reserveHarness(t *testing.T) (*harness, *held) { t.Helper() h := newHarness(t) k := heldToday() hq := &unread{} h.Records = hq h.Highest = k.Highest t.Cleanup(func() { if hq.lists != 0 { t.Errorf("reserve read hq's folders %d time(s)", hq.lists) } }) return h, k } func (h *harness) reserve(t *testing.T, kind string) uint64 { t.Helper() out, err := h.Reserve(ReserveAsk{Kind: kind}) if err != nil { t.Fatalf("reserve %s: %v", kind, err) } return out.Number } func (m *memory) counterOf(t *testing.T, key string) uint64 { t.Helper() e, _ := m.Get(CountersState, key) if e == nil { return 0 } var n uint64 if err := json.Unmarshal(e.Value, &n); err != nil { t.Fatalf("the counter %s holds %s", key, e.Value) } return n } func TestTheFirstReservedNumberIsAboveTheHighestTheKnowledgeBaseHolds(t *testing.T) { h, _ := reserveHarness(t) if got := h.reserve(t, KindDecision); got != 292 { t.Fatalf("the first decision number is %d, want 292: one above 0291", got) } if got := h.reserve(t, KindResearch); got != 46 { t.Fatalf("the first research number is %d, want 46: one above 045", got) } if got := h.reserve(t, KindDecision); got != 293 { t.Fatalf("the second decision number is %d, want 293", got) } if h.mem.counter(t) != 0 { t.Fatal("a reservation moved the issue counter") } } // An open branch the tracker cannot read holds a higher number: the operator's floor says it. func TestTheCounterStartsAboveTheFloorWhenItIsHigher(t *testing.T) { h, _ := reserveHarness(t) h.settings.DecisionFloor = 293 h.settings.ResearchFloor = 40 // below what the record holds: the record wins if got := h.reserve(t, KindDecision); got != 294 { t.Fatalf("got %d, want 294: one above the floor 293", got) } if got := h.reserve(t, KindResearch); got != 46 { t.Fatalf("got %d, want 46: the floor 40 is below 045", got) } } func TestTheCounterNeverStartsBelowAReservationAlreadyKept(t *testing.T) { h, _ := reserveHarness(t) h.mem.put(ReservationsState, ReservationKey(KindDecision, 310), Reservation{Kind: KindDecision, Number: 310}) if got := h.reserve(t, KindDecision); got != 311 { t.Fatalf("got %d, want 311: the counter started below a reservation kept", got) } } // A number the knowledge base holds above the counter — imported, or taken by hand while sessions still did — // raises it: no number at or below one held is handed out. func TestARecordAboveTheCounterRaisesIt(t *testing.T) { h, k := reserveHarness(t) h.reserve(t, KindDecision) // 292, the counter started k.set(KindDecision, 300) if got := h.reserve(t, KindDecision); got != 301 { t.Fatalf("got %d, want 301: one above 0300", got) } } func TestTheCounterStartsOnlyOnceAndTheFloorIsThenNotRead(t *testing.T) { h, _ := reserveHarness(t) h.reserve(t, KindDecision) // 292 h.settings.DecisionFloor = 1 if got := h.reserve(t, KindDecision); got != 293 { t.Fatalf("got %d, want 293", got) } } func TestManyConcurrentReservationsGetDistinctConsecutiveNumbers(t *testing.T) { h, _ := reserveHarness(t) const each = 40 var mu sync.Mutex got := map[string][]uint64{} var wg sync.WaitGroup for i := 0; i < 2*each; i++ { kind := KindDecision if i%2 == 1 { kind = KindResearch } wg.Add(1) go func() { defer wg.Done() out, err := h.Reserve(ReserveAsk{Kind: kind, For: "concurrent"}) if err != nil { t.Errorf("reserve %s: %v", kind, err) return } mu.Lock() got[kind] = append(got[kind], out.Number) mu.Unlock() }() } wg.Wait() for kind, first := range map[string]uint64{KindDecision: 292, KindResearch: 46} { ns := got[kind] sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] }) if len(ns) != each { t.Fatalf("%s: %d numbers, want %d", kind, len(ns), each) } for i, n := range ns { if n != first+uint64(i) { t.Fatalf("%s: numbers %v are not %d.. distinct and consecutive", kind, ns, first) } if _, err := h.mem.Get(ReservationsState, ReservationKey(kind, n)); err != nil { t.Fatal(err) } } if c := h.mem.counterOf(t, kind); c != first+each-1 { t.Fatalf("%s counter at %d, want %d", kind, c, first+each-1) } } if keys, _ := h.mem.Keys(ReservationsState); len(keys) != 2*each { t.Fatalf("%d reservations kept, want %d", len(keys), 2*each) } } func TestARefusedReservationSpendsNothing(t *testing.T) { h, k := reserveHarness(t) refused := []ReserveAsk{ {Kind: ""}, {Kind: "issue"}, {Kind: "Decision"}, {Kind: "design"}, {Kind: KindDecision, For: "two\nlines"}, {Kind: KindDecision, For: strings.Repeat("x", MostTitle+1)}, {Kind: KindResearch, For: "the key is sk-ant-api03-abcdefghijklmnopqrstuvwxyz"}, {Kind: KindResearch, For: "t\x1b[31m"}, } for i, a := range refused { if out, err := h.Reserve(a); err == nil { t.Errorf("case %d (%+v) was reserved: %+v", i, a, out) } } keys, _ := h.mem.Keys(CountersState) kept, _ := h.mem.Keys(ReservationsState) if len(keys) != 0 || len(kept) != 0 || len(h.said()) != 0 || k.asks != 0 { t.Fatalf("a refused reservation left counters %v, reservations %v, events %v, knowledge base asks %d", keys, kept, h.said(), k.asks) } if got := h.reserve(t, KindDecision); got != 292 { t.Fatalf("the first valid reservation got %d, want 292", got) } } // Before its counter starts, a sequence must know what the knowledge base holds, or have the operator's floor: // with neither, nothing is handed out and nothing is spent. Once started, the counter alone is enough, and says it // was not checked. func TestAKnowledgeBaseThatDoesNotAnswerRefusesTheFirstNumberAndNotTheNext(t *testing.T) { h, k := reserveHarness(t) k.err = errors.New("nothing answers mesh-knowledge.knowledge_highest") if _, err := h.Reserve(ReserveAsk{Kind: KindDecision}); err == nil || !strings.Contains(err.Error(), "knowledge_highest") { t.Fatalf("a first reservation with the knowledge base silent: %v", err) } if h.mem.counterOf(t, KindDecision) != 0 || len(h.said()) != 0 { t.Fatal("a refused first reservation started the counter or said something") } if keys, _ := h.mem.Keys(ReservationsState); len(keys) != 0 { t.Fatal("a refused first reservation spent a number") } k.err = nil h.reserve(t, KindDecision) // 292 k.err = errors.New("nothing answers mesh-knowledge.knowledge_highest") out, err := h.Reserve(ReserveAsk{Kind: KindDecision}) if err != nil || out.Number != 293 { t.Fatalf("got %+v (%v), want 293 from the started counter", out, err) } // Failure is loud: the answer says the number was not checked against the knowledge base. if !strings.Contains(out.Warning, "knowledge_highest") || !strings.Contains(out.Warning, "unchecked") { t.Fatalf("a number unchecked against the knowledge base answered warning %q", out.Warning) } k.err = nil if out, err := h.Reserve(ReserveAsk{Kind: KindDecision}); err != nil || out.Warning != "" { t.Fatalf("a reserve with the knowledge base read answered %+v (%v): no warning expected", out, err) } } // A counter not started never starts without the knowledge base's answer, floor or no floor: a floor alone // cannot say a number above it is free. Nothing is spent. With the answer, the floor still raises the start. func TestAFloorDoesNotStartACounterTheKnowledgeBaseDoesNotAnswerFor(t *testing.T) { h, k := reserveHarness(t) k.err = errors.New("the knowledge base holds no record yet") h.settings.ResearchFloor = 47 if out, err := h.Reserve(ReserveAsk{Kind: KindResearch}); err == nil || !strings.Contains(err.Error(), "the knowledge base holds no record yet") { t.Fatalf("a research number from the floor alone: %+v %v", out, err) } if keys, _ := h.mem.Keys(ReservationsState); len(keys) != 0 || h.mem.counterOf(t, KindResearch) != 0 { t.Fatal("a refused first reservation spent a number or started the counter") } k.err = nil if got := h.reserve(t, KindResearch); got != 48 { t.Fatalf("got %d, want 48: one above the floor 47, above the 045 held", got) } } // A knowledge base that holds no record of a kind answers nothing for it: a 0 would start the counter at 1. func TestAKindTheKnowledgeBaseHoldsNoneOfIsNoAnswer(t *testing.T) { h, k := reserveHarness(t) k.set(KindResearch, 0) if _, err := h.Reserve(ReserveAsk{Kind: KindResearch}); err == nil || !strings.Contains(err.Error(), "holds no research") { t.Fatalf("a research number from a knowledge base that holds none: %v", err) } if got := h.reserve(t, KindDecision); got != 292 { t.Fatalf("got %d", got) } } func TestAReservationIsKeptWithWhoWhenAndWhatFor(t *testing.T) { h, _ := reserveHarness(t) out, err := h.Reserve(ReserveAsk{Kind: KindDecision, For: " the knowledge base holds decisions "}) if err != nil { t.Fatal(err) } if out.Kind != KindDecision || out.Number != 292 || out.Prefix != "0292" || out.By != Unproven || out.For != "the knowledge base holds decisions" || !out.Announced { t.Fatalf("answered %+v", out) } e, err := h.mem.Get(ReservationsState, "decision-0292") if err != nil || e == nil { t.Fatalf("the reservation is not kept: %v", err) } var kept Reservation if err := json.Unmarshal(e.Value, &kept); err != nil { t.Fatal(err) } if kept.Kind != KindDecision || kept.Number != 292 || kept.By != Unproven || kept.At.IsZero() || kept.For != "the knowledge base holds decisions" { t.Fatalf("kept %+v", kept) } ev := h.said() if len(ev) != 1 || ev[0].Event != EventReserved || ev[0].Body["kind"] != KindDecision || ev[0].Body["number"] != float64(292) || ev[0].Body["by"] != Unproven || ev[0].Body["for"] != kept.For { t.Fatalf("said %+v", ev) } r, err := h.Reserve(ReserveAsk{Kind: KindResearch}) if err != nil || r.Prefix != "046" { t.Fatalf("a research number's prefix: %+v, %v", r, err) } } func TestAReservationWhoseEventCannotBeSaidIsKeptAndSaysSo(t *testing.T) { h, _ := reserveHarness(t) h.emitErr = errors.New("the bus is away") out, err := h.Reserve(ReserveAsk{Kind: KindResearch}) if err != nil { t.Fatal(err) } if out.Announced || out.Warning == "" || out.Number != 46 { t.Fatalf("answered %+v", out) } } // A reservation whose write cannot be confirmed is refused, and the number stays spent. func TestANumberWhoseReservationIsNotKeptIsSaidAndNeverHandedOutAgain(t *testing.T) { h, _ := reserveHarness(t) h.mem.fail[ReservationsState] = errors.New("the bus did not answer") if got := h.reserve(t, KindDecision); got != 292 { t.Fatalf("one failure is tried again: got %d", got) } failing := &failingReservations{memory: h.mem, fail: 1000} h.Store = failing _, err := h.Reserve(ReserveAsk{Kind: KindDecision}) if err == nil || !strings.Contains(err.Error(), "293") || !strings.Contains(err.Error(), "spent") { t.Fatalf("a reservation never kept: %v", err) } h.Store = h.mem if got := h.reserve(t, KindDecision); got != 294 { t.Fatalf("got %d, want 294: a number is never handed out twice", got) } } type failingReservations struct { *memory fail int } func (f *failingReservations) Create(state, key string, value any) (uint64, error) { if state == ReservationsState && f.fail > 0 { f.fail-- return 0, errors.New("the bus did not answer") } return f.memory.Create(state, key, value) } func TestTheReserveVerbReadsItsArgumentsStrictlyAndAnswersTheContract(t *testing.T) { h, _ := reserveHarness(t) for _, args := range []map[string]any{ {}, {"kind": 5}, {"kind": KindDecision, "for": 7}, {"kind": KindDecision, "by": "operator"}, {"kind": KindDecision, "number": 400}, } { if out, err := h.call(t, "reserve", args); err == nil { t.Errorf("reserve %v was taken: %+v", args, out) } } out, err := h.call(t, "reserve", map[string]any{"kind": "research", "for": "a spike"}) if err != nil { t.Fatal(err) } raw, _ := json.Marshal(out) var answer map[string]any _ = json.Unmarshal(raw, &answer) if answer["kind"] != "research" || answer["number"] != float64(46) || strings.Contains(string(raw), "operator") { t.Fatalf("answered %s", raw) } } func TestAReservationKeyNamesTheKindAndTheRecordsPrefix(t *testing.T) { for _, c := range []struct { kind string n uint64 want string }{{KindDecision, 292, "decision-0292"}, {KindResearch, 46, "research-046"}, {KindDecision, 10000, "decision-10000"}} { if got := ReservationKey(c.kind, c.n); got != c.want { t.Errorf("%s %d: %s, want %s", c.kind, c.n, got, c.want) } } }