192 lines
4.9 KiB
Go
192 lines
4.9 KiB
Go
package vector
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/embedding"
|
|
)
|
|
|
|
func TestSQLite_New_NilDB_异常(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := NewSQLite(context.Background(), nil, 4, testLogger()); !errors.Is(err, ErrNilDB) {
|
|
t.Fatalf("NewSQLite(nil db) err = %v, want ErrNilDB", err)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_New_InvalidDim_异常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
if _, err := NewSQLite(ctx, openTestDB(t), 0, testLogger()); !errors.Is(err, ErrInvalidDim) {
|
|
t.Fatalf("NewSQLite(dim 0) err = %v, want ErrInvalidDim", err)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_AddSearch_正常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 3)
|
|
|
|
for _, e := range []struct {
|
|
id string
|
|
v Vector
|
|
}{
|
|
{id: "cat", v: Vector{1, 0.5, 0}},
|
|
{id: "dog", v: Vector{0.5, 1, 0}},
|
|
{id: "bird", v: Vector{0, 0, 1}},
|
|
} {
|
|
if err := s.Add(ctx, e.id, e.v); err != nil {
|
|
t.Fatalf("Add(%s): %v", e.id, err)
|
|
}
|
|
}
|
|
|
|
hits, err := s.Search(ctx, Vector{1, 0, 0}, 2)
|
|
if err != nil {
|
|
t.Fatalf("Search: %v", err)
|
|
}
|
|
if len(hits) != 2 {
|
|
t.Fatalf("got %d hits, want 2", len(hits))
|
|
}
|
|
if hits[0].ID != "cat" || hits[1].ID != "dog" {
|
|
t.Fatalf("hits = [%s %s], want [cat dog]", hits[0].ID, hits[1].ID)
|
|
}
|
|
if hits[0].Score <= hits[1].Score {
|
|
t.Fatalf("top score not descending: %v <= %v", hits[0].Score, hits[1].Score)
|
|
}
|
|
if hits[0].Score <= 0.89 || hits[0].Score >= 0.9 {
|
|
t.Errorf("cat score = %v, want ~0.894", hits[0].Score)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Add_Upsert_正常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 3)
|
|
|
|
if err := s.Add(ctx, "a", Vector{1, 0, 0}); err != nil {
|
|
t.Fatalf("Add first: %v", err)
|
|
}
|
|
if err := s.Add(ctx, "a", Vector{0, 1, 0}); err != nil {
|
|
t.Fatalf("Add update: %v", err)
|
|
}
|
|
|
|
hits, err := s.Search(ctx, Vector{0, 1, 0}, 5)
|
|
if err != nil {
|
|
t.Fatalf("Search: %v", err)
|
|
}
|
|
if len(hits) != 1 || hits[0].ID != "a" {
|
|
t.Fatalf("hits = %+v, want single a", hits)
|
|
}
|
|
if hits[0].Score < 0.99 {
|
|
t.Fatalf("a score = %v, want ~1.0 after upsert", hits[0].Score)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Add_MissingID_异常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
if err := s.Add(ctx, " ", Vector{1, 0, 0, 0}); !errors.Is(err, ErrMissingID) {
|
|
t.Fatalf("Add(blank id) err = %v, want ErrMissingID", err)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Add_DimMismatch_异常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
if err := s.Add(ctx, "x", Vector{1, 2}); !errors.Is(err, ErrDimMismatch) {
|
|
t.Fatalf("Add(wrong dim) err = %v, want ErrDimMismatch", err)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Search_Empty_正常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
|
|
hits, err := s.Search(ctx, Vector{1, 0, 0, 0}, 3)
|
|
if err != nil {
|
|
t.Fatalf("Search empty: %v", err)
|
|
}
|
|
if hits == nil || len(hits) != 0 {
|
|
t.Fatalf("Search(empty) = %v, want non-nil empty slice", hits)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Search_TopKNonPositive_正常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
if err := s.Add(ctx, "a", Vector{1, 0, 0, 0}); err != nil {
|
|
t.Fatalf("Add: %v", err)
|
|
}
|
|
|
|
for _, k := range []int{0, -1} {
|
|
hits, err := s.Search(ctx, Vector{1, 0, 0, 0}, k)
|
|
if err != nil {
|
|
t.Fatalf("Search(topK %d): %v", k, err)
|
|
}
|
|
if len(hits) != 0 {
|
|
t.Fatalf("Search(topK %d) = %v, want empty", k, hits)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Search_DimMismatch_异常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
if _, err := s.Search(ctx, Vector{1, 0}, 3); !errors.Is(err, ErrDimMismatch) {
|
|
t.Fatalf("Search(wrong dim) err = %v, want ErrDimMismatch", err)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Embed_正常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
s.withEmbedder(&mockEmbedder{dim: 4})
|
|
|
|
vs, err := s.Embed(ctx, []string{"hello", "world"})
|
|
if err != nil {
|
|
t.Fatalf("Embed: %v", err)
|
|
}
|
|
if len(vs) != 2 || len(vs[0]) != 4 || len(vs[1]) != 4 {
|
|
t.Fatalf("Embed => %d vectors, want 2 x 4-dim", len(vs))
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Embed_NoEmbedder_异常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
if _, err := s.Embed(ctx, []string{"hi"}); !errors.Is(err, ErrNoEmbedder) {
|
|
t.Fatalf("Embed(no embedder) err = %v, want ErrNoEmbedder", err)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Embed_EmbedderError_异常(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
s := newInMemorySQLite(t, 4)
|
|
boom := errors.New("embed boom")
|
|
s.withEmbedder(&mockEmbedder{
|
|
dim: 4,
|
|
fn: func(context.Context, []string) ([]embedding.Vector, error) { return nil, boom },
|
|
})
|
|
|
|
if _, err := s.Embed(ctx, []string{"hi"}); !errors.Is(err, boom) {
|
|
t.Fatalf("Embed(driver error) err = %v, want wrap of %v", err, boom)
|
|
}
|
|
}
|
|
|
|
func TestSQLite_Close_正常(t *testing.T) {
|
|
t.Parallel()
|
|
s := newInMemorySQLite(t, 4)
|
|
if err := s.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
}
|