This commit is contained in:
toom1996
2026-10-18 17:42:10 +08:00
parent 1b06bd5bda
commit 1de1ec46a6
58 changed files with 5281 additions and 19191 deletions

View File

@ -0,0 +1,104 @@
//go:build integration
// 集成测试:打真实运行的视觉服务,验证 Go 客户端与服务端的接口契约。
//
// 运行前先起服务(本地开发用项目 .venv,走 GPU):
//
// cd D:\project\fashion-siglip
// .venv\Scripts\python.exe -m uvicorn app:app --host 127.0.0.1 --port 8900 --workers 1
//
// 然后:
//
// go test -tags integration ./internal/vision/ -run TestClientAgainstRealService -v
//
// 服务未启动时本测试 **跳过**(不算失败),以免阻塞没有该服务的环境。
// 可用 VISION_ENDPOINT 环境变量指向其它地址。
package vision
import (
"bytes"
"context"
"encoding/json"
"image"
"image/color"
"image/png"
"net/http"
"os"
"testing"
"time"
)
func TestClientAgainstRealService(t *testing.T) {
endpoint := os.Getenv("VISION_ENDPOINT")
if endpoint == "" {
endpoint = "http://127.0.0.1:8900"
}
// 1) 探活:不通就跳过(本测试依赖外部进程,不应让 CI 变红)。
hc := &http.Client{Timeout: 3 * time.Second}
resp, err := hc.Get(endpoint + "/health")
if err != nil {
t.Skipf("视觉服务不可达(%s),跳过集成测试:%v", endpoint, err)
}
defer func() { _ = resp.Body.Close() }()
var health struct {
Ready bool `json:"ready"`
Dim int `json:"dim"`
Device string `json:"device"`
}
if err := json.NewDecoder(resp.Body).Decode(&health); err != nil {
t.Fatalf("解析 /health 失败: %v", err)
}
if !health.Ready {
t.Skipf("视觉服务未就绪(模型仍在加载或加载失败),跳过:%+v", health)
}
t.Logf("服务就绪:dim=%d device=%s", health.Dim, health.Device)
// 2) 用一张程序生成的 PNG 验证完整契约:base64 请求 → 服务解码 → 768 维归一化向量。
img := generateTestPNG(t)
c := New(endpoint, health.Dim, 60*time.Second, 3)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
vecs, skipped, err := c.Embed(ctx, [][]byte{img})
if err != nil {
t.Fatalf("Embed 失败: %v", err)
}
if len(skipped) != 0 {
t.Fatalf("合法 PNG 不应被跳过,skipped=%v", skipped)
}
if len(vecs) != 1 {
t.Fatalf("应返回 1 个向量,得到 %d", len(vecs))
}
if got := len(vecs[0]); got != health.Dim {
t.Fatalf("向量维度应为 %d,得到 %d", health.Dim, got)
}
// 服务端承诺已 L2 归一化:模长≈1 是「点积即余弦相似度」的前提。
var sumSq float64
for _, f := range vecs[0] {
sumSq += float64(f) * float64(f)
}
norm := sumSq
if norm < 0.99 || norm > 1.01 {
t.Fatalf("向量应已 L2 归一化(模长²≈1),实际 %f", norm)
}
}
// generateTestPNG 生成一张 64x64 的纯色 PNG(不引入外部测试资源)。
func generateTestPNG(t *testing.T) []byte {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, 64, 64))
for x := 0; x < 64; x++ {
for y := 0; y < 64; y++ {
img.Set(x, y, color.RGBA{R: 200, G: 60, B: 120, A: 255})
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
t.Fatalf("生成测试 PNG 失败: %v", err)
}
return buf.Bytes()
}