Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

99 lines
2.8 KiB
Go

package analysis
import (
"fmt"
"testing"
"github.com/zzet/gortex/internal/graph"
)
// clique adds n function nodes with the given id prefix and a call
// edge between every pair — a maximally cohesive cluster.
func clique(g *graph.Graph, prefix string, n int) []string {
ids := make([]string, n)
for i := 0; i < n; i++ {
id := fmt.Sprintf("%s%d", prefix, i)
ids[i] = id
g.AddNode(&graph.Node{ID: id, Kind: graph.KindFunction, Name: id, FilePath: prefix + ".go"})
}
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
g.AddEdge(&graph.Edge{From: ids[i], To: ids[j], Kind: graph.EdgeCalls})
}
}
return ids
}
func TestSpectralClusters_SeparatesTwoCliques(t *testing.T) {
g := graph.New()
a := clique(g, "a", 12)
b := clique(g, "b", 12)
// A single bridge edge — the Fiedler cut should run through it.
g.AddEdge(&graph.Edge{From: a[0], To: b[0], Kind: graph.EdgeCalls})
res := SpectralClusters(g)
if len(res.Communities) != 2 {
t.Fatalf("expected 2 communities, got %d: %+v", len(res.Communities), res.Communities)
}
// No community may mix an a-clique node with a b-clique node.
for _, c := range res.Communities {
var sawA, sawB bool
for _, m := range c.Members {
if m[0] == 'a' {
sawA = true
} else {
sawB = true
}
}
if sawA && sawB {
t.Errorf("community %s mixes the two cliques: %v", c.ID, c.Members)
}
}
// Every node is assigned.
if len(res.NodeToComm) != 24 {
t.Errorf("NodeToComm covers %d nodes, want 24", len(res.NodeToComm))
}
// A well-separated partition has clearly positive modularity.
if res.Modularity <= 0 {
t.Errorf("modularity = %f, want > 0 for two near-disjoint cliques", res.Modularity)
}
}
func TestSpectralClusters_EmptyGraph(t *testing.T) {
res := SpectralClusters(graph.New())
if len(res.Communities) != 0 {
t.Errorf("empty graph should yield no communities, got %d", len(res.Communities))
}
if res.NodeToComm == nil {
t.Error("NodeToComm must be non-nil even for an empty graph")
}
}
func TestSpectralClusters_SmallComponentNotSplit(t *testing.T) {
g := graph.New()
// A single 6-node clique — well below the bisection floor, so it
// must come back as exactly one community.
clique(g, "c", 6)
res := SpectralClusters(g)
if len(res.Communities) != 1 {
t.Fatalf("a small clique should be one community, got %d", len(res.Communities))
}
if res.Communities[0].Size != 6 {
t.Errorf("community size = %d, want 6", res.Communities[0].Size)
}
}
func TestSpectralClusters_DisconnectedComponents(t *testing.T) {
g := graph.New()
clique(g, "x", 5)
clique(g, "y", 5)
clique(g, "z", 5)
// No bridges — three disconnected cliques.
res := SpectralClusters(g)
if len(res.Communities) != 3 {
t.Fatalf("three disconnected cliques should be three communities, got %d", len(res.Communities))
}
}