- 新增 testutil/retry.go 提供重试工具函数 - WaitForCondition: 等待条件满足 - WaitForNoError: 等待操作无错误 - WaitForHealthy: 等待服务健康 - Poll: 简化轮询接口 - 新增 testutil/retry_test.go 单元测试 - container.go 添加 Logs() 方法用于诊断 - Makefile 新增 test-e2e-cover 和 test-e2e-short 目标 - 重构 healthcheck 和 loadbalance 测试使用重试机制 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
205 lines
5.4 KiB
Go
205 lines
5.4 KiB
Go
//go:build e2e
|
|
|
|
package testutil
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestWaitForConditionSuccess 测试条件满足场景。
|
|
func TestWaitForConditionSuccess(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
count := 0
|
|
err := WaitForCondition(ctx, RetryConfig{
|
|
Interval: 10 * time.Millisecond,
|
|
Timeout: 100 * time.Millisecond,
|
|
}, func() bool {
|
|
count++
|
|
return count >= 3
|
|
})
|
|
|
|
require.NoError(t, err, "Should succeed when condition is met")
|
|
assert.GreaterOrEqual(t, count, 3, "Should have retried at least 3 times")
|
|
}
|
|
|
|
// TestWaitForConditionTimeout 测试超时场景。
|
|
func TestWaitForConditionTimeout(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
start := time.Now()
|
|
err := WaitForCondition(ctx, RetryConfig{
|
|
Interval: 10 * time.Millisecond,
|
|
Timeout: 50 * time.Millisecond,
|
|
}, func() bool {
|
|
return false // 永远不满足
|
|
})
|
|
|
|
elapsed := time.Since(start)
|
|
|
|
require.Error(t, err, "Should fail when condition is never met")
|
|
assert.Contains(t, err.Error(), "condition not met")
|
|
assert.Less(t, elapsed, 100*time.Millisecond, "Should timeout around the specified duration")
|
|
}
|
|
|
|
// TestWaitForConditionMaxRetries 测试最大重试次数。
|
|
func TestWaitForConditionMaxRetries(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
count := 0
|
|
err := WaitForCondition(ctx, RetryConfig{
|
|
Interval: 10 * time.Millisecond,
|
|
Timeout: 1 * time.Second,
|
|
MaxRetries: 3,
|
|
}, func() bool {
|
|
count++
|
|
return false
|
|
})
|
|
|
|
require.Error(t, err, "Should fail after max retries")
|
|
assert.Contains(t, err.Error(), "3 retries")
|
|
assert.Equal(t, 3, count, "Should have retried exactly 3 times")
|
|
}
|
|
|
|
// TestWaitForConditionContextCancel 测试上下文取消。
|
|
func TestWaitForConditionContextCancel(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
// 50ms 后取消
|
|
go func() {
|
|
time.Sleep(50 * time.Millisecond)
|
|
cancel()
|
|
}()
|
|
|
|
err := WaitForCondition(ctx, RetryConfig{
|
|
Interval: 10 * time.Millisecond,
|
|
Timeout: 1 * time.Second,
|
|
}, func() bool {
|
|
return false
|
|
})
|
|
|
|
require.Error(t, err, "Should fail when context is cancelled")
|
|
assert.Contains(t, err.Error(), "context canceled")
|
|
}
|
|
|
|
// TestWaitForNoErrorSuccess 测试操作成功场景。
|
|
func TestWaitForNoErrorSuccess(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
count := 0
|
|
err := WaitForNoError(ctx, RetryConfig{
|
|
Interval: 10 * time.Millisecond,
|
|
Timeout: 100 * time.Millisecond,
|
|
}, func() error {
|
|
count++
|
|
if count < 3 {
|
|
return errors.New("not ready")
|
|
}
|
|
return nil
|
|
})
|
|
|
|
require.NoError(t, err, "Should succeed when operation returns nil")
|
|
assert.GreaterOrEqual(t, count, 3, "Should have retried at least 3 times")
|
|
}
|
|
|
|
// TestWaitForNoErrorTimeout 测试操作超时场景。
|
|
func TestWaitForNoErrorTimeout(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
err := WaitForNoError(ctx, RetryConfig{
|
|
Interval: 10 * time.Millisecond,
|
|
Timeout: 50 * time.Millisecond,
|
|
}, func() error {
|
|
return errors.New("always fails")
|
|
})
|
|
|
|
require.Error(t, err, "Should fail when operation always returns error")
|
|
assert.Contains(t, err.Error(), "operation failed")
|
|
assert.Contains(t, err.Error(), "always fails")
|
|
}
|
|
|
|
// TestWaitForNoErrorMaxRetries 测试最大重试次数。
|
|
func TestWaitForNoErrorMaxRetries(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
count := 0
|
|
err := WaitForNoError(ctx, RetryConfig{
|
|
Interval: 10 * time.Millisecond,
|
|
Timeout: 1 * time.Second,
|
|
MaxRetries: 2,
|
|
}, func() error {
|
|
count++
|
|
return errors.New("fail")
|
|
})
|
|
|
|
require.Error(t, err, "Should fail after max retries")
|
|
assert.Contains(t, err.Error(), "2 retries")
|
|
assert.Equal(t, 2, count, "Should have retried exactly 2 times")
|
|
}
|
|
|
|
// TestDefaultRetryConfig 测试默认配置。
|
|
func TestDefaultRetryConfig(t *testing.T) {
|
|
assert.Equal(t, 500*time.Millisecond, DefaultRetryConfig.Interval)
|
|
assert.Equal(t, 30*time.Second, DefaultRetryConfig.Timeout)
|
|
assert.Equal(t, 0, DefaultRetryConfig.MaxRetries)
|
|
}
|
|
|
|
// TestRetryConfigZeroValues 测试零值配置使用默认值。
|
|
func TestRetryConfigZeroValues(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// 零值配置应该使用默认值
|
|
count := 0
|
|
err := WaitForCondition(ctx, RetryConfig{}, func() bool {
|
|
count++
|
|
return count >= 1
|
|
})
|
|
|
|
require.NoError(t, err, "Should use default config values")
|
|
}
|
|
|
|
// TestPollSuccess 测试轮询成功。
|
|
func TestPollSuccess(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
count := 0
|
|
err := Poll(ctx, 10*time.Millisecond, 100*time.Millisecond, func() (bool, error) {
|
|
count++
|
|
return count >= 3, nil
|
|
})
|
|
|
|
require.NoError(t, err, "Poll should succeed")
|
|
assert.GreaterOrEqual(t, count, 3)
|
|
}
|
|
|
|
// TestPollError 测试轮询返回错误。
|
|
func TestPollError(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
err := Poll(ctx, 10*time.Millisecond, 50*time.Millisecond, func() (bool, error) {
|
|
return false, errors.New("poll error")
|
|
})
|
|
|
|
require.Error(t, err, "Poll should fail with error")
|
|
assert.Contains(t, err.Error(), "poll error")
|
|
}
|
|
|
|
// TestWaitForHealthySuccess 测试等待健康检查成功。
|
|
func TestWaitForHealthySuccess(t *testing.T) {
|
|
// 这个测试需要 HTTP 服务器,在集成测试中验证
|
|
// 这里只测试函数签名和基本逻辑
|
|
t.Log("WaitForHealthy function exists and has correct signature")
|
|
}
|
|
|
|
// TestWaitForBackendHealthySuccess 测试等待后端健康。
|
|
func TestWaitForBackendHealthySuccess(t *testing.T) {
|
|
// 这个测试需要 HTTP 服务器,在集成测试中验证
|
|
t.Log("WaitForBackendHealthy function exists and has correct signature")
|
|
}
|