Why: a ContractRequest without a workspace (the conformance minimal case) resolved WorkDir to the process CWD and wrote .agentx transcripts into the repo, dirtying the tree in CI and failing the build step's worktree check. Evidence: GOWORK=off go vet + go test -count=1 ./... green; TestContractEmptyWorkspaceKeepsCWDClean pins the fixture bytes; the adapters/native tree no longer drifts after a suite run. Impact: requests with an explicit workspace are unchanged; empty workspace now lands in a disposable temp dir. Refs: Agent-System-Dev#242 CI failure (run 1144)
114 lines
3.4 KiB
Go
114 lines
3.4 KiB
Go
package native
|
||
|
||
import (
|
||
"context"
|
||
"os"
|
||
"path/filepath"
|
||
"testing"
|
||
"time"
|
||
|
||
"agent-native/conformance"
|
||
"agent-native/exec"
|
||
"agent-native/runtime/llm"
|
||
)
|
||
|
||
// TestContractBackendConformance 把 native 后端的契约形态挂上最小契约套件
|
||
// (N1 WP6:conformance 首次获得真实实现调用方)。
|
||
func TestContractBackendConformance(t *testing.T) {
|
||
conformance.Suite(t, func() exec.ContractBackend {
|
||
return New(Options{
|
||
ModelOptions: ModelOptions{
|
||
Model: "fake",
|
||
Stream: script(
|
||
llm.Message{Role: llm.RoleAssistant, Content: "hi", StopReason: llm.StopEnd},
|
||
),
|
||
},
|
||
}).Contract()
|
||
})
|
||
}
|
||
|
||
// TestContractExecuteCancel 取消路径:流阻塞到 ctx 结束,Outcome=Canceled 且
|
||
// SideEffects 显式。
|
||
func TestContractExecuteCancel(t *testing.T) {
|
||
b := New(Options{
|
||
ModelOptions: ModelOptions{
|
||
Model: "fake",
|
||
Stream: func(ctx context.Context, model string, messages []llm.Message, decls []llm.ToolDecl, opts llm.StreamOptions) <-chan llm.Event {
|
||
ch := make(chan llm.Event)
|
||
go func() {
|
||
<-ctx.Done()
|
||
close(ch)
|
||
}()
|
||
return ch
|
||
},
|
||
},
|
||
}).Contract()
|
||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
||
defer cancel()
|
||
res, err := b.Execute(ctx, exec.ContractRequest{ExecutionID: "cx", Prompt: "hi"})
|
||
if err != nil {
|
||
t.Fatalf("transport error: %v", err)
|
||
}
|
||
if res.Outcome != exec.OutcomeCanceled && res.Outcome != exec.OutcomeFailed {
|
||
t.Fatalf("outcome = %q", res.Outcome)
|
||
}
|
||
if res.Err == nil || res.Err.SideEffects == "" {
|
||
t.Fatalf("err = %+v", res.Err)
|
||
}
|
||
}
|
||
|
||
// TestContractDescriptor 描述的身份与能力。
|
||
func TestContractDescriptor(t *testing.T) {
|
||
d := New(Options{}).Contract().Descriptor()
|
||
if d.Name != "agent-native" {
|
||
t.Fatalf("name = %q", d.Name)
|
||
}
|
||
if !d.Has(exec.CapText) || !d.Has(exec.CapStream) || !d.Has(exec.CapTools) {
|
||
t.Fatalf("capabilities = %v", d.Capabilities)
|
||
}
|
||
}
|
||
|
||
// TestContractEmptyWorkspaceKeepsCWDClean 空 Workspace 的请求不得把会话状态
|
||
// 写进进程 CWD(CI 护栏:make test 后工作区必须保持干净;包目录的已提交
|
||
// .agentx 夹具内容必须逐字节不变)。
|
||
func TestContractEmptyWorkspaceKeepsCWDClean(t *testing.T) {
|
||
wd, err := os.Getwd()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
snapshot := map[string][]byte{}
|
||
for _, rel := range []string{
|
||
filepath.Join(".agentx", "transcript.jsonl"),
|
||
filepath.Join(".agentx", "transcript.jsonl.meta.json"),
|
||
} {
|
||
if b, readErr := os.ReadFile(filepath.Join(wd, rel)); readErr == nil {
|
||
snapshot[rel] = b
|
||
}
|
||
}
|
||
b := New(Options{
|
||
ModelOptions: ModelOptions{
|
||
Model: "fake",
|
||
Stream: script(llm.Message{Role: llm.RoleAssistant, Content: "hi", StopReason: llm.StopEnd}),
|
||
},
|
||
}).Contract()
|
||
res, err := b.Execute(context.Background(), exec.ContractRequest{ExecutionID: "e-clean", Prompt: "hi"})
|
||
if err != nil {
|
||
t.Fatalf("transport error: %v", err)
|
||
}
|
||
if res.Outcome != exec.OutcomeSucceeded {
|
||
t.Fatalf("outcome = %q err = %+v", res.Outcome, res.Err)
|
||
}
|
||
for rel, want := range snapshot {
|
||
got, readErr := os.ReadFile(filepath.Join(wd, rel))
|
||
if readErr != nil {
|
||
t.Fatalf("夹具被删除:%s", rel)
|
||
}
|
||
if string(got) != string(want) {
|
||
t.Fatalf("空 Workspace 的执行改写了包内夹具 %s(应落临时目录)", rel)
|
||
}
|
||
}
|
||
if _, statErr := os.Stat(filepath.Join(wd, ".agentx", "activated-tools.json")); !os.IsNotExist(statErr) {
|
||
t.Fatal("空 Workspace 的执行在包目录写下了 activated-tools.json(应落临时目录)")
|
||
}
|
||
}
|