Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
148 lines
4.7 KiB
Go
148 lines
4.7 KiB
Go
// Package conformance 提供所有 Backend 实现共用的行为验收套件。
|
||
//
|
||
// 用法:实现方在自己的测试中调用 conformance.Run(t, backend, conformance.Case{...})。
|
||
// fake 后端用于契约自测;真实实现(runtime、PI adapter)必须通过同一套件,
|
||
// fake 通过不等于真实适配完成(分别记证据)。
|
||
package conformance
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"testing"
|
||
"time"
|
||
|
||
"git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/exec"
|
||
)
|
||
|
||
// FakeBackend 是确定性假实现,仅用于契约套件自测与消费方单测。
|
||
// 不模拟任何模型行为;按 Script 逐项返回。
|
||
type FakeBackend struct {
|
||
Desc exec.BackendDescriptor
|
||
// Result 为每次 Execute 返回的终态(浅拷贝后返回)。
|
||
Result exec.ContractResult
|
||
// BlockUntil 若非 nil,Execute 阻塞直到该 channel 关闭或 ctx 取消,
|
||
// 用于验证取消语义。
|
||
BlockUntil <-chan struct{}
|
||
// CanceledWithCtx 记录 Execute 返回时 ctx 是否已取消。
|
||
CanceledWithCtx bool
|
||
// LastRequest 记录最近一次请求。
|
||
LastRequest exec.ContractRequest
|
||
}
|
||
|
||
func (f *FakeBackend) Descriptor() exec.BackendDescriptor { return f.Desc }
|
||
|
||
func (f *FakeBackend) Execute(ctx context.Context, req exec.ContractRequest) (*exec.ContractResult, error) {
|
||
f.LastRequest = req
|
||
if f.BlockUntil != nil {
|
||
select {
|
||
case <-f.BlockUntil:
|
||
case <-ctx.Done():
|
||
f.CanceledWithCtx = true
|
||
res := *(&f.Result)
|
||
res.Outcome = exec.OutcomeCanceled
|
||
res.Err = &exec.Error{
|
||
Code: exec.ErrCanceled,
|
||
SideEffects: exec.SideEffectNone,
|
||
}
|
||
return &res, nil
|
||
}
|
||
}
|
||
res := f.Result
|
||
return &res, nil
|
||
}
|
||
|
||
// Case 是一条契约验收用例。
|
||
type Case struct {
|
||
Name string
|
||
// Backend 为被测实现。
|
||
Backend exec.ContractBackend
|
||
// Request 为请求模板。
|
||
Request exec.ContractRequest
|
||
// CancelAfter 若非零,在等待该时长后取消 ctx(验证取消路径)。
|
||
CancelAfter time.Duration
|
||
// WantOutcome 为期望终态。
|
||
WantOutcome exec.Outcome
|
||
// WantErrCode 为期望错误码(Failed/Canceled/Unknown 时)。
|
||
WantErrCode exec.ErrorCode
|
||
}
|
||
|
||
// Run 执行一条契约用例。
|
||
func Run(t *testing.T, c Case) {
|
||
t.Helper()
|
||
ctx := context.Background()
|
||
var cancel context.CancelFunc
|
||
if c.CancelAfter > 0 {
|
||
ctx, cancel = context.WithTimeout(ctx, c.CancelAfter)
|
||
defer cancel()
|
||
}
|
||
res, err := c.Backend.Execute(ctx, c.Request)
|
||
if err != nil {
|
||
t.Fatalf("[%s] Execute returned transport error %v; business outcomes must be in Result", c.Name, err)
|
||
}
|
||
if res == nil {
|
||
t.Fatalf("[%s] Execute returned nil Result", c.Name)
|
||
}
|
||
if res.Outcome != c.WantOutcome {
|
||
t.Fatalf("[%s] Outcome = %q, want %q", c.Name, res.Outcome, c.WantOutcome)
|
||
}
|
||
if c.WantErrCode != "" {
|
||
if res.Err == nil {
|
||
t.Fatalf("[%s] expected error code %q, got nil Err", c.Name, c.WantErrCode)
|
||
}
|
||
if res.Err.Code != c.WantErrCode {
|
||
t.Fatalf("[%s] Err.Code = %q, want %q", c.Name, res.Err.Code, c.WantErrCode)
|
||
}
|
||
if res.Err.SideEffects == "" {
|
||
t.Fatalf("[%s] Err.SideEffects must be explicit (none|possible), got empty", c.Name)
|
||
}
|
||
}
|
||
if res.Outcome == exec.OutcomeSucceeded && res.Err != nil {
|
||
t.Fatalf("[%s] Succeeded result must not carry Err", c.Name)
|
||
}
|
||
}
|
||
|
||
// Suite 是最小契约套件:成功、失败带副作用标记、取消三态。
|
||
// 每个真实 Backend 实现都必须通过;套件外按实现能力补充专项用例。
|
||
func Suite(t *testing.T, mk func() exec.ContractBackend) {
|
||
t.Helper()
|
||
|
||
t.Run("succeeded_minimal", func(t *testing.T) {
|
||
b := mk()
|
||
Run(t, Case{
|
||
Name: "succeeded",
|
||
Backend: b,
|
||
Request: exec.ContractRequest{ExecutionID: "e1", Prompt: "hi"},
|
||
WantOutcome: exec.OutcomeSucceeded,
|
||
})
|
||
})
|
||
|
||
t.Run("descriptor_has_name", func(t *testing.T) {
|
||
b := mk()
|
||
d := b.Descriptor()
|
||
if d.Name == "" {
|
||
t.Fatal("Descriptor().Name must not be empty")
|
||
}
|
||
if len(d.Capabilities) == 0 {
|
||
t.Fatal("Descriptor().Capabilities must declare at least one capability")
|
||
}
|
||
})
|
||
|
||
t.Run("error_sideeffects_explicit", func(t *testing.T) {
|
||
b := mk()
|
||
// 通过描述能力判定是否有失败路径可测;无失败路径的实现跳过。
|
||
if !b.Descriptor().Has(exec.CapText) && !b.Descriptor().Has(exec.CapFiles) {
|
||
t.Skip("no text/files capability to force failure path")
|
||
}
|
||
// 空提示在多数实现下是确定性失败或成功空输出;仅验证当 Outcome=Failed
|
||
// 时错误字段完整。具体实现应补充自身失败注入用例。
|
||
res, _ := b.Execute(context.Background(), exec.ContractRequest{ExecutionID: "e2"})
|
||
if res != nil && res.Outcome == exec.OutcomeFailed {
|
||
if res.Err == nil || res.Err.Code == "" || res.Err.SideEffects == "" {
|
||
t.Fatalf("failed result must carry Code and SideEffects, got %+v", res.Err)
|
||
}
|
||
}
|
||
})
|
||
|
||
_ = fmt.Sprint() // keep fmt import stable for future diagnostics
|
||
}
|