Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
__debug_bin**
/CLAUDE.md
/commit.msg
.claude/
.vibe-vault.toml
example/constitution-search/constitution-search
46 changes: 46 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.DEFAULT_GOAL := help

PREFIX ?= $(HOME)/.local

##@ General
.PHONY: help
help: ## Show this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
@echo ""
@echo "Quick start: make build && make test"

##@ Build
.PHONY: build
build: ## Build all packages
go build ./...

##@ Test
.PHONY: test
test: ## Run unit tests (skip integration)
go test -short -cover ./...

.PHONY: integration
integration: ## Run all tests including integration
go test -cover -count=1 ./...

.PHONY: bench
bench: ## Run benchmarks
go test -bench=. -benchmem -run='^$$' ./...

##@ Quality
.PHONY: vet
vet: ## Run go vet
go vet ./...

.PHONY: check
check: vet test ## Run vet + unit tests

##@ Install
.PHONY: install
install: build ## Build and install to PREFIX
go install ./...

##@ Clean
.PHONY: clean
clean: ## Remove build artifacts
go clean ./...
28 changes: 28 additions & 0 deletions distance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,34 @@ func TestCosineSimilarity(t *testing.T) {
require.InDelta(t, 0, CosineDistance(a, b), 0.000001)
}

func TestRegisterDistanceFunc(t *testing.T) {
custom := func(a, b []float32) float32 { return 42 }
RegisterDistanceFunc("custom_test", custom)

name, ok := distanceFuncToName(custom)
require.True(t, ok)
require.Equal(t, "custom_test", name)

// Clean up
delete(distanceFuncs, "custom_test")
}

func TestDistanceFuncToName(t *testing.T) {
name, ok := distanceFuncToName(CosineDistance)
require.True(t, ok)
require.Equal(t, "cosine", name)

name, ok = distanceFuncToName(EuclideanDistance)
require.True(t, ok)
require.Equal(t, "euclidean", name)
}

func TestDistanceFuncToName_Unknown(t *testing.T) {
unknown := func(a, b []float32) float32 { return 0 }
_, ok := distanceFuncToName(unknown)
require.False(t, ok)
}

func BenchmarkEuclideanDistance(b *testing.B) {
v1 := randFloats(1536)
v2 := randFloats(1536)
Expand Down
83 changes: 83 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package hnsw
import (
"bytes"
"cmp"
"math/rand"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -178,6 +179,88 @@ func TestSavedGraph(t *testing.T) {
requireGraphApproxEquals(t, g1.Graph, g2.Graph)
}

func TestGraph_ExportUnregisteredDistance(t *testing.T) {
g := newTestGraph[int]()
g.Distance = func(a, b []float32) float32 { return 0 }
g.Add(Node[int]{Key: 1, Value: Vector{1.0}})

buf := &bytes.Buffer{}
err := g.Export(buf)
require.Error(t, err)
require.Contains(t, err.Error(), "must be registered")
}

func TestGraph_ImportUnknownDistance(t *testing.T) {
// Build a valid export first, then tamper with the distance name
g := newTestGraph[int]()
g.Add(Node[int]{Key: 1, Value: Vector{1.0}})

buf := &bytes.Buffer{}
err := g.Export(buf)
require.NoError(t, err)

// Replace "euclidean" with "bogus_fn\x00\x00" in the binary
data := buf.Bytes()
dataStr := string(data)
// Just import from a fresh buffer with bad distance
badBuf := &bytes.Buffer{}
// Write version, M, Ml, EfSearch, then a bad distance name
_, err = multiBinaryWrite(badBuf, encodingVersion, 6, 0.5, 20, "nonexistent")
require.NoError(t, err)
_ = dataStr // suppress unused

g2 := &Graph[int]{}
err = g2.Import(badBuf)
require.Error(t, err)
require.Contains(t, err.Error(), "unknown distance function")
}

func TestGraph_ImportBadVersion(t *testing.T) {
buf := &bytes.Buffer{}
_, err := multiBinaryWrite(buf, 999, 6, 0.5, 20, "euclidean")
require.NoError(t, err)

g := &Graph[int]{}
err = g.Import(buf)
require.Error(t, err)
require.Contains(t, err.Error(), "incompatible encoding version")
}

func TestGraph_ImportTruncated(t *testing.T) {
g := &Graph[int]{}
buf := &bytes.Buffer{}
err := g.Import(buf)
require.Error(t, err)
}

func TestGraph_ExportImportStringKeys(t *testing.T) {
g1 := &Graph[string]{
M: 6,
Distance: CosineDistance,
Ml: 0.5,
EfSearch: 20,
Rng: rand.New(rand.NewSource(0)),
}
g1.Add(
Node[string]{Key: "hello", Value: Vector{1, 0}},
Node[string]{Key: "world", Value: Vector{0, 1}},
)

buf := &bytes.Buffer{}
err := g1.Export(buf)
require.NoError(t, err)

g2 := &Graph[string]{}
err = g2.Import(buf)
require.NoError(t, err)

requireGraphApproxEquals(t, g1, g2)

vec, ok := g2.Lookup("hello")
require.True(t, ok)
require.Equal(t, Vector{1, 0}, vec)
}

const benchGraphSize = 100

func BenchmarkGraph_Import(b *testing.B) {
Expand Down
Loading
Loading