Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

360 lines
12 KiB
Go

package rerank
import (
"testing"
"time"
"github.com/zzet/gortex/internal/graph"
)
// --- BM25 + Semantic signals -----------------------------------------
func TestBM25SignalRankToScore(t *testing.T) {
cases := map[int]float64{
-1: 0,
0: 1.0, // top hit normalised to 1.0
1: 61.0 / 62.0, // 1 / (60 + 2) over the top-norm
60: 61.0 / 121.0, // half-decay at rank 60
999: 61.0 / 1060.0, // far tail still > 0
}
for rank, want := range cases {
got := BM25Signal{}.Contribute("q", &Candidate{TextRank: rank}, &Context{})
if !floatNear(got, want, 1e-9) {
t.Errorf("rank=%d: got %v, want %v", rank, got, want)
}
}
}
func TestSemanticSignalRankToScore(t *testing.T) {
got := SemanticSignal{}.Contribute("q", &Candidate{VectorRank: 0}, &Context{})
if !floatNear(got, 1.0, 1e-9) {
t.Errorf("top vector rank: got %v want 1.0", got)
}
got = SemanticSignal{}.Contribute("q", &Candidate{VectorRank: -1}, &Context{})
if got != 0 {
t.Errorf("absent vector rank: got %v want 0", got)
}
}
// --- FanIn / FanOut --------------------------------------------------
func TestFanInOutSignalsNormalised(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "A", graph.KindFunction)
b := mustNode(g, "f.go::B", "B", graph.KindFunction)
c := mustNode(g, "f.go::C", "C", graph.KindFunction)
// A has 3 callers, B has 1, C has 0.
g.AddEdge(&graph.Edge{From: "x1", To: a.ID, Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: "x2", To: a.ID, Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: "x3", To: a.ID, Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: "x4", To: b.ID, Kind: graph.EdgeCalls})
// A has 2 callees, B has 0, C has 1.
g.AddEdge(&graph.Edge{From: a.ID, To: "y1", Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: a.ID, To: "y2", Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: c.ID, To: "y3", Kind: graph.EdgeCalls})
ctx := &Context{Graph: g}
cands := []*Candidate{candidateFor(a, 0, -1), candidateFor(b, 1, -1), candidateFor(c, 2, -1)}
ctx.prepare(cands)
fi := FanInSignal{}
if got := fi.Contribute("q", cands[0], ctx); !floatNear(got, 1.0, 1e-9) {
t.Errorf("A fan-in: got %v want 1.0", got)
}
if got := fi.Contribute("q", cands[2], ctx); got != 0 {
t.Errorf("C fan-in: got %v want 0", got)
}
fo := FanOutSignal{}
if got := fo.Contribute("q", cands[0], ctx); !floatNear(got, 1.0, 1e-9) {
t.Errorf("A fan-out: got %v want 1.0", got)
}
}
func TestFanInSignalNoGraphReturnsZero(t *testing.T) {
a := &graph.Node{ID: "x", Name: "x", Kind: graph.KindFunction}
if got := (FanInSignal{}).Contribute("q", &Candidate{Node: a}, &Context{}); got != 0 {
t.Errorf("no-graph fan-in: got %v want 0", got)
}
}
// --- Churn -----------------------------------------------------------
func TestChurnSignalFromMeta(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "A", graph.KindFunction)
b := mustNode(g, "f.go::B", "B", graph.KindFunction)
a.Meta = map[string]any{"churn": 5}
b.Meta = map[string]any{"churn": 1}
ctx := &Context{Graph: g}
cands := []*Candidate{candidateFor(a, 0, -1), candidateFor(b, 1, -1)}
ctx.prepare(cands)
cs := ChurnSignal{}
if got := cs.Contribute("q", cands[0], ctx); !floatNear(got, 1.0, 1e-9) {
t.Errorf("hottest churn: got %v want 1.0", got)
}
if got := cs.Contribute("q", cands[1], ctx); !(got > 0 && got < 1) {
t.Errorf("medium churn: got %v want in (0,1)", got)
}
}
func TestChurnSignalChurnOfHookWins(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "A", graph.KindFunction)
a.Meta = map[string]any{"churn": 1}
ctx := &Context{Graph: g, ChurnOf: func(id string) int {
if id == a.ID {
return 99
}
return 0
}}
cands := []*Candidate{candidateFor(a, 0, -1)}
ctx.prepare(cands)
if got := (ChurnSignal{}).Contribute("q", cands[0], ctx); !floatNear(got, 1.0, 1e-9) {
t.Errorf("ChurnOf override should produce 1.0, got %v", got)
}
}
// --- Community ------------------------------------------------------
func TestCommunitySignalTopicCluster(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "A", graph.KindFunction)
b := mustNode(g, "f.go::B", "B", graph.KindFunction)
c := mustNode(g, "f.go::C", "C", graph.KindFunction)
communities := map[string]string{a.ID: "c1", b.ID: "c1", c.ID: "c2"}
ctx := &Context{Graph: g, CommunityOf: func(id string) string { return communities[id] }}
cands := []*Candidate{candidateFor(a, 0, -1), candidateFor(b, 1, -1), candidateFor(c, 2, -1)}
ctx.prepare(cands)
cs := CommunitySignal{}
// A and B share c1 (cluster size 2), C alone in c2 (size 1).
// maxCommunityCount = 2, so A and B get 2/2 = 1.0, C gets 0
// (cluster-of-one is not a cluster).
if got := cs.Contribute("q", cands[0], ctx); !floatNear(got, 1.0, 1e-9) {
t.Errorf("A in topic cluster: got %v want 1.0", got)
}
if got := cs.Contribute("q", cands[2], ctx); got != 0 {
t.Errorf("C alone: got %v want 0", got)
}
}
func TestCommunitySignalRepoLocality(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "A", graph.KindFunction)
b := mustNode(g, "f.go::B", "B", graph.KindFunction)
a.RepoPrefix = "myrepo"
b.RepoPrefix = "otherrepo"
ctx := &Context{Graph: g, RepoPrefix: "myrepo"}
cands := []*Candidate{candidateFor(a, 0, -1), candidateFor(b, 1, -1)}
ctx.prepare(cands)
cs := CommunitySignal{}
if got := cs.Contribute("q", cands[0], ctx); !floatNear(got, 1.0, 1e-9) {
t.Errorf("same-repo: got %v want 1.0", got)
}
if got := cs.Contribute("q", cands[1], ctx); got != 0 {
t.Errorf("other-repo: got %v want 0", got)
}
}
func TestCommunitySignalProjectLocalityFallback(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "A", graph.KindFunction)
a.RepoPrefix = "repo1"
a.ProjectID = "myproj"
ctx := &Context{Graph: g, ProjectID: "myproj"}
cands := []*Candidate{candidateFor(a, 0, -1)}
ctx.prepare(cands)
if got := (CommunitySignal{}).Contribute("q", cands[0], ctx); !floatNear(got, 0.7, 1e-9) {
t.Errorf("same-project: got %v want 0.7", got)
}
}
// --- MinHash --------------------------------------------------------
func TestMinHashSignalCohesion(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "A", graph.KindFunction)
b := mustNode(g, "f.go::B", "B", graph.KindFunction)
c := mustNode(g, "f.go::C", "C", graph.KindFunction)
g.AddEdge(&graph.Edge{From: a.ID, To: b.ID, Kind: graph.EdgeSimilarTo, Meta: map[string]any{"similarity": 0.8}})
g.AddEdge(&graph.Edge{From: b.ID, To: a.ID, Kind: graph.EdgeSimilarTo, Meta: map[string]any{"similarity": 0.8}})
ctx := &Context{Graph: g}
cands := []*Candidate{candidateFor(a, 0, -1), candidateFor(b, 1, -1), candidateFor(c, 2, -1)}
ctx.prepare(cands)
cs := MinHashSignal{}
// A and B are both in cluster with sim=0.8; C is isolated.
if got := cs.Contribute("q", cands[0], ctx); !floatNear(got, 0.8, 1e-9) {
t.Errorf("A in similar cluster: got %v want 0.8", got)
}
if got := cs.Contribute("q", cands[2], ctx); got != 0 {
t.Errorf("C isolated: got %v want 0", got)
}
}
// --- Recency --------------------------------------------------------
func TestRecencySignalDecay(t *testing.T) {
now := time.Now().Unix()
day := int64(86400)
cases := []struct {
dtDays int64
want float64
}{
{0, 1.0},
{30, 0.5},
{60, 0.25},
{120, 0.0625},
}
for _, c := range cases {
n := &graph.Node{ID: "x", Kind: graph.KindFunction, Meta: map[string]any{
"last_authored": map[string]any{"timestamp": now - c.dtDays*day},
}}
got := (RecencySignal{}).Contribute("q", &Candidate{Node: n}, &Context{Now: now})
if !floatNear(got, c.want, 1e-3) {
t.Errorf("dt=%dd: got %v want %v", c.dtDays, got, c.want)
}
}
}
func TestRecencySignalNoMetaReturnsZero(t *testing.T) {
n := &graph.Node{ID: "x", Kind: graph.KindFunction}
if got := (RecencySignal{}).Contribute("q", &Candidate{Node: n}, &Context{Now: time.Now().Unix()}); got != 0 {
t.Errorf("no last_authored: got %v want 0", got)
}
}
// --- Signature signals ----------------------------------------------
func TestAPISignatureSignalOverlap(t *testing.T) {
n := &graph.Node{
ID: "x", Kind: graph.KindFunction, Name: "validateToken",
Meta: map[string]any{"signature": "func validateToken(t string) error"},
}
got := (APISignatureSignal{}).Contribute("validate token", &Candidate{Node: n}, &Context{})
if !floatNear(got, 1.0, 1e-9) {
t.Errorf("full overlap: got %v want 1.0", got)
}
got = (APISignatureSignal{}).Contribute("hash password", &Candidate{Node: n}, &Context{})
if got != 0 {
t.Errorf("no overlap: got %v want 0", got)
}
}
func TestAPISignatureSignalIgnoresNonFunctions(t *testing.T) {
n := &graph.Node{ID: "x", Kind: graph.KindType, Name: "User"}
if got := (APISignatureSignal{}).Contribute("user", &Candidate{Node: n}, &Context{}); got != 0 {
t.Errorf("type kind: got %v want 0", got)
}
}
func TestTypeSignatureSignalNameOverlap(t *testing.T) {
n := &graph.Node{ID: "x", Kind: graph.KindType, Name: "UserRecord"}
got := (TypeSignatureSignal{}).Contribute("user record", &Candidate{Node: n}, &Context{})
if !floatNear(got, 1.0, 1e-9) {
t.Errorf("type name overlap: got %v want 1.0", got)
}
}
func TestTypeSignatureIgnoresFunctions(t *testing.T) {
n := &graph.Node{ID: "x", Kind: graph.KindFunction, Name: "ParseUser"}
if got := (TypeSignatureSignal{}).Contribute("user", &Candidate{Node: n}, &Context{}); got != 0 {
t.Errorf("function kind: got %v want 0", got)
}
}
// --- Feedback (session) ---------------------------------------------
func TestFeedbackSignalMaxOfSources(t *testing.T) {
n := &graph.Node{ID: "x", Kind: graph.KindFunction}
ctx := &Context{
FeedbackOf: func(string) float64 { return 0.5 },
FrecencyBoostOf: func(string) float64 { return 1.5 }, // saturates → 1.0
ComboBoostOf: func(string) float64 { return 1.0 }, // no boost
}
got := (FeedbackSignal{}).Contribute("q", &Candidate{Node: n}, ctx)
if !floatNear(got, 1.0, 1e-9) {
t.Errorf("max-merge feedback: got %v want 1.0", got)
}
}
func TestFeedbackSignalNegativeFeedbackIgnored(t *testing.T) {
n := &graph.Node{ID: "x", Kind: graph.KindFunction}
ctx := &Context{
FeedbackOf: func(string) float64 { return -1.0 },
}
if got := (FeedbackSignal{}).Contribute("q", &Candidate{Node: n}, ctx); got != 0 {
t.Errorf("negative feedback: got %v want 0", got)
}
}
func TestFeedbackSignalAbsentHooksReturnsZero(t *testing.T) {
n := &graph.Node{ID: "x", Kind: graph.KindFunction}
if got := (FeedbackSignal{}).Contribute("q", &Candidate{Node: n}, &Context{}); got != 0 {
t.Errorf("no hooks: got %v want 0", got)
}
}
// --- End-to-end mini integration ------------------------------------
func TestPipelineIntegratesAllSignalsWithoutPanic(t *testing.T) {
g := newTestGraph()
a := mustNode(g, "f.go::A", "validateToken", graph.KindFunction)
a.Meta = map[string]any{
"signature": "func validateToken(t string) error",
"churn": 3,
"last_authored": map[string]any{"timestamp": time.Now().Unix()},
}
a.RepoPrefix = "myrepo"
b := mustNode(g, "f.go::B", "B", graph.KindFunction)
b.RepoPrefix = "otherrepo"
g.AddEdge(&graph.Edge{From: "x1", To: a.ID, Kind: graph.EdgeCalls})
g.AddEdge(&graph.Edge{From: a.ID, To: b.ID, Kind: graph.EdgeSimilarTo, Meta: map[string]any{"similarity": 0.9}})
ctx := &Context{
Graph: g,
RepoPrefix: "myrepo",
CommunityOf: func(string) string { return "c1" },
ChurnOf: func(string) int { return 0 },
FeedbackOf: func(string) float64 { return 0.3 },
FrecencyBoostOf: func(string) float64 { return 1.2 },
ComboBoostOf: func(string) float64 { return 1.0 },
}
p := NewDefault()
cands := []*Candidate{candidateFor(a, 0, 0), candidateFor(b, 1, 1)}
out := p.Rerank("validate token", cands, ctx)
if len(out) != 2 {
t.Fatalf("expected 2 results, got %d", len(out))
}
if out[0].Node.ID != a.ID {
t.Errorf("expected A first (every signal favors it), got %s", out[0].Node.ID)
}
// Every signal should have produced a value for A.
for _, name := range AllSignalNames() {
if _, ok := out[0].Signals[name]; !ok {
t.Errorf("expected signal %q recorded", name)
}
}
}
func floatNear(a, b, eps float64) bool {
d := a - b
if d < 0 {
d = -d
}
return d <= eps
}