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)) }