99 lines
3.0 KiB
Go
99 lines
3.0 KiB
Go
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))
|
|
}
|
|
}
|