Files
xlgo-core/database/redis_hdb1_internal_test.go
2026-07-14 10:24:10 +08:00

85 lines
2.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"context"
"net"
"testing"
"time"
"github.com/redis/go-redis/v9"
)
// startHungRedisListener 启动一个接受 TCP 连接但不读写任何数据的 listener,
// 模拟"挂起 Redis"(连接建立但不响应 RESP,区别于宕机的 connection-refused)。
// 返回 listener 地址;t.Cleanup 关闭 listener。
func startHungRedisListener(t *testing.T) string {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("hung redis listen: %v", err)
}
t.Cleanup(func() { _ = ln.Close() })
go func() {
for {
conn, err := ln.Accept()
if err != nil {
return // listener 关闭
}
// 故意不读写、不关闭,持住连接模拟挂起。连接由 t.Cleanup 的 ln.Close 间接清理。
_ = conn
}
}()
return ln.Addr().String()
}
// TestRedisHealthCheckBoundsHungRedis_Hdb1 回归 H-db-1 边界(Redis 侧):
// RedisManager.HealthCheck(Background) 对挂起 Redis(连接活但不响应)应受
// redis client 的 ReadTimeout(3sredis.go D7) 约束有界返回错误,不无限阻塞。
// ctx 无 deadline 时由 client ReadTimeout 兜底;ctx 自带更短 deadline 时优先尊重 ctx。
// 修复前/缺 ReadTimeout 时 Ping(Background) 会无限阻塞。
func TestRedisHealthCheckBoundsHungRedis_Hdb1(t *testing.T) {
addr := startHungRedisListener(t)
cfg := redisTestConfig(t, addr)
m := NewRedisManager()
if err := m.Init(cfg); err == nil {
// Init 的 Ping 有 5s ctx,挂起 Redis 下应在 ~3sReadTimeout)失败返回错误。
t.Fatalf("挂起 Redis 的 Init 应返回错误,got nil")
}
// Init 失败后 m.client 未被安装(nil),HealthCheck 返 "Redis 未初始化"。
// 为测 HealthCheck 自身的 ping 超时约束,需 client 已安装但指向挂起 Redis。
// 用 setClientForTest 注入一个指向挂起 Redis 的 client。
client := newRedisClientForTest(addr)
old := m.setClientForTest(client)
t.Cleanup(func() {
if old != nil {
_ = old.Close()
}
_ = client.Close()
})
// HealthCheck(Background):挂起 Redis 下 client.Ping 受 ReadTimeout 3s 约束有界返回。
done := make(chan error, 1)
start := time.Now()
go func() { done <- m.HealthCheck(context.Background()) }()
select {
case err := <-done:
elapsed := time.Since(start)
if err == nil {
t.Fatalf("挂起 Redis 的 HealthCheck 应返回错误,got nil")
}
// 应在 ReadTimeout(3s) 附近返回,给 6s 上限(含连接/重试余量)。
if elapsed > 6*time.Second {
t.Fatalf("HealthCheck 耗时 %v 超过 6s 上限(应受 ReadTimeout 3s 约束)", elapsed)
}
case <-time.After(10 * time.Second):
t.Fatalf("HealthCheck 在挂起 Redis 上无限阻塞(ReadTimeout 未约束 Ping")
}
}
// newRedisClientForTest 构造指向 addr 的 redis.Client(复用 Init 的 client 配置,
// 含 DialTimeout 5s / ReadTimeout 3s / WriteTimeout 3s)。
func newRedisClientForTest(addr string) *redis.Client {
return newRedisClient(addr, "", 0)
}