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

328 lines
9.5 KiB
Go

package mcp
import (
"encoding/json"
"os"
"path/filepath"
"testing"
mcplib "github.com/mark3labs/mcp-go/mcp"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/config"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/indexer"
"github.com/zzet/gortex/internal/query"
)
// cfgTestServer indexes one Go file with a branchy function so the
// CFG tools can be exercised end-to-end against a real graph +
// on-disk source.
func cfgTestServer(t *testing.T) *Server {
t.Helper()
dir := t.TempDir()
src := `package main
func Classify(score int) string {
label := "low"
if score > 90 {
label = "high"
} else if score > 50 {
label = "mid"
}
for i := 0; i < score; i++ {
if i == 3 {
break
}
}
return label
}
var topLevel = 1
`
require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte(src), 0o644))
g := graph.New()
reg := testRegistry()
cfgConf := config.Default()
idx := indexer.New(g, reg, cfgConf.Index, zap.NewNop())
_, err := idx.Index(dir)
require.NoError(t, err)
idx.ResolveAll()
eng := query.NewEngine(g)
return NewServer(eng, g, idx, nil, zap.NewNop(), nil)
}
func cfgFindSymbol(t *testing.T, srv *Server, name string, kinds ...graph.NodeKind) string {
t.Helper()
kindOK := func(k graph.NodeKind) bool {
if len(kinds) == 0 {
return true
}
for _, want := range kinds {
if k == want {
return true
}
}
return false
}
for _, n := range srv.graph.AllNodes() {
if n.Name == name && kindOK(n.Kind) {
return n.ID
}
}
t.Fatalf("symbol %q not found", name)
return ""
}
func TestHandleGetCFG_BlocksEdgesChains(t *testing.T) {
srv := cfgTestServer(t)
id := cfgFindSymbol(t, srv, "Classify", graph.KindFunction)
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{"id": id}
res, err := srv.handleGetCFG(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError, "tool errored: %v", res)
var payload struct {
Name string `json:"name"`
Blocks []struct {
ID int `json:"id"`
Statements []struct {
Text string `json:"text"`
Defs []string `json:"defs"`
Uses []string `json:"uses"`
} `json:"statements"`
} `json:"blocks"`
Edges []struct {
From int `json:"from"`
To int `json:"to"`
Label string `json:"label"`
} `json:"edges"`
DefUse []struct {
Stmt int `json:"stmt"`
Var string `json:"var"`
Defs []int `json:"defs"`
} `json:"def_use"`
TotalBlocks int `json:"total_blocks"`
}
text := res.Content[0].(mcplib.TextContent).Text
require.NoError(t, json.Unmarshal([]byte(text), &payload))
require.Equal(t, "Classify", payload.Name)
require.Greater(t, payload.TotalBlocks, 4, "branchy function needs several blocks")
labels := map[string]bool{}
for _, e := range payload.Edges {
labels[e.Label] = true
}
for _, want := range []string{"true", "false", "loop_back", "break", "return"} {
require.True(t, labels[want], "missing %s edge in %s", want, text)
}
// The return's use of label must chain to all three defs.
var labelChain []int
for _, ch := range payload.DefUse {
if ch.Var == "label" {
labelChain = ch.Defs
}
}
require.Len(t, labelChain, 3, "label has three reaching defs at the return: %s", text)
}
func TestHandleGetCFG_Mermaid(t *testing.T) {
srv := cfgTestServer(t)
id := cfgFindSymbol(t, srv, "Classify", graph.KindFunction)
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{"id": id, "mermaid": true}
res, err := srv.handleGetCFG(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError)
text := res.Content[0].(mcplib.TextContent).Text
var payload struct {
Mermaid string `json:"mermaid"`
}
require.NoError(t, json.Unmarshal([]byte(text), &payload))
require.Contains(t, payload.Mermaid, "flowchart TD")
require.Contains(t, payload.Mermaid, "-->|true|")
}
func TestHandleGetCFG_GCXFormat(t *testing.T) {
srv := cfgTestServer(t)
id := cfgFindSymbol(t, srv, "Classify", graph.KindFunction)
req := mcplib.CallToolRequest{}
req.Params.Name = "get_cfg"
req.Params.Arguments = map[string]any{"id": id, "format": "gcx", "mermaid": true}
res, err := srv.handleGetCFG(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError)
text := res.Content[0].(mcplib.TextContent).Text
for _, section := range []string{"get_cfg.summary", "get_cfg.stmts", "get_cfg.edges", "get_cfg.chains", "get_cfg.mermaid"} {
require.Contains(t, text, section)
}
}
func TestHandleGetCFG_Errors(t *testing.T) {
srv := cfgTestServer(t)
// Missing id.
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{}
res, err := srv.handleGetCFG(t.Context(), req)
require.NoError(t, err)
require.True(t, res.IsError)
// Unknown symbol.
req.Params.Arguments = map[string]any{"id": "nope.go::Missing"}
res, err = srv.handleGetCFG(t.Context(), req)
require.NoError(t, err)
require.True(t, res.IsError)
// Non-function symbol.
varID := cfgFindSymbol(t, srv, "topLevel")
req.Params.Arguments = map[string]any{"id": varID}
res, err = srv.handleGetCFG(t.Context(), req)
require.NoError(t, err)
require.True(t, res.IsError, "get_cfg on a variable must error")
}
func TestHandleAnalyzeDefUse_ThroughDispatcher(t *testing.T) {
srv := cfgTestServer(t)
id := cfgFindSymbol(t, srv, "Classify", graph.KindFunction)
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{"kind": "def_use", "ids": id}
res, err := srv.handleAnalyze(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError, "analyze def_use errored: %v", res)
var payload struct {
Total int `json:"total"`
Symbols []struct {
ID string `json:"id"`
Error string `json:"error"`
Chains []struct {
Var string `json:"var"`
StmtLine int `json:"stmt_line"`
DefLines []int `json:"def_lines"`
} `json:"chains"`
Variables []struct {
Var string `json:"var"`
Defs int `json:"defs"`
Uses int `json:"uses"`
} `json:"variables"`
} `json:"symbols"`
}
text := res.Content[0].(mcplib.TextContent).Text
require.NoError(t, json.Unmarshal([]byte(text), &payload))
require.Equal(t, 1, payload.Total)
sym := payload.Symbols[0]
require.Empty(t, sym.Error)
require.NotEmpty(t, sym.Chains)
// label's per-variable summary: 3 defs (init + two arms).
foundLabel := false
for _, v := range sym.Variables {
if v.Var == "label" {
foundLabel = true
require.Equal(t, 3, v.Defs, "label is defined three times")
require.GreaterOrEqual(t, v.Uses, 1)
}
}
require.True(t, foundLabel, "variables rollup must include label: %s", text)
}
func TestHandleAnalyzeDefUse_DegradesPerSymbol(t *testing.T) {
srv := cfgTestServer(t)
good := cfgFindSymbol(t, srv, "Classify", graph.KindFunction)
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{"kind": "def_use", "ids": good + ",missing.go::Nope"}
res, err := srv.handleAnalyze(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError)
var payload struct {
Symbols []struct {
ID string `json:"id"`
Error string `json:"error"`
} `json:"symbols"`
}
text := res.Content[0].(mcplib.TextContent).Text
require.NoError(t, json.Unmarshal([]byte(text), &payload))
require.Len(t, payload.Symbols, 2)
require.Empty(t, payload.Symbols[0].Error)
require.NotEmpty(t, payload.Symbols[1].Error, "missing symbol must degrade to a per-symbol error")
}
func TestHandleAnalyzeDefUse_GCX(t *testing.T) {
srv := cfgTestServer(t)
id := cfgFindSymbol(t, srv, "Classify", graph.KindFunction)
req := mcplib.CallToolRequest{}
req.Params.Name = "analyze"
req.Params.Arguments = map[string]any{"kind": "def_use", "ids": id, "format": "gcx"}
res, err := srv.handleAnalyze(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError)
text := res.Content[0].(mcplib.TextContent).Text
require.Contains(t, text, "analyze.def_use")
}
func TestHandleAnalyzeDefUse_RequiresIDs(t *testing.T) {
srv := cfgTestServer(t)
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{"kind": "def_use"}
res, err := srv.handleAnalyze(t.Context(), req)
require.NoError(t, err)
require.True(t, res.IsError)
}
// TestHandleFlowBetween_RefinementLive proves the reaching-
// definitions refinement runs on the real flow_between path: the
// indexed fixture's Mid function binds `v := s`, so the
// param-s → local-v hop must come back stamped
// confirmed_intraprocedural.
func TestHandleFlowBetween_RefinementLive(t *testing.T) {
srv := dataflowTestServer(t)
driverID := findFunctionID(t, srv, "Driver")
sinkID := findFunctionID(t, srv, "Sink")
req := mcplib.CallToolRequest{}
req.Params.Arguments = map[string]any{
"source_id": driverID + "#param:input",
"sink_id": sinkID + "#param:payload",
"max_depth": float64(10),
}
res, err := srv.handleFlowBetween(t.Context(), req)
require.NoError(t, err)
require.False(t, res.IsError, "tool errored: %v", res)
var payload struct {
Paths []struct {
Edges []struct {
From string `json:"from"`
To string `json:"to"`
Kind string `json:"kind"`
Refined string `json:"refined"`
} `json:"edges"`
} `json:"paths"`
}
text := res.Content[0].(mcplib.TextContent).Text
require.NoError(t, json.Unmarshal([]byte(text), &payload))
require.NotEmpty(t, payload.Paths)
confirmed := 0
for _, p := range payload.Paths {
for _, e := range p.Edges {
require.NotEqual(t, "pruned", e.Refined, "fixture has no stale flows: %s -> %s", e.From, e.To)
if e.Refined == "confirmed_intraprocedural" {
confirmed++
}
}
}
require.Greater(t, confirmed, 0, "at least one same-function value_flow hop must be confirmed: %s", text)
}