the record's own tests: never-asked, bounds, fan-out (V-564)

This commit is contained in:
2026-08-06 00:52:28 +04:00
parent 0558dfed0f
commit b56e0e6248
+98
View File
@@ -0,0 +1,98 @@
package decision
import (
"context"
"sync"
"testing"
"time"
)
func TestFinishNamesTheNeverAsked(t *testing.T) {
ctx, rec := With(context.Background(), "какая погода в риме?")
Expect(ctx, StageQuery, []string{"calendar", "weather", "search", "kiwix"})
Note(ctx, Claim{Stage: StageQuery, Claimant: "calendar", Outcome: Declined})
Note(ctx, Claim{Stage: StageQuery, Claimant: "weather", Outcome: Won})
rec.Finish(time.Now())
outcomes := map[string]string{}
for _, c := range rec.Claims {
outcomes[c.Claimant] = c.Outcome
}
if outcomes["weather"] != Won || rec.Winner != StageQuery+":weather" {
t.Errorf("winner = %q, weather = %q", rec.Winner, outcomes["weather"])
}
if outcomes["calendar"] != Declined {
t.Errorf("calendar = %q, want a decline", outcomes["calendar"])
}
// The two below the winner never looked, and saying so is the whole point.
for _, name := range []string{"search", "kiwix"} {
if outcomes[name] != NeverAsked {
t.Errorf("%s = %q, want %q", name, outcomes[name], NeverAsked)
}
}
}
// A real 0.0 confidence must not read as "this claimant has no score".
func TestScoredKeepsAZeroScore(t *testing.T) {
c := Scored(StageRoute, "classifier", "chat", 0, LostOnScore, "")
if !c.HasScore || c.Score != 0 {
t.Errorf("claim = %+v", c)
}
}
func TestNoteIfUnclaimedYieldsToARealWinner(t *testing.T) {
ctx, rec := With(context.Background(), "x")
Note(ctx, Claim{Stage: StageQuery, Claimant: "weather", Outcome: Won})
rec.NoteIfUnclaimed(Claim{Stage: StageAction, Claimant: "action-handler"})
if rec.Winner != StageQuery+":weather" {
t.Errorf("winner = %q, want the query source", rec.Winner)
}
}
// A context with no record must cost nothing and crash nothing: that is what
// makes the claim sites safe to leave in every test and every fixture run.
func TestNoRecorderIsANoOp(t *testing.T) {
ctx := context.Background()
Note(ctx, Claim{Claimant: "x", Outcome: Won})
Expect(ctx, StageQuery, []string{"y"})
if From(ctx) != nil {
t.Error("bare context reported a record")
}
From(ctx).NoteIfUnclaimed(Claim{Claimant: "z"})
if rec := From(ctx).Finish(time.Now()); rec != nil {
t.Error("finishing a nil record produced one")
}
}
func TestRingIsBoundedAndNewestFirst(t *testing.T) {
r := NewRing()
for i := 0; i < ringSize+5; i++ {
r.Push(&Record{Utterance: string(rune('a' + i))})
}
got := r.Recent(ringSize + 10)
if len(got) != ringSize {
t.Fatalf("kept %d records, want %d", len(got), ringSize)
}
if got[0].Utterance != string(rune('a'+ringSize+4)) {
t.Errorf("newest = %q", got[0].Utterance)
}
}
// A query source may fan out to goroutines of its own, so two of them noting at
// once must not race. Run under -race, which is where this earns its keep.
func TestConcurrentNotes(t *testing.T) {
ctx, rec := With(context.Background(), "x")
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
Note(ctx, Claim{Stage: StageQuery, Claimant: "fanout", Outcome: Declined})
}()
}
wg.Wait()
if len(rec.Claims) != 8 {
t.Errorf("recorded %d claims, want 8", len(rec.Claims))
}
}