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

258 lines
8.5 KiB
Go

package analysis
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
// connNode is a single node in a connectivity test fixture.
type connNode struct {
id string
kind graph.NodeKind
file string
}
// connEdge is a single directed edge in a connectivity test fixture.
type connEdge struct {
from string
to string
}
// buildConnGraph assembles a graph from a node/edge fixture so each
// test pins exactly which nodes are isolated / leaf / connected.
func buildConnGraph(nodes []connNode, edges []connEdge) (*graph.Graph, []*graph.Node) {
g := graph.New()
for _, n := range nodes {
g.AddNode(&graph.Node{ID: n.id, Kind: n.kind, Name: n.id, FilePath: n.file, Language: "go"})
}
for _, e := range edges {
g.AddEdge(&graph.Edge{From: e.from, To: e.to, Kind: graph.EdgeDefines, Confidence: 1})
}
return g, g.AllNodes()
}
func TestGraphConnectivity(t *testing.T) {
tests := []struct {
name string
// fixture
nodes []connNode
edges []connEdge
// expectations
wantNominal int
wantEffective int
wantRatio float64
wantIsolated int
wantLeaf int
wantSourceOnly int
wantSinkOnly int
}{
{
name: "empty graph",
nodes: nil,
edges: nil,
wantNominal: 0,
wantEffective: 0,
wantRatio: 0,
},
{
// file.go defines fn — fn is a leaf (one edge) and
// sink-only (only incoming); file.go is source-only.
// No node is isolated.
name: "fully connected pair",
nodes: []connNode{
{"a.go", graph.KindFile, "a.go"},
{"a.go::Fn", graph.KindFunction, "a.go"},
},
edges: []connEdge{{"a.go", "a.go::Fn"}},
wantNominal: 2,
wantEffective: 2,
wantRatio: 1.0,
wantIsolated: 0,
wantLeaf: 2, // both ends of the single edge have degree 1
wantSourceOnly: 1, // a.go
wantSinkOnly: 1, // a.go::Fn
},
{
// One isolated function: zero edges of any kind. This is
// the headline extraction-gap signal — NOT dead code.
name: "one isolated node",
nodes: []connNode{
{"a.go", graph.KindFile, "a.go"},
{"a.go::Fn", graph.KindFunction, "a.go"},
{"orphan.go::Lost", graph.KindFunction, "orphan.go"},
},
edges: []connEdge{{"a.go", "a.go::Fn"}},
wantNominal: 3,
wantEffective: 2,
wantRatio: 2.0 / 3.0,
wantIsolated: 1, // orphan.go::Lost
wantLeaf: 2, // a.go and a.go::Fn
wantSourceOnly: 1,
wantSinkOnly: 1,
},
{
// A chain a -> b -> c. b has in+out (degree 2, neither
// leaf nor source/sink-only). a is source-only, c is
// sink-only; both are leaves.
name: "three-node chain",
nodes: []connNode{
{"a.go::A", graph.KindFunction, "a.go"},
{"a.go::B", graph.KindFunction, "a.go"},
{"a.go::C", graph.KindFunction, "a.go"},
},
edges: []connEdge{
{"a.go::A", "a.go::B"},
{"a.go::B", "a.go::C"},
},
wantNominal: 3,
wantEffective: 3,
wantRatio: 1.0,
wantIsolated: 0,
wantLeaf: 2, // A and C
wantSourceOnly: 1, // A
wantSinkOnly: 1, // C
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g, nodes := buildConnGraph(tt.nodes, tt.edges)
report := GraphConnectivity(g, nodes, 0)
assert.Equal(t, tt.wantNominal, report.NominalNodes, "nominal_nodes")
assert.Equal(t, tt.wantEffective, report.EffectiveNodes, "effective_nodes")
assert.InDelta(t, tt.wantRatio, report.EffectiveRatio, 1e-9, "effective_ratio")
assert.Equal(t, tt.wantIsolated, report.Isolated, "isolated")
assert.Equal(t, tt.wantLeaf, report.Leaf, "leaf")
assert.Equal(t, tt.wantSourceOnly, report.SourceOnly, "source_only")
assert.Equal(t, tt.wantSinkOnly, report.SinkOnly, "sink_only")
assert.NotEmpty(t, report.Note, "report must carry the extraction-vs-dead-code note")
})
}
}
// TestGraphConnectivity_DeadWeightByFile asserts the per-file
// dead-weight attribution ranks files by isolated+leaf contribution
// so an extraction gap can be localised.
func TestGraphConnectivity_DeadWeightByFile(t *testing.T) {
// gappy.go contributes 3 isolated nodes; ok.go contributes a
// single connected pair (two leaves, zero isolated).
nodes := []connNode{
{"ok.go", graph.KindFile, "ok.go"},
{"ok.go::Fn", graph.KindFunction, "ok.go"},
{"gappy.go::L1", graph.KindFunction, "gappy.go"},
{"gappy.go::L2", graph.KindFunction, "gappy.go"},
{"gappy.go::L3", graph.KindFunction, "gappy.go"},
}
edges := []connEdge{{"ok.go", "ok.go::Fn"}}
g, allNodes := buildConnGraph(nodes, edges)
report := GraphConnectivity(g, allNodes, 0)
require.Len(t, report.DeadWeightByFile, 2, "both files contribute dead-weight nodes")
// gappy.go ranks first: 3 isolated nodes > ok.go's 2 leaf nodes.
top := report.DeadWeightByFile[0]
assert.Equal(t, "gappy.go", top.FilePath)
assert.Equal(t, 3, top.Isolated)
assert.Equal(t, 0, top.Leaf)
assert.Equal(t, 3, top.DeadWeight)
second := report.DeadWeightByFile[1]
assert.Equal(t, "ok.go", second.FilePath)
assert.Equal(t, 0, second.Isolated)
assert.Equal(t, 2, second.Leaf)
assert.Equal(t, 2, second.DeadWeight)
}
// TestGraphConnectivity_FileLimit asserts the fileLimit argument
// truncates the dead-weight ranking to the top-N files.
func TestGraphConnectivity_FileLimit(t *testing.T) {
nodes := []connNode{
{"a.go::A", graph.KindFunction, "a.go"},
{"b.go::B", graph.KindFunction, "b.go"},
{"c.go::C", graph.KindFunction, "c.go"},
}
g, allNodes := buildConnGraph(nodes, nil) // all three isolated
report := GraphConnectivity(g, allNodes, 2)
assert.Len(t, report.DeadWeightByFile, 2, "fileLimit=2 must cap the ranking at 2 files")
assert.Equal(t, 3, report.Isolated, "the isolated count is not affected by fileLimit")
}
// TestGraphConnectivity_ByKind asserts the per-node-kind breakdown
// tallies isolated / leaf counts separately per kind.
func TestGraphConnectivity_ByKind(t *testing.T) {
// One connected file->function pair, plus an isolated type.
nodes := []connNode{
{"a.go", graph.KindFile, "a.go"},
{"a.go::Fn", graph.KindFunction, "a.go"},
{"a.go::Orphan", graph.KindType, "a.go"},
}
edges := []connEdge{{"a.go", "a.go::Fn"}}
g, allNodes := buildConnGraph(nodes, edges)
report := GraphConnectivity(g, allNodes, 0)
byKind := map[string]ConnectivityKindEntry{}
for _, k := range report.ByKind {
byKind[k.Kind] = k
}
require.Contains(t, byKind, "type")
assert.Equal(t, 1, byKind["type"].Total)
assert.Equal(t, 1, byKind["type"].Isolated, "the orphan type is isolated")
assert.Equal(t, 0, byKind["type"].Leaf)
require.Contains(t, byKind, "function")
assert.Equal(t, 1, byKind["function"].Total)
assert.Equal(t, 0, byKind["function"].Isolated)
assert.Equal(t, 1, byKind["function"].Leaf, "the defined function has degree 1")
}
// TestGraphConnectivity_IsolatedIsNotDeadCode pins the load-bearing
// distinction: an isolated node (zero edges of ANY kind) is an
// extraction-gap signal, whereas a dead-code node still carries a
// structural edge. A node that is `defines`-linked from its file but
// has no incoming usage edge is dead code, NOT isolated — this
// analyzer must not count it as isolated.
func TestGraphConnectivity_IsolatedIsNotDeadCode(t *testing.T) {
nodes := []connNode{
{"a.go", graph.KindFile, "a.go"},
// Defined by its file but never used — classic dead code.
{"a.go::Unused", graph.KindFunction, "a.go"},
// No edges at all — an extraction gap.
{"b.go::Missing", graph.KindFunction, "b.go"},
}
edges := []connEdge{{"a.go", "a.go::Unused"}}
g, allNodes := buildConnGraph(nodes, edges)
report := GraphConnectivity(g, allNodes, 0)
// Only the zero-edge node counts as isolated; the dead-code node
// has a structural edge and does not.
assert.Equal(t, 1, report.Isolated, "only the zero-edge node is isolated")
// Cross-check against the shared classifier the analyzer reuses.
assert.Equal(t, graph.ZeroEdgePossibleExtractionGap,
graph.ClassifyZeroEdge(g, "b.go::Missing"),
"the zero-edge node classifies as an extraction gap")
assert.NotEqual(t, graph.ZeroEdgePossibleExtractionGap,
graph.ClassifyZeroEdge(g, "a.go::Unused"),
"the structurally-linked dead-code node is NOT an extraction gap")
}
// TestGraphConnectivity_NilGraph asserts a nil graph yields a zero
// report rather than panicking.
func TestGraphConnectivity_NilGraph(t *testing.T) {
report := GraphConnectivity(nil, nil, 0)
assert.Equal(t, 0, report.NominalNodes)
assert.Equal(t, 0, report.EffectiveNodes)
assert.True(t, math.IsNaN(report.EffectiveRatio) == false, "ratio stays a real number")
}