- testutil: 用 sync.Once 缓存 LollyImageAvailable 结果 - testutil: 原子计数器替代时间戳避免容器名竞态 - testutil: SetupProxyTest 接受 suffix 参数生成独立 Docker 网络 - testutil: CleanupProxyTest 显式调用 network.Remove() 清理 - testutil: 移除死代码 SetupProxyTestEnv/ProxyTestEnv - testutil: HealthCheckWaitTimeout 30s→15s, DefaultTestTimeout 180s→120s - e2e: 所有 107 个测试函数添加 t.Parallel() - e2e: 替换 65 处硬编码 30*time.Second 为常量 - make: test-all 三类测试并行运行,显式 PID wait 收集退出码 - make: test-e2e 添加 -parallel 4 flag Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
215 lines
5.0 KiB
Go
215 lines
5.0 KiB
Go
//go:build e2e
|
||
|
||
// Package testutil 提供 E2E 测试的工具函数。
|
||
//
|
||
// 包含统一的测试环境设置函数。
|
||
//
|
||
// 作者:xfy
|
||
package testutil
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/testcontainers/testcontainers-go"
|
||
)
|
||
|
||
// E2ETestEnv E2E 测试环境。
|
||
//
|
||
// 封装测试所需的资源和清理函数。
|
||
type E2ETestEnv struct {
|
||
Ctx context.Context
|
||
Network testcontainers.Network
|
||
NetworkName string
|
||
Pool *BackendPool
|
||
Lolly *LollyContainer
|
||
Client *http.Client
|
||
cleanup func()
|
||
}
|
||
|
||
// SetupE2ETest 设置 E2E 测试环境。
|
||
//
|
||
// 自动处理镜像检查、后端启动、lolly 启动和资源清理。
|
||
// 使用 t.Cleanup() 确保资源正确释放。
|
||
//
|
||
// 参数:
|
||
// - t: 测试对象
|
||
// - backendCount: 后端数量
|
||
// - cfgBuilder: 配置构建函数,接收后端池返回 YAML 配置
|
||
//
|
||
// 使用示例:
|
||
//
|
||
// env := testutil.SetupE2ETest(t, 2, func(pool *testutil.BackendPool) string {
|
||
// cfg := testutil.NewConfigBuilder().
|
||
// WithServer(":8080").
|
||
// WithProxy("/", pool.InternalAddresses())
|
||
// yaml, _ := cfg.Build()
|
||
// return yaml
|
||
// })
|
||
// defer env.Cleanup()
|
||
func SetupE2ETest(t *testing.T, backendCount int, cfgBuilder func(*BackendPool) string) *E2ETestEnv {
|
||
t.Helper()
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), DefaultTestTimeout)
|
||
|
||
if !LollyImageAvailable(ctx) {
|
||
t.Skip("lolly:latest image not available, run 'make docker-build' first")
|
||
}
|
||
|
||
netObj, networkName, pool, err := SetupProxyTest(ctx, backendCount, t.Name())
|
||
if err != nil {
|
||
cancel()
|
||
t.Fatalf("Failed to setup proxy test: %v", err)
|
||
}
|
||
|
||
cfgYAML := cfgBuilder(pool)
|
||
|
||
lolly, err := StartLolly(ctx,
|
||
WithConfigYAML(cfgYAML),
|
||
WithNetwork(networkName),
|
||
)
|
||
if err != nil {
|
||
CleanupProxyTest(ctx, netObj, networkName, pool)
|
||
cancel()
|
||
t.Fatalf("Failed to start lolly: %v", err)
|
||
}
|
||
|
||
if err := lolly.WaitForHealthy(ctx, HealthCheckWaitTimeout); err != nil {
|
||
lolly.Terminate(ctx)
|
||
CleanupProxyTest(ctx, netObj, networkName, pool)
|
||
cancel()
|
||
t.Fatalf("Lolly not healthy: %v", err)
|
||
}
|
||
|
||
env := &E2ETestEnv{
|
||
Ctx: ctx,
|
||
Network: netObj,
|
||
NetworkName: networkName,
|
||
Pool: pool,
|
||
Lolly: lolly,
|
||
Client: CreateDefaultHTTPClient(),
|
||
}
|
||
|
||
env.cleanup = func() {
|
||
lolly.Terminate(ctx)
|
||
CleanupProxyTest(ctx, netObj, networkName, pool)
|
||
cancel()
|
||
}
|
||
|
||
t.Cleanup(env.Cleanup)
|
||
|
||
return env
|
||
}
|
||
|
||
// SetupE2ETestWithTimeout 设置带自定义超时的 E2E 测试环境。
|
||
func SetupE2ETestWithTimeout(t *testing.T, backendCount int, timeout time.Duration, cfgBuilder func(*BackendPool) string) *E2ETestEnv {
|
||
t.Helper()
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||
|
||
if !LollyImageAvailable(ctx) {
|
||
t.Skip("lolly:latest image not available, run 'make docker-build' first")
|
||
}
|
||
|
||
netObj, networkName, pool, err := SetupProxyTest(ctx, backendCount, t.Name())
|
||
if err != nil {
|
||
cancel()
|
||
t.Fatalf("Failed to setup proxy test: %v", err)
|
||
}
|
||
|
||
cfgYAML := cfgBuilder(pool)
|
||
|
||
lolly, err := StartLolly(ctx,
|
||
WithConfigYAML(cfgYAML),
|
||
WithNetwork(networkName),
|
||
)
|
||
if err != nil {
|
||
CleanupProxyTest(ctx, netObj, networkName, pool)
|
||
cancel()
|
||
t.Fatalf("Failed to start lolly: %v", err)
|
||
}
|
||
|
||
if err := lolly.WaitForHealthy(ctx, HealthCheckWaitTimeout); err != nil {
|
||
lolly.Terminate(ctx)
|
||
CleanupProxyTest(ctx, netObj, networkName, pool)
|
||
cancel()
|
||
t.Fatalf("Lolly not healthy: %v", err)
|
||
}
|
||
|
||
env := &E2ETestEnv{
|
||
Ctx: ctx,
|
||
Network: netObj,
|
||
NetworkName: networkName,
|
||
Pool: pool,
|
||
Lolly: lolly,
|
||
Client: CreateDefaultHTTPClient(),
|
||
}
|
||
|
||
env.cleanup = func() {
|
||
lolly.Terminate(ctx)
|
||
CleanupProxyTest(ctx, netObj, networkName, pool)
|
||
cancel()
|
||
}
|
||
|
||
t.Cleanup(env.Cleanup)
|
||
|
||
return env
|
||
}
|
||
|
||
// Cleanup 手动清理资源。
|
||
func (e *E2ETestEnv) Cleanup() {
|
||
if e.cleanup != nil {
|
||
e.cleanup()
|
||
e.cleanup = nil
|
||
}
|
||
}
|
||
|
||
// HTTPURL 返回 lolly HTTP 地址。
|
||
func (e *E2ETestEnv) HTTPURL() string {
|
||
return e.Lolly.HTTPBaseURL()
|
||
}
|
||
|
||
// HTTPSURL 返回 lolly HTTPS 地址。
|
||
func (e *E2ETestEnv) HTTPSURL() string {
|
||
return e.Lolly.HTTPSBaseURL()
|
||
}
|
||
|
||
// SetupSSLTest 设置 SSL 测试环境。
|
||
//
|
||
// 用于 SSL/TLS 测试场景,自动生成证书。
|
||
func SetupSSLTest(t *testing.T, cfgBuilder func() string) (*LollyContainer, string, string) {
|
||
t.Helper()
|
||
|
||
ctx := context.Background()
|
||
|
||
if !LollyImageAvailable(ctx) {
|
||
t.Skip("lolly:latest image not available, run 'make docker-build' first")
|
||
}
|
||
|
||
// 生成自签名证书
|
||
certPath, keyPath, cleanup, err := GenerateSelfSignedCert(t.TempDir())
|
||
if err != nil {
|
||
t.Fatalf("Failed to generate certificate: %v", err)
|
||
}
|
||
t.Cleanup(cleanup)
|
||
|
||
cfgYAML := cfgBuilder()
|
||
|
||
lolly, err := StartLolly(ctx,
|
||
WithConfigYAML(cfgYAML),
|
||
WithCert(certPath, keyPath),
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("Failed to start lolly: %v", err)
|
||
}
|
||
t.Cleanup(func() { lolly.Terminate(ctx) })
|
||
|
||
if err := lolly.WaitForHealthy(ctx, HealthCheckWaitTimeout); err != nil {
|
||
t.Fatalf("Lolly not healthy: %v", err)
|
||
}
|
||
|
||
return lolly, certPath, keyPath
|
||
}
|