Why: models need a capped way to call several tools from one script, and a slow MCP server must not block the first prompt. Evidence: GOWORK=off go test ./runtime/codeorch/ ./runtime/tools/ ./runtime/mcp/ ./runtime/policy/ ./adapters/native/ Impact: run_tool_script stays unregistered until the caller turns it on. Deferred tools stay out of the model list. An unready MCP server connects in the background. Refs: feature/code-orchestration Co-authored-by: Cursor <cursoragent@cursor.com>
90 lines
2.7 KiB
Go
90 lines
2.7 KiB
Go
package codeorch
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
func TestScriptCallsTwoToolsAndReportsTheOneThatRan(t *testing.T) {
|
|
body := "import agentwf as wf\nprint(wf.tool('alpha', {}))\nprint(wf.tool('beta', {}))\n"
|
|
out := Run(context.Background(), t.TempDir(), body, Limits{}, nil, func(_ context.Context, call Call) (string, error) {
|
|
if call.Name == "beta" {
|
|
return "", errors.New("beta 失败")
|
|
}
|
|
return "A", nil
|
|
})
|
|
text := out.ModelText()
|
|
if len(out.Calls) != 1 || out.Calls[0] != "alpha" {
|
|
t.Fatalf("calls %+v", out.Calls)
|
|
}
|
|
if !strings.Contains(text, "已执行:alpha") || !strings.Contains(text, "beta 失败") {
|
|
t.Fatalf("text %q err %v", text, out.Err)
|
|
}
|
|
}
|
|
|
|
func TestForbiddenToolIsRejectedBeforeDispatchRunsIt(t *testing.T) {
|
|
body := "import agentwf as wf\nprint(wf.tool('read_file', {}))\nprint(wf.tool('run_shell', {}))\n"
|
|
var ran []string
|
|
out := Run(context.Background(), t.TempDir(), body, Limits{}, nil, func(_ context.Context, call Call) (string, error) {
|
|
if Forbidden(call.Name) {
|
|
return "", errors.New("不能调用 " + call.Name)
|
|
}
|
|
ran = append(ran, call.Name)
|
|
return "ok", nil
|
|
})
|
|
if len(ran) != 1 || ran[0] != "read_file" {
|
|
t.Fatalf("ran %+v", ran)
|
|
}
|
|
if !strings.Contains(out.ModelText(), "不能调用 run_shell") {
|
|
t.Fatalf("text %q", out.ModelText())
|
|
}
|
|
}
|
|
|
|
func TestLongOutputIsClippedAndStored(t *testing.T) {
|
|
body := "import agentwf as wf\nprint(wf.tool('big', {}))\n"
|
|
dir := t.TempDir()
|
|
out := Run(context.Background(), dir, body, Limits{MaxModelChars: 8, FullDir: dir}, nil, func(_ context.Context, call Call) (string, error) {
|
|
return strings.Repeat("测", 20), nil
|
|
})
|
|
if out.FullPath == "" {
|
|
t.Fatal("expected a full-text file")
|
|
}
|
|
full, err := os.ReadFile(out.FullPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if utf8.RuneCount(full) < 20 {
|
|
t.Fatalf("full text too short: %q", full)
|
|
}
|
|
if !strings.HasSuffix(out.Output, "…(超长截断)") || utf8.RuneCountInString(strings.TrimSuffix(out.Output, "…(超长截断)")) != 8 {
|
|
t.Fatalf("model output not clipped: %q", out.Output)
|
|
}
|
|
if !strings.Contains(out.ModelText(), out.FullPath) {
|
|
t.Fatalf("model text missing path: %q", out.ModelText())
|
|
}
|
|
}
|
|
|
|
func TestResultGivenToScriptIsCapped(t *testing.T) {
|
|
got := clipBytes(strings.Repeat("x", 20), 8)
|
|
if got != "xxxxxxxx" {
|
|
t.Fatalf("clip %q", got)
|
|
}
|
|
if !utf8.ValidString(clipBytes("测测测", 4)) {
|
|
t.Fatal("clip broke utf-8")
|
|
}
|
|
}
|
|
|
|
func TestTimeoutIsCapped(t *testing.T) {
|
|
if (Limits{Timeout: 900 * time.Second, MaxTimeout: 600 * time.Second}).bounded() != 600*time.Second {
|
|
t.Fatal("timeout must clamp to the max")
|
|
}
|
|
if (Limits{}).bounded() != defaultTimeout {
|
|
t.Fatal("empty limits must use the 120s default")
|
|
}
|
|
}
|