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