- Wire face service into main.go with FACE_SERVICE_ENABLED toggle - Add integration tests for AWS Rekognition (build tag: integration) - Add performance benchmarks for face operations (build tag: performance) - Fix PhotoRepository interface to use store types directly - Remove duplicate PhotoInfo/FaceEmbeddingInfo types from face package - Add go.mod replace directive for local module resolution Integration tests: go test -tags=integration ./internal/face/... Performance tests: go test -tags=performance -bench=. ./internal/face/... Standard tests: go test ./...
225 lines
5.8 KiB
Go
225 lines
5.8 KiB
Go
//go:build performance
|
|
|
|
package face
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// BenchmarkFaceDetection benchmarks face detection performance
|
|
func BenchmarkFaceDetection(b *testing.B) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
b.Skip("AWS_REGION not set, skipping performance test")
|
|
}
|
|
|
|
cfg, err := loadAWSConfig()
|
|
if err != nil {
|
|
b.Fatalf("failed to load AWS config: %v", err)
|
|
}
|
|
|
|
collectionID := os.Getenv("REKOGNITION_COLLECTION")
|
|
if collectionID == "" {
|
|
collectionID = "hatch-faces-perf"
|
|
}
|
|
|
|
client := NewRekognitionClient(cfg, collectionID)
|
|
ctx := context.Background()
|
|
|
|
// Load test image
|
|
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
|
|
if err != nil {
|
|
b.Fatalf("failed to load test image: %v", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := client.DetectFaces(ctx, imageBytes)
|
|
if err != nil {
|
|
b.Fatalf("failed to detect faces: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkFaceIndexing benchmarks face indexing performance
|
|
func BenchmarkFaceIndexing(b *testing.B) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
b.Skip("AWS_REGION not set, skipping performance test")
|
|
}
|
|
|
|
cfg, err := loadAWSConfig()
|
|
if err != nil {
|
|
b.Fatalf("failed to load AWS config: %v", err)
|
|
}
|
|
|
|
collectionID := os.Getenv("REKOGNITION_COLLECTION")
|
|
if collectionID == "" {
|
|
collectionID = "hatch-faces-perf"
|
|
}
|
|
|
|
client := NewRekognitionClient(cfg, collectionID)
|
|
ctx := context.Background()
|
|
|
|
// Create collection
|
|
if err := client.CreateCollection(ctx, collectionID); err != nil {
|
|
b.Fatalf("failed to create collection: %v", err)
|
|
}
|
|
defer client.DeleteCollection(ctx, collectionID)
|
|
|
|
// Load test image
|
|
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
|
|
if err != nil {
|
|
b.Fatalf("failed to load test image: %v", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
photoID := fmt.Sprintf("perf-test-%d", time.Now().UnixNano())
|
|
_, err := client.IndexFace(ctx, imageBytes, photoID)
|
|
if err != nil {
|
|
b.Fatalf("failed to index face: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkFaceSearch benchmarks face search performance
|
|
func BenchmarkFaceSearch(b *testing.B) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
b.Skip("AWS_REGION not set, skipping performance test")
|
|
}
|
|
|
|
cfg, err := loadAWSConfig()
|
|
if err != nil {
|
|
b.Fatalf("failed to load AWS config: %v", err)
|
|
}
|
|
|
|
collectionID := os.Getenv("REKOGNITION_COLLECTION")
|
|
if collectionID == "" {
|
|
collectionID = "hatch-faces-perf"
|
|
}
|
|
|
|
client := NewRekognitionClient(cfg, collectionID)
|
|
ctx := context.Background()
|
|
|
|
// Create collection
|
|
if err := client.CreateCollection(ctx, collectionID); err != nil {
|
|
b.Fatalf("failed to create collection: %v", err)
|
|
}
|
|
defer client.DeleteCollection(ctx, collectionID)
|
|
|
|
// Load and index test image
|
|
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
|
|
if err != nil {
|
|
b.Fatalf("failed to load test image: %v", err)
|
|
}
|
|
|
|
photoID := "perf-search-" + time.Now().Format("20060102150405")
|
|
_, err = client.IndexFace(ctx, imageBytes, photoID)
|
|
if err != nil {
|
|
b.Fatalf("failed to index face: %v", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
|
|
if err != nil {
|
|
b.Fatalf("failed to search faces: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPerformanceTargets validates performance targets
|
|
func TestPerformanceTargets(t *testing.T) {
|
|
if os.Getenv("AWS_REGION") == "" {
|
|
t.Skip("AWS_REGION not set, skipping performance test")
|
|
}
|
|
|
|
cfg, err := loadAWSConfig()
|
|
if err != nil {
|
|
t.Fatalf("failed to load AWS config: %v", err)
|
|
}
|
|
|
|
collectionID := os.Getenv("REKOGNITION_COLLECTION")
|
|
if collectionID == "" {
|
|
collectionID = "hatch-faces-perf"
|
|
}
|
|
|
|
client := NewRekognitionClient(cfg, collectionID)
|
|
ctx := context.Background()
|
|
|
|
// Create collection
|
|
if err := client.CreateCollection(ctx, collectionID); err != nil {
|
|
t.Fatalf("failed to create collection: %v", err)
|
|
}
|
|
defer client.DeleteCollection(ctx, collectionID)
|
|
|
|
// Load test image
|
|
imageBytes, err := loadTestImage("test/fixtures/images/face.jpg")
|
|
if err != nil {
|
|
t.Fatalf("failed to load test image: %v", err)
|
|
}
|
|
|
|
// Test face detection latency
|
|
start := time.Now()
|
|
_, err = client.DetectFaces(ctx, imageBytes)
|
|
if err != nil {
|
|
t.Fatalf("failed to detect faces: %v", err)
|
|
}
|
|
detectionLatency := time.Since(start)
|
|
if detectionLatency > 2*time.Second {
|
|
t.Errorf("face detection latency exceeded 2 seconds: %v", detectionLatency)
|
|
}
|
|
t.Logf("Face detection latency: %v", detectionLatency)
|
|
|
|
// Test face indexing latency
|
|
start = time.Now()
|
|
photoID := "perf-target-" + time.Now().Format("20060102150405")
|
|
_, err = client.IndexFace(ctx, imageBytes, photoID)
|
|
if err != nil {
|
|
t.Fatalf("failed to index face: %v", err)
|
|
}
|
|
indexingLatency := time.Since(start)
|
|
if indexingLatency > 3*time.Second {
|
|
t.Errorf("face indexing latency exceeded 3 seconds: %v", indexingLatency)
|
|
}
|
|
t.Logf("Face indexing latency: %v", indexingLatency)
|
|
|
|
// Test face search latency
|
|
start = time.Now()
|
|
_, err = client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
|
|
if err != nil {
|
|
t.Fatalf("failed to search faces: %v", err)
|
|
}
|
|
searchLatency := time.Since(start)
|
|
if searchLatency > 2*time.Second {
|
|
t.Errorf("face search latency exceeded 2 seconds: %v", searchLatency)
|
|
}
|
|
t.Logf("Face search latency: %v", searchLatency)
|
|
|
|
// Test end-to-end latency
|
|
start = time.Now()
|
|
// Detect
|
|
_, err = client.DetectFaces(ctx, imageBytes)
|
|
if err != nil {
|
|
t.Fatalf("failed to detect faces: %v", err)
|
|
}
|
|
// Index
|
|
photoID = "perf-e2e-" + time.Now().Format("20060102150405")
|
|
_, err = client.IndexFace(ctx, imageBytes, photoID)
|
|
if err != nil {
|
|
t.Fatalf("failed to index face: %v", err)
|
|
}
|
|
// Search
|
|
_, err = client.SearchFacesByImage(ctx, imageBytes, 10, 80.0)
|
|
if err != nil {
|
|
t.Fatalf("failed to search faces: %v", err)
|
|
}
|
|
e2eLatency := time.Since(start)
|
|
if e2eLatency > 5*time.Second {
|
|
t.Errorf("end-to-end latency exceeded 5 seconds: %v", e2eLatency)
|
|
}
|
|
t.Logf("End-to-end latency: %v", e2eLatency)
|
|
} |