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
123 lines
5.0 KiB
Go
123 lines
5.0 KiB
Go
package analyzer_test
|
|
|
|
// PURPOSE — shape tests for AnalyzeResolutionOutcomes: verify the function
|
|
// classifies unresolved edges correctly and returns the right struct shape.
|
|
// RATIONALE — tests are MCP-layer-free so the core logic is independently
|
|
// verifiable; they mirror the taxonomy asserted in the MCP-layer tests.
|
|
// KEYWORDS — resolution_outcomes, unit, shape
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/analyzer"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
func TestAnalyzeResolutionOutcomes_Shape(t *testing.T) {
|
|
g := newTestGraph()
|
|
// caller (go)
|
|
g.AddNode(&graph.Node{ID: "a.go::caller", Kind: graph.KindFunction, Name: "caller", FilePath: "a.go", Language: "go"})
|
|
|
|
// unresolved edge — no definition in graph at all.
|
|
g.AddEdge(&graph.Edge{From: "a.go::caller", To: "unresolved::ghost", Kind: graph.EdgeCalls, FilePath: "a.go", Line: 5})
|
|
|
|
res := analyzer.AnalyzeResolutionOutcomes(g, "", 50)
|
|
if res.Total == 0 {
|
|
t.Fatal("expected total > 0")
|
|
}
|
|
if res.Rows == nil {
|
|
t.Fatal("expected rows not nil")
|
|
}
|
|
if len(res.Rows) == 0 {
|
|
t.Fatal("expected at least one row")
|
|
}
|
|
}
|
|
|
|
func TestAnalyzeResolutionOutcomes_Taxonomy(t *testing.T) {
|
|
g := newTestGraph()
|
|
g.AddNode(&graph.Node{ID: "a.go::caller", Kind: graph.KindFunction, Name: "caller", FilePath: "a.go", Language: "go"})
|
|
|
|
// ambiguous_multi_match: two same-name go funcs named "doThing".
|
|
g.AddNode(&graph.Node{ID: "x.go::doThing", Kind: graph.KindFunction, Name: "doThing", FilePath: "x.go", Language: "go"})
|
|
g.AddNode(&graph.Node{ID: "y.go::doThing", Kind: graph.KindFunction, Name: "doThing", FilePath: "y.go", Language: "go"})
|
|
g.AddEdge(&graph.Edge{From: "a.go::caller", To: "unresolved::doThing", Kind: graph.EdgeCalls, FilePath: "a.go", Line: 2})
|
|
|
|
// candidate_out_of_scope: exactly one same-lang def named "single".
|
|
g.AddNode(&graph.Node{ID: "z.go::single", Kind: graph.KindFunction, Name: "single", FilePath: "z.go", Language: "go"})
|
|
g.AddEdge(&graph.Edge{From: "a.go::caller", To: "unresolved::single", Kind: graph.EdgeCalls, FilePath: "a.go", Line: 3})
|
|
|
|
// cross_language_only: only a python def named "pyOnly".
|
|
g.AddNode(&graph.Node{ID: "p.py::pyOnly", Kind: graph.KindFunction, Name: "pyOnly", FilePath: "p.py", Language: "python"})
|
|
g.AddEdge(&graph.Edge{From: "a.go::caller", To: "unresolved::pyOnly", Kind: graph.EdgeCalls, FilePath: "a.go", Line: 4})
|
|
|
|
// no_definition: nothing named "ghost".
|
|
g.AddEdge(&graph.Edge{From: "a.go::caller", To: "unresolved::ghost", Kind: graph.EdgeCalls, FilePath: "a.go", Line: 5})
|
|
|
|
res := analyzer.AnalyzeResolutionOutcomes(g, "", 50)
|
|
check := func(reason string, want int) {
|
|
t.Helper()
|
|
got := res.ByReason[reason]
|
|
if got != want {
|
|
t.Errorf("by_reason[%q] = %d, want %d", reason, got, want)
|
|
}
|
|
}
|
|
check("ambiguous_multi_match", 1)
|
|
check("candidate_out_of_scope", 1)
|
|
check("cross_language_only", 1)
|
|
check("no_definition", 1)
|
|
}
|
|
|
|
func TestAnalyzeResolutionOutcomes_ReasonFilter(t *testing.T) {
|
|
g := newTestGraph()
|
|
g.AddNode(&graph.Node{ID: "a.go::caller", Kind: graph.KindFunction, Name: "caller", FilePath: "a.go", Language: "go"})
|
|
g.AddEdge(&graph.Edge{From: "a.go::caller", To: "unresolved::ghost", Kind: graph.EdgeCalls, FilePath: "a.go", Line: 5})
|
|
|
|
res := analyzer.AnalyzeResolutionOutcomes(g, "no_definition", 50)
|
|
if len(res.Rows) != 1 {
|
|
t.Fatalf("reason filter: want 1 row, got %d", len(res.Rows))
|
|
}
|
|
if res.Rows[0].Reason != "no_definition" {
|
|
t.Errorf("row reason = %q", res.Rows[0].Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyUnresolved_NoDefinition(t *testing.T) {
|
|
g := newTestGraph()
|
|
reason, candidates := analyzer.ClassifyUnresolved(g, "ghost", "go")
|
|
if reason != "no_definition" {
|
|
t.Errorf("expected no_definition, got %q", reason)
|
|
}
|
|
if candidates != 0 {
|
|
t.Errorf("expected 0 candidates, got %d", candidates)
|
|
}
|
|
}
|
|
|
|
func TestAnalyzeResolutionOutcomes_StdlibHeader(t *testing.T) {
|
|
g := newTestGraph()
|
|
g.AddNode(&graph.Node{ID: "main.c", Kind: graph.KindFile, Name: "main.c", FilePath: "main.c", Language: "c"})
|
|
// A standard-library angle include left external by the resolver.
|
|
g.AddEdge(&graph.Edge{
|
|
From: "main.c", To: "unresolved::import::stdio.h", Kind: graph.EdgeImports,
|
|
FilePath: "main.c", Meta: map[string]any{"include_kind": "system"},
|
|
})
|
|
// A non-stdlib system include must NOT be classified as stdlib.
|
|
g.AddEdge(&graph.Edge{
|
|
From: "main.c", To: "unresolved::import::myproj/api.h", Kind: graph.EdgeImports,
|
|
FilePath: "main.c", Meta: map[string]any{"include_kind": "system"},
|
|
})
|
|
|
|
res := analyzer.AnalyzeResolutionOutcomes(g, "", 50)
|
|
if res.ByReason[analyzer.OutcomeStdlibHeader] != 1 {
|
|
t.Errorf("by_reason[%q] = %d, want 1", analyzer.OutcomeStdlibHeader, res.ByReason[analyzer.OutcomeStdlibHeader])
|
|
}
|
|
|
|
// The reason filter narrows the rows to the stdlib header.
|
|
res = analyzer.AnalyzeResolutionOutcomes(g, analyzer.OutcomeStdlibHeader, 50)
|
|
if len(res.Rows) != 1 {
|
|
t.Fatalf("stdlib_header filter: want 1 row, got %d", len(res.Rows))
|
|
}
|
|
if res.Rows[0].Reason != analyzer.OutcomeStdlibHeader || res.Rows[0].Name != "stdio.h" {
|
|
t.Errorf("row = %+v", res.Rows[0])
|
|
}
|
|
}
|