|
1 | 1 | package search |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "math" |
4 | 5 | "testing" |
| 6 | + |
| 7 | + "github.com/XiaoConstantine/sgrep/pkg/store" |
| 8 | + "github.com/XiaoConstantine/sgrep/pkg/util" |
5 | 9 | ) |
6 | 10 |
|
7 | 11 | func TestDecomposeQuery(t *testing.T) { |
@@ -29,22 +33,22 @@ func TestDecomposeQuery(t *testing.T) { |
29 | 33 |
|
30 | 34 | func TestDecomposeDocument(t *testing.T) { |
31 | 35 | tests := []struct { |
32 | | - name string |
33 | | - content string |
34 | | - minSegs int |
35 | | - maxSegs int |
| 36 | + name string |
| 37 | + content string |
| 38 | + minSegs int |
| 39 | + maxSegs int |
36 | 40 | }{ |
37 | 41 | { |
38 | | - name: "empty", |
39 | | - content: "", |
40 | | - minSegs: 0, |
41 | | - maxSegs: 0, |
| 42 | + name: "empty", |
| 43 | + content: "", |
| 44 | + minSegs: 0, |
| 45 | + maxSegs: 0, |
42 | 46 | }, |
43 | 47 | { |
44 | | - name: "single line", |
45 | | - content: "func main() {}", |
46 | | - minSegs: 1, |
47 | | - maxSegs: 1, |
| 48 | + name: "single line", |
| 49 | + content: "func main() {}", |
| 50 | + minSegs: 1, |
| 51 | + maxSegs: 1, |
48 | 52 | }, |
49 | 53 | { |
50 | 54 | name: "multiple functions", |
@@ -221,3 +225,51 @@ func TestSegmentCache(t *testing.T) { |
221 | 225 | // At least one of the original keys should be evicted |
222 | 226 | // Note: eviction is simple and clears half, so behavior may vary |
223 | 227 | } |
| 228 | + |
| 229 | +func TestPrepareQueryTerms(t *testing.T) { |
| 230 | + embeddings := [][]float32{ |
| 231 | + {0.25, -0.5, 0.75}, |
| 232 | + {-0.25, 0.5, -0.75}, |
| 233 | + } |
| 234 | + |
| 235 | + terms := prepareQueryTerms(embeddings) |
| 236 | + if len(terms) != len(embeddings) { |
| 237 | + t.Fatalf("prepareQueryTerms returned %d terms, want %d", len(terms), len(embeddings)) |
| 238 | + } |
| 239 | + |
| 240 | + for i, term := range terms { |
| 241 | + if len(term.embedding) != len(embeddings[i]) { |
| 242 | + t.Fatalf("term %d embedding len = %d, want %d", i, len(term.embedding), len(embeddings[i])) |
| 243 | + } |
| 244 | + if diff := math.Abs(term.sum - sumFloat32(embeddings[i])); diff > 1e-9 { |
| 245 | + t.Fatalf("term %d sum diff = %.12f", i, diff) |
| 246 | + } |
| 247 | + } |
| 248 | +} |
| 249 | + |
| 250 | +func TestMaxSimPreparedInt8MatchesCurrent(t *testing.T) { |
| 251 | + query := util.NormalizeVector([]float32{0.9, -0.3, 0.2, 0.1}) |
| 252 | + segments := make([]store.ColBERTSegment, 3) |
| 253 | + |
| 254 | + rawSegments := [][]float32{ |
| 255 | + util.NormalizeVector([]float32{0.8, -0.25, 0.25, 0.15}), |
| 256 | + util.NormalizeVector([]float32{-0.4, 0.9, -0.1, 0.05}), |
| 257 | + util.NormalizeVector([]float32{0.2, -0.1, 0.95, -0.05}), |
| 258 | + } |
| 259 | + |
| 260 | + for i, emb := range rawSegments { |
| 261 | + quantized, scale, min := util.QuantizeInt8(emb) |
| 262 | + segments[i] = store.ColBERTSegment{ |
| 263 | + SegmentIdx: i, |
| 264 | + EmbeddingInt8: quantized, |
| 265 | + QuantScale: scale, |
| 266 | + QuantMin: min, |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | + current := maxSimInt8(query, segments) |
| 271 | + prepared := maxSimPreparedInt8(prepareQueryTerms([][]float32{query})[0], segments) |
| 272 | + if diff := math.Abs(current - prepared); diff > 1e-7 { |
| 273 | + t.Fatalf("prepared scorer mismatch: current %.12f prepared %.12f diff %.12f", current, prepared, diff) |
| 274 | + } |
| 275 | +} |
0 commit comments