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
237 lines
6.8 KiB
Go
237 lines
6.8 KiB
Go
package analysis
|
|
|
|
import "testing"
|
|
|
|
func TestPureClusterLabel(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
files []string
|
|
want string
|
|
}{
|
|
{
|
|
name: "all files under one deep dir",
|
|
files: []string{
|
|
"gortex/internal/parser/languages/cpp.go",
|
|
"gortex/internal/parser/languages/dart.go",
|
|
"gortex/internal/parser/languages/python.go",
|
|
},
|
|
want: "parser/languages",
|
|
},
|
|
{
|
|
name: "all files under repo + plumbing only",
|
|
files: []string{
|
|
"gortex/internal/parser/extractor.go",
|
|
"gortex/internal/graph/edge.go",
|
|
},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "single file → plumbing stripped from directory",
|
|
files: []string{
|
|
"gortex/internal/server/dashboard.go",
|
|
},
|
|
want: "server",
|
|
},
|
|
{
|
|
name: "empty",
|
|
files: []string{},
|
|
want: "",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := pureClusterLabel(tt.files)
|
|
if got != tt.want {
|
|
t.Errorf("pureClusterLabel(%v) = %q; want %q", tt.files, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMixedClusterLabel(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
files []string
|
|
want string
|
|
}{
|
|
{
|
|
name: "spread across many top-level dirs surfaces spread",
|
|
files: []string{
|
|
"gortex/internal/parser/languages/abap.go",
|
|
"gortex/internal/parser/languages/actionscript.go",
|
|
"gortex/internal/parser/languages/ada.go",
|
|
"gortex/internal/parser/extractor.go",
|
|
"gortex/internal/dataflow/dataflow.go",
|
|
"gortex/internal/graph/edge.go",
|
|
"gortex/internal/graph/node.go",
|
|
"gortex/internal/mcp/tools_ast.go",
|
|
"gortex/internal/llm/daemon_backend.go",
|
|
},
|
|
want: "parser/languages +5 dirs",
|
|
},
|
|
{
|
|
name: "two dirs, modal wins with +1 dirs",
|
|
files: []string{
|
|
"gortex/internal/foo/a.go",
|
|
"gortex/internal/foo/b.go",
|
|
"gortex/internal/bar/x.go",
|
|
},
|
|
want: "foo +1 dirs",
|
|
},
|
|
{
|
|
name: "single dir → no spread annotation",
|
|
files: []string{
|
|
"gortex/internal/foo/a.go",
|
|
"gortex/internal/foo/b.go",
|
|
},
|
|
want: "foo",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := mixedClusterLabel(tt.files)
|
|
if got != tt.want {
|
|
t.Errorf("mixedClusterLabel = %q; want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDisambiguateLabelsCascadesToSizeSuffix(t *testing.T) {
|
|
// Two clusters share modal dir AND first-file AND second-file —
|
|
// only the size suffix breaks the tie.
|
|
communities := []Community{
|
|
{ID: "a", Label: "server", Size: 50, Files: []string{"server/h.go", "server/m.go"}},
|
|
{ID: "b", Label: "server", Size: 70, Files: []string{"server/h.go", "server/m.go"}},
|
|
}
|
|
disambiguateLabels(communities)
|
|
if communities[0].Label == communities[1].Label {
|
|
t.Fatalf("size tiebreaker failed: both %q", communities[0].Label)
|
|
}
|
|
}
|
|
|
|
func TestDisambiguateLabelsOrdinalTiebreaker(t *testing.T) {
|
|
// Three clusters with the same dir, same first two files, AND
|
|
// the same size — only the ordinal tiebreaker can split them.
|
|
communities := []Community{
|
|
{ID: "a", Label: "mcp", Size: 15, Files: []string{"mcp/memories.go", "mcp/notes.go"}},
|
|
{ID: "b", Label: "mcp", Size: 15, Files: []string{"mcp/memories.go", "mcp/notes.go"}},
|
|
{ID: "c", Label: "mcp", Size: 15, Files: []string{"mcp/memories.go", "mcp/notes.go"}},
|
|
}
|
|
disambiguateLabels(communities)
|
|
labels := map[string]bool{}
|
|
for _, c := range communities {
|
|
if labels[c.Label] {
|
|
t.Fatalf("ordinal tiebreaker failed: duplicate %q", c.Label)
|
|
}
|
|
labels[c.Label] = true
|
|
}
|
|
}
|
|
|
|
func TestDisambiguateLabelsUsesHubFirst(t *testing.T) {
|
|
// Two pure clusters under the same directory should get their
|
|
// hub-symbol names appended — the most meaningful disambiguator.
|
|
// File basenames are only the fallback when the hub is missing.
|
|
communities := []Community{
|
|
{
|
|
ID: "community-1",
|
|
Label: "parser/languages",
|
|
Hub: "GoExtractor",
|
|
Files: []string{
|
|
"gortex/internal/parser/languages/cpp.go",
|
|
"gortex/internal/parser/languages/csharp.go",
|
|
},
|
|
},
|
|
{
|
|
ID: "community-2",
|
|
Label: "parser/languages",
|
|
Hub: "DartExtractor",
|
|
Files: []string{
|
|
"gortex/internal/parser/languages/dart.go",
|
|
"gortex/internal/parser/languages/flutter.go",
|
|
},
|
|
},
|
|
{
|
|
ID: "community-3",
|
|
Label: "server",
|
|
Hub: "RunServer",
|
|
Files: []string{"gortex/internal/server/handler.go"},
|
|
},
|
|
}
|
|
disambiguateLabels(communities)
|
|
wantA := "parser/languages · GoExtractor"
|
|
wantB := "parser/languages · DartExtractor"
|
|
if communities[0].Label != wantA {
|
|
t.Errorf("community[0] = %q; want %q", communities[0].Label, wantA)
|
|
}
|
|
if communities[1].Label != wantB {
|
|
t.Errorf("community[1] = %q; want %q", communities[1].Label, wantB)
|
|
}
|
|
if communities[2].Label != "server" {
|
|
t.Errorf("unique label was modified: %q", communities[2].Label)
|
|
}
|
|
}
|
|
|
|
func TestDisambiguateFallsBackToFileWhenNoHub(t *testing.T) {
|
|
// When the hub is missing (e.g. legacy data, no member with a
|
|
// resolvable name), the cascade falls back to file basenames.
|
|
communities := []Community{
|
|
{
|
|
ID: "a", Label: "parser/languages", Hub: "",
|
|
Files: []string{"gortex/internal/parser/languages/cpp.go"},
|
|
},
|
|
{
|
|
ID: "b", Label: "parser/languages", Hub: "",
|
|
Files: []string{"gortex/internal/parser/languages/dart.go"},
|
|
},
|
|
}
|
|
disambiguateLabels(communities)
|
|
if communities[0].Label == communities[1].Label {
|
|
t.Fatalf("fallback failed: both %q", communities[0].Label)
|
|
}
|
|
}
|
|
|
|
func TestCleanHubName(t *testing.T) {
|
|
cases := map[string]string{
|
|
"": "",
|
|
"runServer": "runServer",
|
|
"(*Extractor).Extract": "Extractor.Extract",
|
|
"(Receiver).Method": "Receiver.Method",
|
|
"WayTooLongFunctionNameOfAReallyLongType.WithAMethod": "WayTooLongFunctionNameOfAReally…",
|
|
}
|
|
for in, want := range cases {
|
|
got := cleanHubName(in)
|
|
if got != want {
|
|
t.Errorf("cleanHubName(%q) = %q; want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInferCommunityLabelDistinguishesSameBasename(t *testing.T) {
|
|
// Two clusters whose basenames collide ("languages") but whose
|
|
// contents differ should produce different labels.
|
|
pureFiles := []string{
|
|
"gortex/internal/parser/languages/cpp.go",
|
|
"gortex/internal/parser/languages/dart.go",
|
|
"gortex/internal/parser/languages/python.go",
|
|
}
|
|
mixedFiles := []string{
|
|
"gortex/internal/parser/languages/abap.go",
|
|
"gortex/internal/parser/extractor.go",
|
|
"gortex/internal/dataflow/dataflow.go",
|
|
"gortex/internal/graph/edge.go",
|
|
"gortex/internal/mcp/tools_ast.go",
|
|
}
|
|
pure := inferCommunityLabel(nil, nil, pureFiles)
|
|
mixed := inferCommunityLabel(nil, nil, mixedFiles)
|
|
if pure == mixed {
|
|
t.Fatalf("two structurally distinct clusters share label %q (regression — naming collision is back)", pure)
|
|
}
|
|
if pure != "parser/languages" {
|
|
t.Errorf("pure cluster label = %q; want %q", pure, "parser/languages")
|
|
}
|
|
if mixed == "languages" || mixed == "parser/languages" {
|
|
t.Errorf("mixed cluster label = %q; should surface its spread", mixed)
|
|
}
|
|
}
|