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
95 lines
3.1 KiB
Go
95 lines
3.1 KiB
Go
package mcp
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"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"
|
|
)
|
|
|
|
// setupDeterminismServer indexes a fixture deliberately full of
|
|
// interchangeable symbols — 40 identically-shaped Handler functions
|
|
// that each call shared() — so BM25 scores and the structural rerank
|
|
// signals tie en masse. Any reliance on Go's randomised map iteration
|
|
// then surfaces as run-to-run variance.
|
|
func setupDeterminismServer(t *testing.T) *Server {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
var src strings.Builder
|
|
src.WriteString("package handlers\n\nfunc shared() {}\n\n")
|
|
for i := 0; i < 40; i++ {
|
|
fmt.Fprintf(&src, "func Handler%02d() { shared() }\n", i)
|
|
}
|
|
src.WriteString("\nfunc dispatch() {\n")
|
|
for i := 0; i < 40; i++ {
|
|
fmt.Fprintf(&src, "\tHandler%02d()\n", i)
|
|
}
|
|
src.WriteString("}\n")
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "handlers.go"), []byte(src.String()), 0o644))
|
|
|
|
g := graph.New()
|
|
reg := testRegistry()
|
|
cfg := config.Default()
|
|
idx := indexer.New(g, reg, cfg.Index, zap.NewNop())
|
|
_, err := idx.Index(dir)
|
|
require.NoError(t, err)
|
|
eng := query.NewEngine(g)
|
|
srv := NewServer(eng, g, idx, nil, zap.NewNop(), nil)
|
|
srv.RunAnalysis()
|
|
return srv
|
|
}
|
|
|
|
func stableToolText(t *testing.T, srv *Server, tool string, args map[string]any) string {
|
|
t.Helper()
|
|
res := callTool(t, srv, tool, args)
|
|
require.Falsef(t, res.IsError, "%s returned an error", tool)
|
|
require.NotEmpty(t, res.Content)
|
|
return res.Content[0].(mcplib.TextContent).Text
|
|
}
|
|
|
|
// TestDeterminism_ReadToolsByteIdentical asserts that repeated
|
|
// invocations of the result-set tools produce byte-identical output
|
|
// on a fixture engineered to tie BM25 and structural signals.
|
|
func TestDeterminism_ReadToolsByteIdentical(t *testing.T) {
|
|
srv := setupDeterminismServer(t)
|
|
|
|
const runs = 25
|
|
assertStable := func(name, tool string, args map[string]any) {
|
|
t.Run(name, func(t *testing.T) {
|
|
first := stableToolText(t, srv, tool, args)
|
|
for run := 1; run < runs; run++ {
|
|
if got := stableToolText(t, srv, tool, args); got != first {
|
|
t.Fatalf("%s run %d is not byte-identical to run 0", tool, run)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
assertStable("search_concept", "search_symbols", map[string]any{"query": "handler"})
|
|
assertStable("search_identifier", "search_symbols", map[string]any{"query": "Handler17"})
|
|
assertStable("get_repo_outline", "get_repo_outline", map[string]any{})
|
|
assertStable("get_file_summary", "get_file_summary", map[string]any{"path": "handlers.go"})
|
|
assertStable("suggest_queries", "suggest_queries", map[string]any{})
|
|
|
|
// find_usages over a 40-caller symbol exercises the usage-map
|
|
// extraction order most heavily.
|
|
sharedID := ""
|
|
for _, id := range resultIDs(searchSymbolsResp(t, srv, "shared")) {
|
|
if strings.HasSuffix(id, "::shared") {
|
|
sharedID = id
|
|
}
|
|
}
|
|
require.NotEmpty(t, sharedID, "could not resolve the shared() symbol ID")
|
|
assertStable("find_usages", "find_usages", map[string]any{"id": sharedID})
|
|
}
|