test: add indexer benchmark and integration test
- BenchmarkIndexRevision: benchmark full nixpkgs indexing - BenchmarkIndexRevisionWithFiles: benchmark with file content storage - TestIndexRevision: integration test for indexer - Uses nixpkgs revision from flake.lock (e6eae2ee...) - Skips if nix-build not available or in short mode Run with: go test -bench=BenchmarkIndexRevision -benchtime=1x -timeout=30m ./internal/nixos/... Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
182
internal/nixos/indexer_test.go
Normal file
182
internal/nixos/indexer_test.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package nixos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"git.t-juice.club/torjus/labmcp/internal/database"
|
||||
)
|
||||
|
||||
// TestNixpkgsRevision is the revision from flake.lock used for testing.
|
||||
const TestNixpkgsRevision = "e6eae2ee2110f3d31110d5c222cd395303343b08"
|
||||
|
||||
// BenchmarkIndexRevision benchmarks indexing a full nixpkgs revision.
|
||||
// This is a slow benchmark that requires nix to be installed.
|
||||
// Run with: go test -bench=BenchmarkIndexRevision -benchtime=1x -timeout=30m ./internal/nixos/...
|
||||
func BenchmarkIndexRevision(b *testing.B) {
|
||||
// Check if nix-build is available
|
||||
if _, err := exec.LookPath("nix-build"); err != nil {
|
||||
b.Skip("nix-build not found, skipping indexer benchmark")
|
||||
}
|
||||
|
||||
// Use in-memory SQLite for the benchmark
|
||||
store, err := database.NewSQLiteStore(":memory:")
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
if err := store.Initialize(ctx); err != nil {
|
||||
b.Fatalf("Failed to initialize store: %v", err)
|
||||
}
|
||||
|
||||
indexer := NewIndexer(store)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Delete any existing revision first (for repeated runs)
|
||||
if rev, _ := store.GetRevision(ctx, TestNixpkgsRevision); rev != nil {
|
||||
store.DeleteRevision(ctx, rev.ID)
|
||||
}
|
||||
|
||||
result, err := indexer.IndexRevision(ctx, TestNixpkgsRevision)
|
||||
if err != nil {
|
||||
b.Fatalf("IndexRevision failed: %v", err)
|
||||
}
|
||||
|
||||
b.ReportMetric(float64(result.OptionCount), "options")
|
||||
b.ReportMetric(float64(result.Duration.Milliseconds()), "ms")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkIndexRevisionWithFiles benchmarks indexing with file content storage.
|
||||
// This downloads the full nixpkgs tarball and stores allowed file types.
|
||||
// Run with: go test -bench=BenchmarkIndexRevisionWithFiles -benchtime=1x -timeout=60m ./internal/nixos/...
|
||||
func BenchmarkIndexRevisionWithFiles(b *testing.B) {
|
||||
// Check if nix-build is available
|
||||
if _, err := exec.LookPath("nix-build"); err != nil {
|
||||
b.Skip("nix-build not found, skipping indexer benchmark")
|
||||
}
|
||||
|
||||
// Use in-memory SQLite for the benchmark
|
||||
store, err := database.NewSQLiteStore(":memory:")
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
if err := store.Initialize(ctx); err != nil {
|
||||
b.Fatalf("Failed to initialize store: %v", err)
|
||||
}
|
||||
|
||||
indexer := NewIndexer(store)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Delete any existing revision first
|
||||
if rev, _ := store.GetRevision(ctx, TestNixpkgsRevision); rev != nil {
|
||||
store.DeleteRevision(ctx, rev.ID)
|
||||
}
|
||||
|
||||
result, err := indexer.IndexRevision(ctx, TestNixpkgsRevision)
|
||||
if err != nil {
|
||||
b.Fatalf("IndexRevision failed: %v", err)
|
||||
}
|
||||
|
||||
fileCount, err := indexer.IndexFiles(ctx, result.Revision.ID, TestNixpkgsRevision)
|
||||
if err != nil {
|
||||
b.Fatalf("IndexFiles failed: %v", err)
|
||||
}
|
||||
|
||||
b.ReportMetric(float64(result.OptionCount), "options")
|
||||
b.ReportMetric(float64(fileCount), "files")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIndexRevision is an integration test for the indexer.
|
||||
// Run with: go test -run=TestIndexRevision -timeout=30m ./internal/nixos/...
|
||||
func TestIndexRevision(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Check if nix-build is available
|
||||
if _, err := exec.LookPath("nix-build"); err != nil {
|
||||
t.Skip("nix-build not found, skipping indexer test")
|
||||
}
|
||||
|
||||
store, err := database.NewSQLiteStore(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
if err := store.Initialize(ctx); err != nil {
|
||||
t.Fatalf("Failed to initialize store: %v", err)
|
||||
}
|
||||
|
||||
indexer := NewIndexer(store)
|
||||
|
||||
t.Logf("Indexing nixpkgs revision %s...", TestNixpkgsRevision[:12])
|
||||
result, err := indexer.IndexRevision(ctx, TestNixpkgsRevision)
|
||||
if err != nil {
|
||||
t.Fatalf("IndexRevision failed: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Indexed %d options in %s", result.OptionCount, result.Duration)
|
||||
|
||||
// Verify we got a reasonable number of options (NixOS has thousands)
|
||||
if result.OptionCount < 1000 {
|
||||
t.Errorf("Expected at least 1000 options, got %d", result.OptionCount)
|
||||
}
|
||||
|
||||
// Verify revision was stored
|
||||
rev, err := store.GetRevision(ctx, TestNixpkgsRevision)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRevision failed: %v", err)
|
||||
}
|
||||
if rev == nil {
|
||||
t.Fatal("Revision not found after indexing")
|
||||
}
|
||||
if rev.OptionCount != result.OptionCount {
|
||||
t.Errorf("Stored option count %d != result count %d", rev.OptionCount, result.OptionCount)
|
||||
}
|
||||
|
||||
// Test searching
|
||||
options, err := store.SearchOptions(ctx, rev.ID, "nginx", database.SearchFilters{Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("SearchOptions failed: %v", err)
|
||||
}
|
||||
if len(options) == 0 {
|
||||
t.Error("Expected to find nginx options")
|
||||
}
|
||||
t.Logf("Found %d nginx options", len(options))
|
||||
|
||||
// Test getting a specific option
|
||||
opt, err := store.GetOption(ctx, rev.ID, "services.nginx.enable")
|
||||
if err != nil {
|
||||
t.Fatalf("GetOption failed: %v", err)
|
||||
}
|
||||
if opt == nil {
|
||||
t.Error("services.nginx.enable not found")
|
||||
} else {
|
||||
t.Logf("services.nginx.enable: type=%s", opt.Type)
|
||||
if opt.Type != "boolean" {
|
||||
t.Errorf("Expected type 'boolean', got %q", opt.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Test getting children
|
||||
children, err := store.GetChildren(ctx, rev.ID, "services.nginx")
|
||||
if err != nil {
|
||||
t.Fatalf("GetChildren failed: %v", err)
|
||||
}
|
||||
if len(children) == 0 {
|
||||
t.Error("Expected services.nginx to have children")
|
||||
}
|
||||
t.Logf("services.nginx has %d direct children", len(children))
|
||||
}
|
||||
Reference in New Issue
Block a user