a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
138 lines
3.9 KiB
Go
138 lines
3.9 KiB
Go
package recall
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestVerdictKey_OrderIndependent(t *testing.T) {
|
|
k1 := verdictKey("claude", "find X", []string{"a/b.go::Foo", "c/d.go::Bar"})
|
|
k2 := verdictKey("claude", "find X", []string{"c/d.go::Bar", "a/b.go::Foo"})
|
|
assert.Equal(t, k1, k2, "verdict key must be independent of top-K order")
|
|
}
|
|
|
|
func TestVerdictKey_ChangesWithModel(t *testing.T) {
|
|
k1 := verdictKey("claude-haiku-4-5", "q", []string{"a"})
|
|
k2 := verdictKey("claude-opus-4", "q", []string{"a"})
|
|
assert.NotEqual(t, k1, k2, "verdict key must differ when the model differs")
|
|
}
|
|
|
|
func TestJudgeCacheRoundTrip(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
path := filepath.Join(tmp, "judge_cache.json")
|
|
j := &Judge{
|
|
Model: "claude-haiku-4-5",
|
|
APIKey: "test-key",
|
|
CachePath: path,
|
|
}
|
|
j.loadCache()
|
|
j.cacheMu.Lock()
|
|
j.cache["abc"] = true
|
|
j.cache["def"] = false
|
|
j.cacheMu.Unlock()
|
|
require.NoError(t, j.saveCache())
|
|
|
|
// Reload from disk.
|
|
j2 := &Judge{CachePath: path}
|
|
j2.loadCache()
|
|
assert.Equal(t, true, j2.cache["abc"])
|
|
assert.Equal(t, false, j2.cache["def"])
|
|
|
|
// Sanity: on-disk format is valid JSON.
|
|
raw, err := os.ReadFile(path)
|
|
require.NoError(t, err)
|
|
var decoded map[string]bool
|
|
require.NoError(t, json.Unmarshal(raw, &decoded))
|
|
assert.Len(t, decoded, 2)
|
|
}
|
|
|
|
func TestNewJudge_NoAPIKeyReturnsNil(t *testing.T) {
|
|
t.Setenv("ANTHROPIC_API_KEY", "")
|
|
assert.Nil(t, NewJudge("claude-haiku-4-5"))
|
|
}
|
|
|
|
func TestNewJudge_EmptyModelReturnsNil(t *testing.T) {
|
|
t.Setenv("ANTHROPIC_API_KEY", "fake")
|
|
assert.Nil(t, NewJudge(""))
|
|
}
|
|
|
|
func TestApplyJudge_NilIsNoop(t *testing.T) {
|
|
r := Report{Rankers: []RankerResult{
|
|
{Name: "x", Hits: map[int]int{20: 5}, Recall: map[int]float64{20: 0.5}, Cases: 10},
|
|
}}
|
|
before := r.Rankers[0].Hits[20]
|
|
rescued, errs := ApplyJudge(&r, nil)
|
|
assert.Equal(t, 0, rescued)
|
|
assert.Nil(t, errs)
|
|
assert.Equal(t, before, r.Rankers[0].Hits[20])
|
|
}
|
|
|
|
func TestRun_CapturesMisses(t *testing.T) {
|
|
fixture := Fixture{
|
|
Name: "t",
|
|
Cases: []Case{
|
|
{ID: "hit", Query: "q1", Tier: TierExact, Expected: []string{"a"}},
|
|
{ID: "miss", Query: "q2", Tier: TierExact, Expected: []string{"z"}},
|
|
},
|
|
}
|
|
r := staticRanker("x", []string{"a", "b", "c"})
|
|
report := Run(fixture, []Ranker{r}, nil)
|
|
require.Len(t, report.Rankers, 1)
|
|
assert.Len(t, report.Rankers[0].Misses, 1)
|
|
miss := report.Rankers[0].Misses[0]
|
|
assert.Equal(t, "miss", miss.CaseID)
|
|
assert.Equal(t, []string{"z"}, miss.Expected)
|
|
assert.Equal(t, []string{"a", "b", "c"}, miss.Top)
|
|
assert.Nil(t, miss.JudgedHit)
|
|
}
|
|
|
|
func TestApplyJudge_RescuesMissWithStubbedVerdict(t *testing.T) {
|
|
// Shape-level test: we can't hit the real API, so pre-seed the
|
|
// judge cache with a "yes" verdict for a known query+top.
|
|
tmp := t.TempDir()
|
|
cachePath := filepath.Join(tmp, "cache.json")
|
|
query := "find me a writer"
|
|
top := []string{"pkg/x.go::WriteThing"}
|
|
|
|
judge := &Judge{
|
|
Model: "claude-stub",
|
|
APIKey: "stub",
|
|
CachePath: cachePath,
|
|
}
|
|
judge.loadCache()
|
|
judge.cacheMu.Lock()
|
|
judge.cache[verdictKey("claude-stub", query, top)] = true
|
|
judge.cacheMu.Unlock()
|
|
|
|
report := Report{
|
|
Rankers: []RankerResult{{
|
|
Name: "r",
|
|
Cases: 1,
|
|
Hits: map[int]int{1: 0, 5: 0, 20: 0},
|
|
Recall: map[int]float64{1: 0, 5: 0, 20: 0},
|
|
Misses: []Miss{{
|
|
CaseID: "c1",
|
|
Query: query,
|
|
Expected: []string{"pkg/x.go::OtherWriter"},
|
|
Top: top,
|
|
}},
|
|
}},
|
|
}
|
|
rescued, errs := ApplyJudge(&report, judge)
|
|
assert.Empty(t, errs)
|
|
assert.Equal(t, 1, rescued)
|
|
// Judged hits bump the biggest-K bucket (20).
|
|
assert.Equal(t, 1, report.Rankers[0].Hits[20])
|
|
assert.Equal(t, 1.0, report.Rankers[0].Recall[20])
|
|
// R@1 / R@5 are NOT credited by the judge — we don't know the
|
|
// judged rank, so the conservative call is max-K only.
|
|
assert.Equal(t, 0, report.Rankers[0].Hits[1])
|
|
require.NotNil(t, report.Rankers[0].Misses[0].JudgedHit)
|
|
assert.True(t, *report.Rankers[0].Misses[0].JudgedHit)
|
|
}
|