update
This commit is contained in:
104
internal/vision/client_integration_test.go
Normal file
104
internal/vision/client_integration_test.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user