Files
zzet--gortex/internal/mcp/tools_list_budget_test.go
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

117 lines
5.2 KiB
Go

package mcp
import (
"context"
"encoding/json"
"sort"
"testing"
"github.com/stretchr/testify/require"
)
// serializeToolsList drives a real tools/list through a server built with
// the given preset and returns the raw JSON-RPC result byte count plus the
// visible tool names — the exact cold-connect payload a client pays for.
func serializeToolsList(t *testing.T, preset, mode string) (int, []string) {
t.Helper()
srv := setupPresetServer(t, ToolPolicyConfig{Preset: preset, Mode: mode})
reply := srv.MCPServer().HandleMessage(context.Background(),
[]byte(`{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}`))
require.NotNil(t, reply)
out, err := json.Marshal(reply)
require.NoError(t, err)
var parsed struct {
Result struct {
Tools []struct {
Name string `json:"name"`
} `json:"tools"`
} `json:"result"`
}
require.NoError(t, json.Unmarshal(out, &parsed))
names := make([]string, 0, len(parsed.Result.Tools))
for _, e := range parsed.Result.Tools {
names = append(names, e.Name)
}
sort.Strings(names)
return len(out), names
}
// Pre-diet baselines measured on this test harness (the same NewServer path
// the gate uses), so "strictly smaller than today" is a real regression
// assertion rather than a moving target.
const (
corePresetBaselineBytes = 95060
fullPresetBaselineBytes = 289808
)
// agentPresetByteCeiling is the hard budget for the coding-agent preset's
// cold tools/list. Blowing it (a future tool or description balloon) fails
// this test loudly instead of silently regressing the schema tax.
//
// Re-based 27000 → 28200 when the floor deliberately grew 18 → 20 tools
// (the `explore` one-shot localization verb + `batch_symbols`, its
// follow-up reader). Measured cost after the addition: 27883 bytes —
// the ceiling keeps ~300 bytes of slack, so any further description
// creep still fails loudly.
const agentPresetByteCeiling = 28200
// localizationPresetByteCeiling is the hard budget for the diet
// localization preset (the `localization` instruction profile's tool
// surface). It must stay well under the agent floor — the profile
// exists to cut the per-turn schema tax.
//
// Re-based 20000 → 21000 when the surface deliberately grew 12 → 14
// tools: `explore` (the one-shot opener the profile is built around)
// and `batch_symbols` (bench-measured: one extra median turn per
// session without a multi-symbol read). Measured after the growth:
// 20432 bytes — still ~27% under the agent floor, with slack so any
// further description creep fails loudly.
const localizationPresetByteCeiling = 21000
// TestToolsListByteCeilings is the permanent measurement gate: it prints the
// cold tools/list byte cost of every preset and asserts the agent preset
// stays inside its ceiling while core and full shrink below their pre-diet
// baselines.
func TestToolsListByteCeilings(t *testing.T) {
agentBytes, agentNames := serializeToolsList(t, "agent", "defer")
coreBytes, _ := serializeToolsList(t, "core", "defer")
fullBytes, _ := serializeToolsList(t, "full", "")
locBytes, locNames := serializeToolsList(t, "localization", "defer")
t.Logf("tools/list byte cost per preset (cold):")
t.Logf(" agent mode=defer tools=%-3d bytes=%d (ceiling %d)", len(agentNames), agentBytes, agentPresetByteCeiling)
t.Logf(" loc mode=defer tools=%-3d bytes=%d (ceiling %d)", len(locNames), locBytes, localizationPresetByteCeiling)
t.Logf(" core mode=defer bytes=%d (baseline %d)", coreBytes, corePresetBaselineBytes)
t.Logf(" full bytes=%d (baseline %d)", fullBytes, fullPresetBaselineBytes)
require.LessOrEqualf(t, agentBytes, agentPresetByteCeiling,
"agent preset cold tools/list is %d bytes, over the %d ceiling", agentBytes, agentPresetByteCeiling)
require.LessOrEqualf(t, locBytes, localizationPresetByteCeiling,
"localization preset cold tools/list is %d bytes, over the %d ceiling", locBytes, localizationPresetByteCeiling)
require.Lessf(t, locBytes, agentBytes,
"localization (%d bytes) must stay leaner than the agent floor (%d bytes)", locBytes, agentBytes)
locSet := map[string]bool{}
for _, n := range locNames {
locSet[n] = true
}
require.True(t, locSet["smart_context"], "the one-shot opener must ship eagerly in the localization surface")
require.True(t, locSet[LazyToolsSearchName], "tools_search must survive every preset")
require.False(t, locSet["edit_file"], "the localization surface is read-only")
require.Lessf(t, coreBytes, corePresetBaselineBytes,
"core preset must shrink below its pre-diet baseline (%d), got %d", corePresetBaselineBytes, coreBytes)
require.Lessf(t, fullBytes, fullPresetBaselineBytes,
"full preset must shrink below its pre-diet baseline (%d), got %d", fullPresetBaselineBytes, fullBytes)
// The agent surface is well-formed: the discovery + introspection tools
// are always present, and a workhorse floor tool ships eagerly.
set := map[string]bool{}
for _, n := range agentNames {
set[n] = true
}
require.True(t, set[LazyToolsSearchName], "tools_search must be in the agent surface")
require.True(t, set["tool_profile"], "tool_profile must be in the agent surface")
require.True(t, set["search_symbols"], "a floor tool must ship eagerly")
require.False(t, set["analyze"], "analyze is deferred out of the lean agent surface")
}