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

214 lines
5.4 KiB
Go

package indexer
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
// indexAll indexes a single-file Go fixture and runs the global
// resolve + dataflow materialisation pass. Returns the graph for
// assertions.
func indexAll(t *testing.T, src string) graph.Store {
t.Helper()
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte(src), 0o644))
g := graph.New()
idx := newTestIndexer(g)
_, err := idx.Index(dir)
require.NoError(t, err)
idx.ResolveAll()
return g
}
// findEdges returns all edges matching the predicate.
func findEdges(g graph.Store, kind graph.EdgeKind, match func(*graph.Edge) bool) []*graph.Edge {
var out []*graph.Edge
for _, e := range g.AllEdges() {
if e.Kind != kind {
continue
}
if match(e) {
out = append(out, e)
}
}
return out
}
// TestMaterializeDataflowParams_ArgOfLifted verifies the post-pass
// rewrites EdgeArgOf targets from the function node to the actual
// param node at the recorded position.
func TestMaterializeDataflowParams_ArgOfLifted(t *testing.T) {
src := `package main
func Sink(payload string) {}
func Driver(input string) {
Sink(input)
}
`
g := indexAll(t, src)
// Look up the resolved Driver function ID — we only know the
// suffix, the prefix is whatever IndexDirectory uses.
driverID := findFuncID(t, g, "Driver")
sinkID := findFuncID(t, g, "Sink")
args := findEdges(g, graph.EdgeArgOf, func(e *graph.Edge) bool {
return e.From == driverID+"#param:input"
})
if len(args) == 0 {
t.Fatalf("no arg_of edges from Driver#param:input; have edges:\n%s", dumpAllEdges(g))
}
// The post-pass should have lifted To from sinkID itself to
// sinkID#param:payload.
want := sinkID + "#param:payload"
for _, e := range args {
if e.To == want {
return
}
}
t.Fatalf("arg_of not lifted to %q; got: %s", want, dumpEdges(args))
}
// TestMaterializeDataflowParams_ReturnsToLifted verifies the post-pass
// rewrites EdgeReturnsTo From from the placeholder caller ID to
// the resolved callee.
func TestMaterializeDataflowParams_ReturnsToLifted(t *testing.T) {
src := `package main
func Source() string { return "hi" }
func Driver() {
v := Source()
_ = v
}
`
g := indexAll(t, src)
driverID := findFuncID(t, g, "Driver")
sourceID := findFuncID(t, g, "Source")
rets := findEdges(g, graph.EdgeReturnsTo, func(e *graph.Edge) bool {
return strings.HasPrefix(e.To, driverID+"#local:v@")
})
if len(rets) == 0 {
t.Fatalf("no returns_to edges; have edges:\n%s", dumpAllEdges(g))
}
for _, e := range rets {
if e.From == sourceID {
return
}
}
t.Fatalf("returns_to.From not lifted to %q; got: %s", sourceID, dumpEdges(rets))
}
// TestMaterializeDataflowParams_CrossFileBinding asserts dataflow
// edges connect across files: `Driver` (in driver.go) calls
// `Sink` (in sink.go) and the post-pass binds the arg_of edge to
// Sink's param node, even though neither side has the other's
// AST visible at extraction time.
func TestMaterializeDataflowParams_CrossFileBinding(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "sink.go"), []byte(`package main
func Sink(payload string) {}
`), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "driver.go"), []byte(`package main
func Driver(input string) {
Sink(input)
}
`), 0o644))
g := graph.New()
idx := newTestIndexer(g)
_, err := idx.Index(dir)
require.NoError(t, err)
idx.ResolveAll()
driverID := findFuncID(t, g, "Driver")
sinkID := findFuncID(t, g, "Sink")
want := sinkID + "#param:payload"
args := findEdges(g, graph.EdgeArgOf, func(e *graph.Edge) bool {
return e.From == driverID+"#param:input" && e.To == want
})
if len(args) == 0 {
t.Fatalf("missing cross-file arg_of(Driver#param:input → %s); have:\n%s", want, dumpAllEdges(g))
}
}
// TestMaterializeDataflowParams_NoStrandedPlaceholders asserts the
// post-pass leaves no stranded placeholders for resolvable callees.
func TestMaterializeDataflowParams_NoStrandedPlaceholders(t *testing.T) {
src := `package main
func A(x int) int { return x }
func B(y int) int { return y }
func Driver(z int) int {
return A(B(z))
}
`
g := indexAll(t, src)
// All arg_of edges that reference resolved callees should have
// been lifted to a param-node target.
driverID := findFuncID(t, g, "Driver")
args := findEdges(g, graph.EdgeArgOf, func(e *graph.Edge) bool {
return e.From == driverID+"#param:z" || e.From == ""
})
for _, e := range args {
if !strings.Contains(e.To, "#param:") {
t.Errorf("expected arg_of.To to contain #param:; got %q", e.To)
}
}
}
func findFuncID(t *testing.T, g graph.Store, name string) string {
t.Helper()
candidates := g.FindNodesByName(name)
for _, n := range candidates {
if n.Kind == graph.KindFunction || n.Kind == graph.KindMethod {
return n.ID
}
}
t.Fatalf("function %q not found", name)
return ""
}
func dumpAllEdges(g graph.Store) string {
var b strings.Builder
for _, e := range g.AllEdges() {
b.WriteString(string(e.Kind))
b.WriteString(" ")
b.WriteString(e.From)
b.WriteString(" -> ")
b.WriteString(e.To)
b.WriteString("\n")
}
return b.String()
}
func dumpEdges(edges []*graph.Edge) string {
var b strings.Builder
for i, e := range edges {
if i > 0 {
b.WriteString(" | ")
}
b.WriteString(string(e.Kind))
b.WriteString(" ")
b.WriteString(e.From)
b.WriteString(" -> ")
b.WriteString(e.To)
}
return b.String()
}