| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package searcheval |
| |
| import ( |
| "context" |
| "errors" |
| "math" |
| "testing" |
| ) |
| |
| type mockSearcher struct { |
| results map[string][]SearchResult |
| err error |
| } |
| |
| func (m *mockSearcher) Search(ctx context.Context, query string, opts SearchOptions) ([]SearchResult, error) { |
| if m.err != nil { |
| return nil, m.err |
| } |
| return m.results[query], nil |
| } |
| |
| func TestRunEvaluation(t *testing.T) { |
| queries := []GoldenQuery{ |
| { |
| Query: "http client", |
| ExpectedPackages: []ExpectedPackage{ |
| {PackagePath: "net/http"}, |
| }, |
| }, |
| { |
| Query: "json parser", |
| ExpectedPackages: []ExpectedPackage{ |
| {PackagePath: "encoding/json"}, |
| }, |
| }, |
| } |
| |
| searcher := &mockSearcher{ |
| results: map[string][]SearchResult{ |
| "http client": { |
| {PackagePath: "net/http"}, |
| }, |
| "json parser": { |
| {PackagePath: "github.com/foo/bar"}, |
| {PackagePath: "encoding/json"}, |
| }, |
| }, |
| } |
| |
| opts := SearchOptions{Limit: 5} |
| |
| t.Run("successful evaluation", func(t *testing.T) { |
| report, err := RunEvaluation(context.Background(), searcher, queries, opts, opts, opts) |
| if err != nil { |
| t.Fatalf("RunEvaluation error: %v", err) |
| } |
| |
| if len(report.QueryReports) != 2 { |
| t.Fatalf("len(QueryReports) = %d, want 2", len(report.QueryReports)) |
| } |
| |
| // http client: net/http is rank 1 -> RR = 1.0 |
| if math.Abs(report.QueryReports[0].KeywordRR-1.0) > 1e-6 { |
| t.Errorf("QueryReports[0].KeywordRR = %f, want 1.0", report.QueryReports[0].KeywordRR) |
| } |
| |
| // json parser: encoding/json is rank 2 -> RR = 0.5 |
| if math.Abs(report.QueryReports[1].KeywordRR-0.5) > 1e-6 { |
| t.Errorf("QueryReports[1].KeywordRR = %f, want 0.5", report.QueryReports[1].KeywordRR) |
| } |
| |
| // Mean = (1.0 + 0.5) / 2 = 0.75 |
| if math.Abs(report.MeanKeyword-0.75) > 1e-6 { |
| t.Errorf("report.MeanKeyword = %f, want 0.75", report.MeanKeyword) |
| } |
| }) |
| |
| t.Run("context canceled", func(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| cancel() // cancel context immediately |
| |
| _, err := RunEvaluation(ctx, searcher, queries, opts, opts, opts) |
| if !errors.Is(err, context.Canceled) { |
| t.Errorf("RunEvaluation on canceled context = %v, want context.Canceled", err) |
| } |
| }) |
| |
| t.Run("search error propagation", func(t *testing.T) { |
| errSearch := errors.New("database connection failed") |
| failingSearcher := &mockSearcher{err: errSearch} |
| |
| report, err := RunEvaluation(context.Background(), failingSearcher, queries, opts, opts, opts) |
| if err != nil { |
| t.Fatalf("RunEvaluation error: %v", err) |
| } |
| |
| if len(report.QueryReports) != 2 { |
| t.Fatalf("len(QueryReports) = %d, want 2", len(report.QueryReports)) |
| } |
| |
| if report.QueryReports[0].Error == nil { |
| t.Errorf("QueryReports[0].Error = nil, want error") |
| } |
| }) |
| } |