项目文件夹

文件
Asim Aslam daacd4830f harness/new: make conformance timeout honest and contract test cheaper (#3008)
Follow-up to #3006:

- provider-conformance: build each harness to a temp binary and run that
  instead of 'go run'. 'go run' launches the harness as a child it doesn't
  kill on context cancellation, so a timed-out harness (which starts local
  services) could be orphaned and outlive the run. Running the built binary
  makes the per-run timeout actually terminate the work.
- contract test: skip under -short, and use 'go build ./...' instead of
  'go test ./...' (the contract is that the generated service builds). This
  keeps the default unit-test suite from shelling out to the toolchain and
  the network on every run.


Claude-Session: https://claude.ai/code/session_01CmdEY7pYmV5zzwCjNJ4ykL

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-24 11:48:40 +01:00

141 行
3.9 KiB
Go

// Provider conformance runs the same end-to-end harnesses across model
// providers whose API keys are configured. Missing keys are skipped so the
// command is safe in local development and scheduled CI; a configured provider
// that fails any harness makes the command fail.
//
// Run all live providers with configured keys:
//
// go run ./internal/harness/provider-conformance
//
// Run the deterministic mock path only:
//
// go run ./internal/harness/provider-conformance -providers mock
package main
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
var providerEnv = map[string]string{
"anthropic": "ANTHROPIC_API_KEY",
"openai": "OPENAI_API_KEY",
"gemini": "GEMINI_API_KEY",
"groq": "GROQ_API_KEY",
"mistral": "MISTRAL_API_KEY",
"together": "TOGETHER_API_KEY",
"atlascloud": "ATLASCLOUD_API_KEY",
}
func main() {
providersFlag := flag.String("providers", "anthropic,openai,gemini,groq,mistral,together,atlascloud", "comma-separated providers to check; use mock for deterministic local checks")
harnessesFlag := flag.String("harnesses", "universe,agent-flow,plan-delegate", "comma-separated harness names under internal/harness")
timeoutFlag := flag.Duration("timeout", 10*time.Minute, "timeout per provider/harness run")
flag.Parse()
providers := splitCSV(*providersFlag)
harnesses := splitCSV(*harnessesFlag)
var ran, skipped, failed int
for _, provider := range providers {
if provider != "mock" && providerKey(provider) == "" {
fmt.Printf("- %s: skipped (set MICRO_AI_API_KEY or %s)\n", provider, providerEnv[provider])
skipped++
continue
}
for _, harness := range harnesses {
fmt.Printf("\n==> %s / %s\n", provider, harness)
if err := runHarness(provider, harness, *timeoutFlag); err != nil {
fmt.Printf("FAIL %s / %s: %v\n", provider, harness, err)
failed++
continue
}
ran++
}
}
fmt.Printf("\nprovider conformance: %d passed, %d skipped providers, %d failed\n", ran, skipped, failed)
if failed > 0 {
os.Exit(1)
}
}
func splitCSV(s string) []string {
var out []string
for _, part := range strings.Split(s, ",") {
part = strings.TrimSpace(part)
if part != "" {
out = append(out, part)
}
}
return out
}
func providerKey(provider string) string {
if v := os.Getenv("MICRO_AI_API_KEY"); v != "" {
return v
}
return os.Getenv(providerEnv[provider])
}
func localRPCEnv(env []string) []string {
filtered := env[:0]
for _, kv := range env {
key, _, ok := strings.Cut(kv, "=")
if !ok {
filtered = append(filtered, kv)
continue
}
switch strings.ToUpper(key) {
case "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY":
continue
default:
filtered = append(filtered, kv)
}
}
return append(filtered, "HTTP_PROXY=", "HTTPS_PROXY=", "NO_PROXY=*")
}
func runHarness(provider, harness string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// Build the harness to a temp binary and run that, rather than `go run`:
// `go run` launches the compiled binary as a child it does not kill on
// context cancellation, so a harness that starts local services could
// outlive the timeout. Running the binary ourselves keeps the timeout
// honest — canceling the context kills the process that does the work.
binDir, err := os.MkdirTemp("", "harness-")
if err != nil {
return err
}
defer os.RemoveAll(binDir)
binPath := filepath.Join(binDir, harness)
build := exec.CommandContext(ctx, "go", "build", "-o", binPath, "./internal/harness/"+harness)
build.Stdout = os.Stdout
build.Stderr = os.Stderr
if err := build.Run(); err != nil {
return fmt.Errorf("build: %w", err)
}
cmd := exec.CommandContext(ctx, binPath, "-provider", provider)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = localRPCEnv(os.Environ())
if err := cmd.Run(); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("timed out after %s", timeout)
}
return err
}
return nil
}