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

122 lines
4.4 KiB
Go

package graph
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// buildClassifyGraph wires three functions in one file, each with a
// distinct edge profile, so ClassifyZeroEdge can be exercised against
// every classification:
//
// - Used — the file defines it AND a caller calls it.
// - Unused — the file defines it, it makes an outgoing call, but
// nothing calls it back. Indexed, dead.
// - Orphaned — added with no edges at all, mimicking a symbol the
// extractor never processed.
func buildClassifyGraph() *Graph {
g := New()
g.AddNode(&Node{ID: "svc.go", Kind: KindFile, Name: "svc.go", FilePath: "svc.go"})
g.AddNode(&Node{ID: "svc.go::Caller", Kind: KindFunction, Name: "Caller", FilePath: "svc.go"})
g.AddNode(&Node{ID: "svc.go::Used", Kind: KindFunction, Name: "Used", FilePath: "svc.go"})
g.AddNode(&Node{ID: "svc.go::Unused", Kind: KindFunction, Name: "Unused", FilePath: "svc.go"})
g.AddNode(&Node{ID: "svc.go::Helper", Kind: KindFunction, Name: "Helper", FilePath: "svc.go"})
// The file defines every function except the orphan.
g.AddEdge(&Edge{From: "svc.go", To: "svc.go::Caller", Kind: EdgeDefines})
g.AddEdge(&Edge{From: "svc.go", To: "svc.go::Used", Kind: EdgeDefines})
g.AddEdge(&Edge{From: "svc.go", To: "svc.go::Unused", Kind: EdgeDefines})
g.AddEdge(&Edge{From: "svc.go", To: "svc.go::Helper", Kind: EdgeDefines})
// Used has an incoming call edge.
g.AddEdge(&Edge{From: "svc.go::Caller", To: "svc.go::Used", Kind: EdgeCalls})
// Unused has only an outgoing call — no incoming usage edge.
g.AddEdge(&Edge{From: "svc.go::Unused", To: "svc.go::Helper", Kind: EdgeCalls})
return g
}
func TestClassifyZeroEdge(t *testing.T) {
g := buildClassifyGraph()
// A method node with only its member_of structural edge — indexed,
// but nothing references it.
g.AddNode(&Node{ID: "svc.go::T", Kind: KindType, Name: "T", FilePath: "svc.go"})
g.AddNode(&Node{ID: "svc.go::T.Method", Kind: KindMethod, Name: "Method", FilePath: "svc.go"})
g.AddEdge(&Edge{From: "svc.go::T.Method", To: "svc.go::T", Kind: EdgeMemberOf})
// An orphan: in the graph, but carrying no edges of any kind.
g.AddNode(&Node{ID: "svc.go::Orphan", Kind: KindFunction, Name: "Orphan", FilePath: "svc.go"})
tests := []struct {
name string
symbolID string
want ZeroEdgeClass
}{
{
name: "incoming call edge yields none",
symbolID: "svc.go::Used",
want: ZeroEdgeNone,
},
{
name: "only structural defines plus outgoing call yields likely_unused",
symbolID: "svc.go::Unused",
want: ZeroEdgeLikelyUnused,
},
{
name: "method with only member_of yields likely_unused",
symbolID: "svc.go::T.Method",
want: ZeroEdgeLikelyUnused,
},
{
name: "zero edges of any kind yields possible_extraction_gap",
symbolID: "svc.go::Orphan",
want: ZeroEdgePossibleExtractionGap,
},
{
name: "unknown symbol id yields possible_extraction_gap",
symbolID: "svc.go::DoesNotExist",
want: ZeroEdgePossibleExtractionGap,
},
{
name: "empty symbol id yields possible_extraction_gap",
symbolID: "",
want: ZeroEdgePossibleExtractionGap,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ClassifyZeroEdge(g, tt.symbolID)
assert.Equal(t, tt.want, got)
})
}
}
func TestClassifyZeroEdge_NilGraph(t *testing.T) {
assert.Equal(t, ZeroEdgePossibleExtractionGap, ClassifyZeroEdge(nil, "svc.go::Used"))
}
func TestCaveatForZeroEdge(t *testing.T) {
g := buildClassifyGraph()
g.AddNode(&Node{ID: "svc.go::Orphan", Kind: KindFunction, Name: "Orphan", FilePath: "svc.go"})
// A used symbol carries no caveat — nil is returned so callers can
// attach it unconditionally.
assert.Nil(t, CaveatForZeroEdge(g, "svc.go::Used"))
// Likely-unused: classification plus a non-empty message.
unused := CaveatForZeroEdge(g, "svc.go::Unused")
require.NotNil(t, unused)
assert.Equal(t, ZeroEdgeLikelyUnused, unused.Class)
assert.NotEmpty(t, unused.Message)
// Extraction gap: classification plus a non-empty message.
gap := CaveatForZeroEdge(g, "svc.go::Orphan")
require.NotNil(t, gap)
assert.Equal(t, ZeroEdgePossibleExtractionGap, gap.Class)
assert.NotEmpty(t, gap.Message)
// The two caveat messages must differ — they describe opposite
// situations and an agent has to be able to tell them apart.
assert.NotEqual(t, unused.Message, gap.Message)
}