mirror of
https://github.com/redis/go-redis.git
synced 2026-10-22 19:49:00 +03:00
test(tests): TCP-probe before Ping in skip gates (#4001)
Tests that skip when Redis is absent gated on a client Ping to a hardcoded/helper address. A Ping to an addr with nothing listening does not fail fast: it pays the dial-retry (DialerRetries, default 5) plus command-retry budget first, ~1.7s per call. A recent RE integration nightly spent ~1.6 min across 68 such skips. Probe the address with a short-timeout TCP dial first, which fails in microseconds on connection-refused, then keep the existing Ping so a reachable-but-unusable server still skips rather than proceeds.
This commit is contained in:
@@ -42,6 +42,9 @@ func TestAutoPipelineLoneCallerFlushesImmediately(t *testing.T) {
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -97,6 +100,9 @@ func TestAutoPipelineExplicitDelayWaitsFullWindow(t *testing.T) {
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -192,6 +198,9 @@ func TestAutoPipelineWaveCoalesces(t *testing.T) {
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -322,6 +331,9 @@ func TestDoBypassesPipeline(t *testing.T) {
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr(), PipelineReadBufferSize: 64 << 10, PipelineWriteBufferSize: 64 << 10})
|
||||
defer client.Close()
|
||||
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -564,6 +576,9 @@ func TestDispatchChunkedAbortsAfterFailedPrefix(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr(), MaxRetries: -1})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -764,6 +779,9 @@ func TestCloseWaitsForDivertedCommand(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -794,6 +812,9 @@ func TestCloseWaitsForDivertedCommand(t *testing.T) {
|
||||
// opposite in its doc).
|
||||
func TestSharedClosedRejectsEveryEntryPoint(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
parent := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer parent.Close()
|
||||
if err := parent.Ping(ctx).Err(); err != nil {
|
||||
@@ -863,6 +884,9 @@ func TestDivertRegistrationRacesClose(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -933,6 +957,9 @@ func TestOtelMetricsDoNotAwaitOnAsyncFace(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1036,6 +1063,9 @@ func TestEvalDoesNotAwaitOnAsyncFace(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1191,6 +1221,9 @@ func TestNoRetryOrderObservedEndToEnd(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1227,6 +1260,9 @@ func TestNoRetrySplitExecutesEveryCommand(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1308,6 +1344,9 @@ func TestSuccessfulHookShortCircuitIsHonored(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1370,6 +1409,9 @@ func TestMustDivertKeepsCommandOffTheBatchPath(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1418,6 +1460,9 @@ func TestRetryRunsStopAfterFailedPrefix(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr(), MaxRetries: -1})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1459,6 +1504,9 @@ func TestAsyncProcessReportsSubmitRejection(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := NewClient(&Options{Addr: internalTestRedisAddr()})
|
||||
defer client.Close()
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -1507,6 +1555,9 @@ func TestAutopipelineSoloRoutingGate(t *testing.T) {
|
||||
}
|
||||
|
||||
// CSC client: needs a live RESP3 server for CLIENT TRACKING to attach.
|
||||
if err := probeRedis(internalTestRedisAddr()); err != nil {
|
||||
t.Skipf("no redis for the CSC half: %v", err)
|
||||
}
|
||||
if err := NewClient(&Options{Addr: internalTestRedisAddr(), Protocol: 3}).Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis for the CSC half: %v", err)
|
||||
}
|
||||
|
||||
@@ -321,6 +321,9 @@ func TestCSCNonZeroDBRejected(t *testing.T) {
|
||||
t.Cleanup(func() { _ = c.Close() })
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis(cscNativeAddr()); err != nil {
|
||||
t.Skipf("redis not available at %s: %v", cscNativeAddr(), err)
|
||||
}
|
||||
if err := c.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("redis not available at %s: %v", cscNativeAddr(), err)
|
||||
}
|
||||
@@ -371,6 +374,9 @@ func TestCSCReadYourWrites(t *testing.T) {
|
||||
t.Cleanup(func() { _ = mutator.Close() })
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis(cscNativeAddr()); err != nil {
|
||||
t.Skipf("redis not available at %s: %v", cscNativeAddr(), err)
|
||||
}
|
||||
if err := c.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("redis not available at %s: %v", cscNativeAddr(), err)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ func TestDigestBasic(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
@@ -74,6 +77,9 @@ func TestSetIFDEQWithDigest(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
@@ -138,6 +144,9 @@ func TestSetIFDNEWithDigest(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
@@ -202,6 +211,9 @@ func TestDelExArgsWithDigest(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
@@ -267,6 +279,9 @@ func TestDigestHelperMatchesRedis(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
@@ -326,6 +341,9 @@ func TestDigestBytesHelperMatchesRedis(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
@@ -382,6 +400,9 @@ func TestDigestHelperWithSetIFDEQ(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
@@ -439,6 +460,9 @@ func TestDigestHelperWithDelExArgs(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
|
||||
func newLanguagesTestClient(t *testing.T) *redis.Client {
|
||||
t.Helper()
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
|
||||
@@ -194,6 +194,9 @@ func TestPipelinePoolSpillsToMainPool(t *testing.T) {
|
||||
PoolTimeout: 100 * time.Millisecond, // spill latency bound
|
||||
})
|
||||
defer c.Close()
|
||||
if err := probeRedis(gateTestAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := c.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
@@ -265,6 +268,9 @@ func TestPipelineConnsSkipClientTracking(t *testing.T) {
|
||||
ClientSideCacheConfig: &ClientSideCacheConfig{},
|
||||
})
|
||||
defer c.Close()
|
||||
if err := probeRedis(gateTestAddr()); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := c.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ func TestWithPipelineConnSpillAccountsLimiterOnce(t *testing.T) {
|
||||
lim := &spillCountingLimiter{}
|
||||
c := NewClient(&Options{Addr: ":6379", PipelinePoolSize: 1, Limiter: lim})
|
||||
defer c.Close()
|
||||
if err := probeRedis(":6379"); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
if err := c.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("no redis: %v", err)
|
||||
}
|
||||
|
||||
18
probe_internal_test.go
Normal file
18
probe_internal_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// probeRedis is the package redis copy of the redis_test helper of the same
|
||||
// name (the internal tests cannot import the external test package). See
|
||||
// probe_test.go for the rationale: a fast TCP probe avoids the ~1.7s
|
||||
// dial-retry + command-retry cost a Ping pays when nothing is listening.
|
||||
func probeRedis(addr string) error {
|
||||
c, err := net.DialTimeout("tcp", addr, 300*time.Millisecond)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Close()
|
||||
}
|
||||
27
probe_test.go
Normal file
27
probe_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// probeRedis reports whether a TCP listener answers at addr within a short
|
||||
// timeout. Tests that skip when Redis is absent call this before building a
|
||||
// client and Ping-ing it: a Ping to an addr with nothing listening does not
|
||||
// return immediately, it pays go-redis's dial-retry (DialerRetries, 5 by
|
||||
// default) plus command-retry (MaxRetries) budget first — ~1.7s per call. In an
|
||||
// environment without a local Redis (for example the RE nightly, whose real
|
||||
// endpoint is remote), hundreds of such skips add up to minutes of wasted wall
|
||||
// time. A single short-timeout TCP dial fails in microseconds on
|
||||
// connection-refused instead.
|
||||
//
|
||||
// A nil return only means something is listening; callers keep their Ping so a
|
||||
// reachable-but-unusable server (auth required, a different service on the port)
|
||||
// still skips rather than proceeds.
|
||||
func probeRedis(addr string) error {
|
||||
c, err := net.DialTimeout("tcp", addr, 300*time.Millisecond)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Close()
|
||||
}
|
||||
@@ -41,6 +41,9 @@ func TestFTAggregateCollect_Integration(t *testing.T) {
|
||||
// REDIS_VERSION env variable (whose float form cannot tell 8.10 from 8.1
|
||||
// and defaults when unset).
|
||||
probeCtx := context.Background()
|
||||
if err := probeRedis(collectTestAddr()); err != nil {
|
||||
t.Skipf("redis not reachable at %s: %v", collectTestAddr(), err)
|
||||
}
|
||||
probe := redis.NewClient(&redis.Options{Addr: collectTestAddr()})
|
||||
t.Cleanup(func() { _ = probe.Close() })
|
||||
if err := probe.Ping(probeCtx).Err(); err != nil {
|
||||
|
||||
@@ -28,6 +28,9 @@ func TestSearchCommandsRESP2AndRESP3Equivalence(t *testing.T) {
|
||||
defer client3.Close()
|
||||
|
||||
// Check connection
|
||||
if err := probeRedis("localhost:6379"); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
if err := client2.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user