package similarity import "testing" func tempoLibrary() []Item { // A synthetic library where the only varying dimension is "tempo": // songs at 60, 70, 80, ..., 150 BPM. Deterministic and needs no // audio decoding, so it exercises the index math directly. var items []Item for i, bpm := range []float64{60, 70, 80, 90, 100, 110, 120, 130, 140, 150} { items = append(items, Item{ID: idFor(i), Vector: []float64{bpm, 0, 0}}) } return items } func idFor(i int) string { return string(rune('a' + i)) } func TestNearestOrdersByActualDistance(t *testing.T) { idx := Build(tempoLibrary()) // "d" is 90 BPM; its closest neighbors by tempo should be "c" (80) // and "e" (100), in that order (tie broken by index order is fine // since both are exactly 10 BPM away — check the pair, not order). matches := idx.Nearest("d", 2) if len(matches) != 2 { t.Fatalf("matches = %v, want 2", matches) } got := map[string]bool{matches[0].ID: true, matches[1].ID: true} if !got["c"] || !got["e"] { t.Errorf("nearest to d(90bpm) = %v, want {c(80bpm), e(100bpm)}", matches) } } func TestNearestExcludesSelf(t *testing.T) { idx := Build(tempoLibrary()) matches := idx.Nearest("a", 20) for _, m := range matches { if m.ID == "a" { t.Fatal("Nearest must never include the query track itself") } } } func TestNearestUnknownID(t *testing.T) { idx := Build(tempoLibrary()) if matches := idx.Nearest("does-not-exist", 5); matches != nil { t.Errorf("expected nil for unknown id, got %v", matches) } } func TestNearestFarthestIsOppositeEnd(t *testing.T) { idx := Build(tempoLibrary()) matches := idx.Nearest("a", 20) // a = 60bpm, all 9 others if len(matches) != 9 { t.Fatalf("matches = %d, want 9", len(matches)) } farthest := matches[len(matches)-1] if farthest.ID != "j" { // j = 150bpm, the farthest from 60bpm t.Errorf("farthest from a(60bpm) = %v, want j(150bpm)", farthest) } } func TestPathIsMonotonicAlongTheOnlyVaryingDimension(t *testing.T) { idx := Build(tempoLibrary()) path := idx.Path("a", "j", 5) // 60bpm -> 150bpm in 5 hops if len(path) != 5 { t.Fatalf("path = %v, want length 5", path) } if path[0] != "a" || path[len(path)-1] != "j" { t.Fatalf("path endpoints = %q..%q, want a..j", path[0], path[len(path)-1]) } seen := map[string]bool{} for _, id := range path { if seen[id] { t.Fatalf("path repeats %q: %v", id, path) } seen[id] = true } // With tempo as the only varying dimension, a straight-line // interpolation from 60 to 150 BPM should visit strictly increasing // tempos. bpmOf := map[string]float64{"a": 60, "b": 70, "c": 80, "d": 90, "e": 100, "f": 110, "g": 120, "h": 130, "i": 140, "j": 150} for i := 1; i < len(path); i++ { if bpmOf[path[i]] <= bpmOf[path[i-1]] { t.Errorf("path not monotonically increasing in tempo: %v", path) break } } } func TestPathUnknownEndpoint(t *testing.T) { idx := Build(tempoLibrary()) if path := idx.Path("a", "nope", 5); path != nil { t.Errorf("expected nil path for unknown endpoint, got %v", path) } } func TestBuildEmpty(t *testing.T) { idx := Build(nil) if matches := idx.Nearest("a", 5); matches != nil { t.Errorf("expected nil from an empty index, got %v", matches) } } func TestBuildConstantDimensionDoesNotDivideByZero(t *testing.T) { items := []Item{ {ID: "x", Vector: []float64{5, 1}}, {ID: "y", Vector: []float64{5, 2}}, } idx := Build(items) matches := idx.Nearest("x", 5) if len(matches) != 1 || matches[0].ID != "y" { t.Fatalf("matches = %v", matches) } }