Files
mesh-catalog/modules/mesh-issues/cmd/mesh-issues/reserve_test.go
T
jschoubben 1b7f79f761
mesh/merge-gate pass: builds mesh-issues, mesh-knowledge, records → novox; no bus step; every machine composes with the change as it did without (4 of 4 co…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh-issues: a counter not started never starts without the knowledge base's answer, floor or no floor (review of #214)
2026-10-11 12:44:45 +02:00

404 lines
14 KiB
Go

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