2012-07-25 17:00:50 +04:00
|
|
|
package redis_test
|
|
|
|
|
|
|
|
import (
|
2016-03-16 17:57:24 +03:00
|
|
|
"bytes"
|
2019-06-04 14:05:29 +03:00
|
|
|
"context"
|
2020-02-02 12:54:42 +03:00
|
|
|
"errors"
|
2023-03-06 16:31:39 +03:00
|
|
|
"fmt"
|
2012-08-06 12:33:49 +04:00
|
|
|
"net"
|
2024-11-20 14:38:06 +03:00
|
|
|
"sync"
|
2020-02-02 12:54:42 +03:00
|
|
|
"testing"
|
2016-12-03 18:30:13 +03:00
|
|
|
"time"
|
|
|
|
|
2023-01-27 18:00:49 +03:00
|
|
|
. "github.com/bsm/ginkgo/v2"
|
|
|
|
. "github.com/bsm/gomega"
|
2021-09-08 16:00:52 +03:00
|
|
|
|
2023-01-23 09:48:54 +03:00
|
|
|
"github.com/redis/go-redis/v9"
|
2015-05-14 16:13:45 +03:00
|
|
|
)
|
2015-04-28 18:14:19 +03:00
|
|
|
|
2022-10-11 15:37:34 +03:00
|
|
|
type redisHookError struct{}
|
2020-02-02 12:54:42 +03:00
|
|
|
|
|
|
|
var _ redis.Hook = redisHookError{}
|
|
|
|
|
2022-10-11 15:37:34 +03:00
|
|
|
func (redisHookError) DialHook(hook redis.DialHook) redis.DialHook {
|
|
|
|
return hook
|
|
|
|
}
|
|
|
|
|
|
|
|
func (redisHookError) ProcessHook(hook redis.ProcessHook) redis.ProcessHook {
|
|
|
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
|
|
|
return errors.New("hook error")
|
|
|
|
}
|
2020-02-02 12:54:42 +03:00
|
|
|
}
|
|
|
|
|
2022-10-11 15:37:34 +03:00
|
|
|
func (redisHookError) ProcessPipelineHook(hook redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
|
|
|
return hook
|
2020-02-02 12:54:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestHookError(t *testing.T) {
|
|
|
|
rdb := redis.NewClient(&redis.Options{
|
|
|
|
Addr: ":6379",
|
|
|
|
})
|
|
|
|
rdb.AddHook(redisHookError{})
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err := rdb.Ping(ctx).Err()
|
2020-02-02 12:54:42 +03:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("got nil, expected an error")
|
|
|
|
}
|
|
|
|
|
|
|
|
wanted := "hook error"
|
|
|
|
if err.Error() != wanted {
|
|
|
|
t.Fatalf(`got %q, wanted %q`, err, wanted)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
2015-01-15 18:51:22 +03:00
|
|
|
var _ = Describe("Client", func() {
|
|
|
|
var client *redis.Client
|
2012-07-25 17:00:50 +04:00
|
|
|
|
2015-01-15 18:51:22 +03:00
|
|
|
BeforeEach(func() {
|
2016-03-16 17:57:24 +03:00
|
|
|
client = redis.NewClient(redisOptions())
|
2020-03-11 17:26:42 +03:00
|
|
|
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
2014-05-11 11:42:40 +04:00
|
|
|
})
|
|
|
|
|
2015-01-15 18:51:22 +03:00
|
|
|
AfterEach(func() {
|
2015-05-02 16:11:18 +03:00
|
|
|
client.Close()
|
2014-05-11 11:42:40 +04:00
|
|
|
})
|
|
|
|
|
2020-02-02 15:59:27 +03:00
|
|
|
It("should Stringer", func() {
|
2024-01-01 23:19:22 +03:00
|
|
|
if RECluster {
|
|
|
|
Expect(client.String()).To(Equal(fmt.Sprintf("Redis<:%s db:0>", redisPort)))
|
|
|
|
} else {
|
|
|
|
Expect(client.String()).To(Equal(fmt.Sprintf("Redis<:%s db:15>", redisPort)))
|
|
|
|
}
|
2020-02-02 15:59:27 +03:00
|
|
|
})
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
It("supports context", func() {
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
2019-07-04 11:18:06 +03:00
|
|
|
cancel()
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.Ping(ctx).Err()
|
2019-07-04 11:18:06 +03:00
|
|
|
Expect(err).To(MatchError("context canceled"))
|
|
|
|
})
|
|
|
|
|
2024-01-01 23:19:22 +03:00
|
|
|
It("supports WithTimeout", Label("NonRedisEnterprise"), func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.ClientPause(ctx, time.Second).Err()
|
2020-02-02 15:59:27 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err = client.WithTimeout(10 * time.Millisecond).Ping(ctx).Err()
|
2020-02-02 15:59:27 +03:00
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err = client.Ping(ctx).Err()
|
2020-02-02 15:59:27 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
2015-05-15 15:21:28 +03:00
|
|
|
})
|
|
|
|
|
2015-01-15 18:51:22 +03:00
|
|
|
It("should ping", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
val, err := client.Ping(ctx).Result()
|
2015-01-15 18:51:22 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(val).To(Equal("PONG"))
|
2014-05-11 11:42:40 +04:00
|
|
|
})
|
2013-02-02 14:50:43 +04:00
|
|
|
|
2016-01-19 19:36:40 +03:00
|
|
|
It("should return pool stats", func() {
|
|
|
|
Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{}))
|
|
|
|
})
|
|
|
|
|
2015-01-15 18:51:22 +03:00
|
|
|
It("should support custom dialers", func() {
|
|
|
|
custom := redis.NewClient(&redis.Options{
|
2019-05-18 14:00:07 +03:00
|
|
|
Network: "tcp",
|
|
|
|
Addr: redisAddr,
|
2019-06-04 14:05:29 +03:00
|
|
|
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
2019-07-30 23:03:40 +03:00
|
|
|
var d net.Dialer
|
|
|
|
return d.DialContext(ctx, network, addr)
|
2015-01-15 18:51:22 +03:00
|
|
|
},
|
|
|
|
})
|
2014-09-30 12:46:56 +04:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
val, err := custom.Ping(ctx).Result()
|
2015-01-15 18:51:22 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(val).To(Equal("PONG"))
|
|
|
|
Expect(custom.Close()).NotTo(HaveOccurred())
|
2014-05-11 11:42:40 +04:00
|
|
|
})
|
2012-08-25 16:35:39 +04:00
|
|
|
|
2015-01-15 18:51:22 +03:00
|
|
|
It("should close", func() {
|
|
|
|
Expect(client.Close()).NotTo(HaveOccurred())
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.Ping(ctx).Err()
|
2015-01-15 18:51:22 +03:00
|
|
|
Expect(err).To(MatchError("redis: client is closed"))
|
2014-05-11 11:42:40 +04:00
|
|
|
})
|
2012-08-25 16:35:39 +04:00
|
|
|
|
2016-03-02 14:26:05 +03:00
|
|
|
It("should close pubsub without closing the client", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
pubsub := client.Subscribe(ctx)
|
2015-01-15 18:51:22 +03:00
|
|
|
Expect(pubsub.Close()).NotTo(HaveOccurred())
|
2014-05-11 11:42:40 +04:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
_, err := pubsub.Receive(ctx)
|
2015-01-15 18:51:22 +03:00
|
|
|
Expect(err).To(MatchError("redis: client is closed"))
|
2020-03-11 17:26:42 +03:00
|
|
|
Expect(client.Ping(ctx).Err()).NotTo(HaveOccurred())
|
2014-05-11 11:42:40 +04:00
|
|
|
})
|
|
|
|
|
2016-05-02 15:54:15 +03:00
|
|
|
It("should close Tx without closing the client", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.Watch(ctx, func(tx *redis.Tx) error {
|
|
|
|
_, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
|
|
|
|
pipe.Ping(ctx)
|
2016-05-02 15:54:15 +03:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
2015-01-15 18:51:22 +03:00
|
|
|
})
|
2016-05-02 15:54:15 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
2016-03-02 14:26:05 +03:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
Expect(client.Ping(ctx).Err()).NotTo(HaveOccurred())
|
2014-05-11 11:42:40 +04:00
|
|
|
})
|
|
|
|
|
2015-06-03 16:45:46 +03:00
|
|
|
It("should close pubsub when client is closed", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
pubsub := client.Subscribe(ctx)
|
2015-06-03 16:45:46 +03:00
|
|
|
Expect(client.Close()).NotTo(HaveOccurred())
|
2016-07-21 16:04:40 +03:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
_, err := pubsub.Receive(ctx)
|
2017-04-17 15:43:58 +03:00
|
|
|
Expect(err).To(MatchError("redis: client is closed"))
|
2016-07-21 16:04:40 +03:00
|
|
|
|
2015-06-03 16:45:46 +03:00
|
|
|
Expect(pubsub.Close()).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
2024-01-01 23:19:22 +03:00
|
|
|
It("should select DB", Label("NonRedisEnterprise"), func() {
|
2016-03-16 17:57:24 +03:00
|
|
|
db2 := redis.NewClient(&redis.Options{
|
2015-01-15 18:51:22 +03:00
|
|
|
Addr: redisAddr,
|
2016-03-16 17:57:24 +03:00
|
|
|
DB: 2,
|
2015-01-15 18:51:22 +03:00
|
|
|
})
|
2020-03-11 17:26:42 +03:00
|
|
|
Expect(db2.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
|
|
|
Expect(db2.Get(ctx, "db").Err()).To(Equal(redis.Nil))
|
|
|
|
Expect(db2.Set(ctx, "db", 2, 0).Err()).NotTo(HaveOccurred())
|
2012-08-11 18:42:10 +04:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
n, err := db2.Get(ctx, "db").Int64()
|
2016-03-16 17:57:24 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(n).To(Equal(int64(2)))
|
2012-08-26 13:18:42 +04:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
Expect(client.Get(ctx, "db").Err()).To(Equal(redis.Nil))
|
2013-12-30 15:45:04 +04:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
Expect(db2.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
2016-03-16 17:57:24 +03:00
|
|
|
Expect(db2.Close()).NotTo(HaveOccurred())
|
|
|
|
})
|
2015-07-13 13:45:37 +03:00
|
|
|
|
2022-12-28 17:14:52 +03:00
|
|
|
It("should client setname", func() {
|
|
|
|
opt := redisOptions()
|
|
|
|
opt.ClientName = "hi"
|
|
|
|
db := redis.NewClient(opt)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
Expect(db.Close()).NotTo(HaveOccurred())
|
|
|
|
}()
|
|
|
|
|
|
|
|
Expect(db.Ping(ctx).Err()).NotTo(HaveOccurred())
|
|
|
|
val, err := db.ClientList(ctx).Result()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(val).Should(ContainSubstring("name=hi"))
|
|
|
|
})
|
|
|
|
|
2023-05-16 17:02:22 +03:00
|
|
|
It("should client PROTO 2", func() {
|
|
|
|
opt := redisOptions()
|
|
|
|
opt.Protocol = 2
|
|
|
|
db := redis.NewClient(opt)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
Expect(db.Close()).NotTo(HaveOccurred())
|
|
|
|
}()
|
|
|
|
|
|
|
|
val, err := db.Do(ctx, "HELLO").Result()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(val).Should(ContainElements("proto", int64(2)))
|
|
|
|
})
|
|
|
|
|
|
|
|
It("should client PROTO 3", func() {
|
|
|
|
opt := redisOptions()
|
|
|
|
db := redis.NewClient(opt)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
Expect(db.Close()).NotTo(HaveOccurred())
|
|
|
|
}()
|
|
|
|
|
|
|
|
val, err := db.Do(ctx, "HELLO").Result()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
|
|
|
|
})
|
|
|
|
|
2016-04-12 19:41:56 +03:00
|
|
|
It("processes custom commands", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
cmd := redis.NewCmd(ctx, "PING")
|
|
|
|
_ = client.Process(ctx, cmd)
|
2016-04-12 19:41:56 +03:00
|
|
|
|
|
|
|
// Flush buffers.
|
2020-03-11 17:26:42 +03:00
|
|
|
Expect(client.Echo(ctx, "hello").Err()).NotTo(HaveOccurred())
|
2016-04-12 19:41:56 +03:00
|
|
|
|
2016-03-16 17:57:24 +03:00
|
|
|
Expect(cmd.Err()).NotTo(HaveOccurred())
|
2016-04-12 19:41:56 +03:00
|
|
|
Expect(cmd.Val()).To(Equal("PONG"))
|
2015-07-13 13:45:37 +03:00
|
|
|
})
|
|
|
|
|
2015-05-10 15:33:04 +03:00
|
|
|
It("should retry command on network error", func() {
|
|
|
|
Expect(client.Close()).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
client = redis.NewClient(&redis.Options{
|
|
|
|
Addr: redisAddr,
|
|
|
|
MaxRetries: 1,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Put bad connection in the pool.
|
2020-03-11 17:26:42 +03:00
|
|
|
cn, err := client.Pool().Get(ctx)
|
2015-05-10 15:33:04 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
2015-09-06 13:50:16 +03:00
|
|
|
|
2017-02-08 12:24:09 +03:00
|
|
|
cn.SetNetConn(&badConn{})
|
2020-08-15 15:36:02 +03:00
|
|
|
client.Pool().Put(ctx, cn)
|
2015-05-10 15:33:04 +03:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err = client.Ping(ctx).Err()
|
2015-05-10 15:33:04 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
})
|
2016-03-02 14:26:05 +03:00
|
|
|
|
2017-05-25 08:08:44 +03:00
|
|
|
It("should retry with backoff", func() {
|
|
|
|
clientNoRetry := redis.NewClient(&redis.Options{
|
2018-02-27 17:09:55 +03:00
|
|
|
Addr: ":1234",
|
2020-09-11 11:24:38 +03:00
|
|
|
MaxRetries: -1,
|
2017-05-25 08:08:44 +03:00
|
|
|
})
|
|
|
|
defer clientNoRetry.Close()
|
|
|
|
|
|
|
|
clientRetry := redis.NewClient(&redis.Options{
|
2018-02-27 17:09:55 +03:00
|
|
|
Addr: ":1234",
|
2017-05-25 08:08:44 +03:00
|
|
|
MaxRetries: 5,
|
|
|
|
MaxRetryBackoff: 128 * time.Millisecond,
|
|
|
|
})
|
|
|
|
defer clientRetry.Close()
|
|
|
|
|
|
|
|
startNoRetry := time.Now()
|
2020-03-11 17:26:42 +03:00
|
|
|
err := clientNoRetry.Ping(ctx).Err()
|
2017-05-25 08:08:44 +03:00
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
elapseNoRetry := time.Since(startNoRetry)
|
|
|
|
|
|
|
|
startRetry := time.Now()
|
2020-03-11 17:26:42 +03:00
|
|
|
err = clientRetry.Ping(ctx).Err()
|
2017-05-25 08:08:44 +03:00
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
elapseRetry := time.Since(startRetry)
|
|
|
|
|
2018-02-27 17:09:55 +03:00
|
|
|
Expect(elapseRetry).To(BeNumerically(">", elapseNoRetry, 10*time.Millisecond))
|
2017-05-25 08:08:44 +03:00
|
|
|
})
|
|
|
|
|
2016-03-16 17:57:24 +03:00
|
|
|
It("should update conn.UsedAt on read/write", func() {
|
2019-07-04 11:18:06 +03:00
|
|
|
cn, err := client.Pool().Get(context.Background())
|
2016-03-02 14:26:05 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
2016-03-04 11:03:50 +03:00
|
|
|
Expect(cn.UsedAt).NotTo(BeZero())
|
2021-03-12 14:06:11 +03:00
|
|
|
|
|
|
|
// set cn.SetUsedAt(time) or time.Sleep(>1*time.Second)
|
|
|
|
// simulate the last time Conn was used
|
|
|
|
// time.Sleep() is not the standard sleep time
|
|
|
|
// link: https://go-review.googlesource.com/c/go/+/232298
|
|
|
|
cn.SetUsedAt(time.Now().Add(-1 * time.Second))
|
2017-02-08 12:24:09 +03:00
|
|
|
createdAt := cn.UsedAt()
|
2016-03-04 11:03:50 +03:00
|
|
|
|
2020-08-15 15:36:02 +03:00
|
|
|
client.Pool().Put(ctx, cn)
|
2017-02-08 12:24:09 +03:00
|
|
|
Expect(cn.UsedAt().Equal(createdAt)).To(BeTrue())
|
2016-03-02 14:26:05 +03:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err = client.Ping(ctx).Err()
|
2016-03-02 14:26:05 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
2019-07-04 11:18:06 +03:00
|
|
|
cn, err = client.Pool().Get(context.Background())
|
2016-03-17 19:00:47 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
2016-03-02 14:26:05 +03:00
|
|
|
Expect(cn).NotTo(BeNil())
|
2017-02-08 12:24:09 +03:00
|
|
|
Expect(cn.UsedAt().After(createdAt)).To(BeTrue())
|
2016-03-02 14:26:05 +03:00
|
|
|
})
|
2016-03-16 17:57:24 +03:00
|
|
|
|
2016-12-03 18:30:13 +03:00
|
|
|
It("should process command with special chars", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
set := client.Set(ctx, "key", "hello1\r\nhello2\r\n", 0)
|
2016-03-16 17:57:24 +03:00
|
|
|
Expect(set.Err()).NotTo(HaveOccurred())
|
|
|
|
Expect(set.Val()).To(Equal("OK"))
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
get := client.Get(ctx, "key")
|
2016-03-16 17:57:24 +03:00
|
|
|
Expect(get.Err()).NotTo(HaveOccurred())
|
|
|
|
Expect(get.Val()).To(Equal("hello1\r\nhello2\r\n"))
|
|
|
|
})
|
|
|
|
|
|
|
|
It("should handle big vals", func() {
|
2016-11-09 11:04:37 +03:00
|
|
|
bigVal := bytes.Repeat([]byte{'*'}, 2e6)
|
2016-03-16 17:57:24 +03:00
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.Set(ctx, "key", bigVal, 0).Err()
|
2016-03-16 17:57:24 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
// Reconnect to get new connection.
|
2016-12-03 18:30:13 +03:00
|
|
|
Expect(client.Close()).NotTo(HaveOccurred())
|
2016-03-16 17:57:24 +03:00
|
|
|
client = redis.NewClient(redisOptions())
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
got, err := client.Get(ctx, "key").Bytes()
|
2016-03-16 17:57:24 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(got).To(Equal(bigVal))
|
|
|
|
})
|
2020-10-13 09:33:12 +03:00
|
|
|
|
|
|
|
It("should set and scan time", func() {
|
|
|
|
tm := time.Now()
|
2021-04-28 09:39:03 +03:00
|
|
|
err := client.Set(ctx, "now", tm, 0).Err()
|
2020-10-13 09:33:12 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
var tm2 time.Time
|
2021-04-28 09:39:03 +03:00
|
|
|
err = client.Get(ctx, "now").Scan(&tm2)
|
2020-10-13 09:33:12 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
Expect(tm2).To(BeTemporally("==", tm))
|
|
|
|
})
|
2020-10-17 15:21:09 +03:00
|
|
|
|
2021-10-23 01:10:30 +03:00
|
|
|
It("should set and scan durations", func() {
|
|
|
|
duration := 10 * time.Minute
|
|
|
|
err := client.Set(ctx, "duration", duration, 0).Err()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
var duration2 time.Duration
|
|
|
|
err = client.Get(ctx, "duration").Scan(&duration2)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
Expect(duration2).To(Equal(duration))
|
|
|
|
})
|
|
|
|
|
2020-10-17 15:21:09 +03:00
|
|
|
It("should Conn", func() {
|
2022-07-13 08:49:28 +03:00
|
|
|
err := client.Conn().Get(ctx, "this-key-does-not-exist").Err()
|
2020-10-17 15:21:09 +03:00
|
|
|
Expect(err).To(Equal(redis.Nil))
|
|
|
|
})
|
2022-03-28 04:16:12 +03:00
|
|
|
|
|
|
|
It("should set and scan net.IP", func() {
|
|
|
|
ip := net.ParseIP("192.168.1.1")
|
|
|
|
err := client.Set(ctx, "ip", ip, 0).Err()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
var ip2 net.IP
|
|
|
|
err = client.Get(ctx, "ip").Scan(&ip2)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
Expect(ip2).To(Equal(ip))
|
|
|
|
})
|
2016-12-03 18:30:13 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
var _ = Describe("Client timeout", func() {
|
2017-04-17 15:43:58 +03:00
|
|
|
var opt *redis.Options
|
2016-12-03 18:30:13 +03:00
|
|
|
var client *redis.Client
|
2016-03-16 17:57:24 +03:00
|
|
|
|
2016-12-03 18:30:13 +03:00
|
|
|
AfterEach(func() {
|
|
|
|
Expect(client.Close()).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
testTimeout := func() {
|
|
|
|
It("Ping timeouts", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.Ping(ctx).Err()
|
2016-12-03 18:30:13 +03:00
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("Pipeline timeouts", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
_, err := client.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
|
|
|
pipe.Ping(ctx)
|
2016-12-03 18:30:13 +03:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("Subscribe timeouts", func() {
|
2017-04-17 15:43:58 +03:00
|
|
|
if opt.WriteTimeout == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
pubsub := client.Subscribe(ctx)
|
2017-04-17 15:43:58 +03:00
|
|
|
defer pubsub.Close()
|
|
|
|
|
2020-03-11 17:26:42 +03:00
|
|
|
err := pubsub.Subscribe(ctx, "_")
|
2016-12-03 18:30:13 +03:00
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("Tx timeouts", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.Watch(ctx, func(tx *redis.Tx) error {
|
|
|
|
return tx.Ping(ctx).Err()
|
2016-12-03 18:30:13 +03:00
|
|
|
})
|
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("Tx Pipeline timeouts", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
err := client.Watch(ctx, func(tx *redis.Tx) error {
|
|
|
|
_, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
|
|
|
|
pipe.Ping(ctx)
|
2016-12-03 18:30:13 +03:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
Context("read timeout", func() {
|
|
|
|
BeforeEach(func() {
|
2017-04-17 15:43:58 +03:00
|
|
|
opt = redisOptions()
|
2016-12-03 18:30:13 +03:00
|
|
|
opt.ReadTimeout = time.Nanosecond
|
|
|
|
opt.WriteTimeout = -1
|
|
|
|
client = redis.NewClient(opt)
|
|
|
|
})
|
|
|
|
|
|
|
|
testTimeout()
|
|
|
|
})
|
|
|
|
|
|
|
|
Context("write timeout", func() {
|
|
|
|
BeforeEach(func() {
|
2017-04-17 15:43:58 +03:00
|
|
|
opt = redisOptions()
|
2016-12-03 18:30:13 +03:00
|
|
|
opt.ReadTimeout = -1
|
|
|
|
opt.WriteTimeout = time.Nanosecond
|
|
|
|
client = redis.NewClient(opt)
|
|
|
|
})
|
|
|
|
|
|
|
|
testTimeout()
|
|
|
|
})
|
2015-01-15 18:51:22 +03:00
|
|
|
})
|
2017-05-25 14:16:39 +03:00
|
|
|
|
|
|
|
var _ = Describe("Client OnConnect", func() {
|
|
|
|
var client *redis.Client
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
opt := redisOptions()
|
2017-05-30 15:45:36 +03:00
|
|
|
opt.DB = 0
|
2020-06-10 10:36:22 +03:00
|
|
|
opt.OnConnect = func(ctx context.Context, cn *redis.Conn) error {
|
2020-03-11 17:26:42 +03:00
|
|
|
return cn.ClientSetName(ctx, "on_connect").Err()
|
2017-05-25 14:16:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
client = redis.NewClient(opt)
|
|
|
|
})
|
|
|
|
|
|
|
|
AfterEach(func() {
|
|
|
|
Expect(client.Close()).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("calls OnConnect", func() {
|
2020-03-11 17:26:42 +03:00
|
|
|
name, err := client.ClientGetName(ctx).Result()
|
2017-05-25 14:16:39 +03:00
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(name).To(Equal("on_connect"))
|
|
|
|
})
|
|
|
|
})
|
2020-09-14 21:27:26 +03:00
|
|
|
|
2024-04-13 20:15:23 +03:00
|
|
|
var _ = Describe("Client context cancellation", func() {
|
2020-09-14 21:27:26 +03:00
|
|
|
var opt *redis.Options
|
|
|
|
var client *redis.Client
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
opt = redisOptions()
|
|
|
|
opt.ReadTimeout = -1
|
|
|
|
opt.WriteTimeout = -1
|
|
|
|
client = redis.NewClient(opt)
|
|
|
|
})
|
|
|
|
|
|
|
|
AfterEach(func() {
|
|
|
|
Expect(client.Close()).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
2024-04-13 20:15:23 +03:00
|
|
|
It("Blocking operation cancellation", func() {
|
2020-09-14 21:27:26 +03:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
err := client.BLPop(ctx, 1*time.Second, "test").Err()
|
|
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
Expect(err).To(BeIdenticalTo(context.Canceled))
|
|
|
|
})
|
|
|
|
})
|
2022-11-22 15:28:39 +03:00
|
|
|
|
|
|
|
var _ = Describe("Conn", func() {
|
|
|
|
var client *redis.Client
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
client = redis.NewClient(redisOptions())
|
|
|
|
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
AfterEach(func() {
|
|
|
|
err := client.Close()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
2024-01-01 23:19:22 +03:00
|
|
|
It("TxPipeline", Label("NonRedisEnterprise"), func() {
|
2022-11-22 15:28:39 +03:00
|
|
|
tx := client.Conn().TxPipeline()
|
|
|
|
tx.SwapDB(ctx, 0, 2)
|
|
|
|
tx.SwapDB(ctx, 1, 0)
|
|
|
|
_, err := tx.Exec(ctx)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
})
|
2023-01-20 18:56:20 +03:00
|
|
|
|
|
|
|
var _ = Describe("Hook", func() {
|
|
|
|
var client *redis.Client
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
client = redis.NewClient(redisOptions())
|
|
|
|
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
AfterEach(func() {
|
|
|
|
err := client.Close()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("fifo", func() {
|
|
|
|
var res []string
|
|
|
|
client.AddHook(&hook{
|
|
|
|
processHook: func(hook redis.ProcessHook) redis.ProcessHook {
|
|
|
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
|
|
|
res = append(res, "hook-1-process-start")
|
|
|
|
err := hook(ctx, cmd)
|
|
|
|
res = append(res, "hook-1-process-end")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
client.AddHook(&hook{
|
|
|
|
processHook: func(hook redis.ProcessHook) redis.ProcessHook {
|
|
|
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
|
|
|
res = append(res, "hook-2-process-start")
|
|
|
|
err := hook(ctx, cmd)
|
|
|
|
res = append(res, "hook-2-process-end")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
err := client.Ping(ctx).Err()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
Expect(res).To(Equal([]string{
|
|
|
|
"hook-1-process-start",
|
|
|
|
"hook-2-process-start",
|
|
|
|
"hook-2-process-end",
|
|
|
|
"hook-1-process-end",
|
|
|
|
}))
|
|
|
|
})
|
2023-10-30 19:08:18 +03:00
|
|
|
|
|
|
|
It("wrapped error in a hook", func() {
|
|
|
|
client.AddHook(&hook{
|
|
|
|
processHook: func(hook redis.ProcessHook) redis.ProcessHook {
|
|
|
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
|
|
|
if err := hook(ctx, cmd); err != nil {
|
|
|
|
return fmt.Errorf("wrapped error: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
client.ScriptFlush(ctx)
|
|
|
|
|
|
|
|
script := redis.NewScript(`return 'Script and hook'`)
|
|
|
|
|
|
|
|
cmd := script.Run(ctx, client, nil)
|
|
|
|
Expect(cmd.Err()).NotTo(HaveOccurred())
|
|
|
|
Expect(cmd.Val()).To(Equal("Script and hook"))
|
|
|
|
})
|
2023-01-20 18:56:20 +03:00
|
|
|
})
|
2023-12-10 13:04:13 +03:00
|
|
|
|
|
|
|
var _ = Describe("Hook with MinIdleConns", func() {
|
|
|
|
var client *redis.Client
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
options := redisOptions()
|
|
|
|
options.MinIdleConns = 1
|
|
|
|
client = redis.NewClient(options)
|
|
|
|
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
AfterEach(func() {
|
|
|
|
err := client.Close()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("fifo", func() {
|
|
|
|
var res []string
|
|
|
|
client.AddHook(&hook{
|
|
|
|
processHook: func(hook redis.ProcessHook) redis.ProcessHook {
|
|
|
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
|
|
|
res = append(res, "hook-1-process-start")
|
|
|
|
err := hook(ctx, cmd)
|
|
|
|
res = append(res, "hook-1-process-end")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
client.AddHook(&hook{
|
|
|
|
processHook: func(hook redis.ProcessHook) redis.ProcessHook {
|
|
|
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
|
|
|
res = append(res, "hook-2-process-start")
|
|
|
|
err := hook(ctx, cmd)
|
|
|
|
res = append(res, "hook-2-process-end")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
err := client.Ping(ctx).Err()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
Expect(res).To(Equal([]string{
|
|
|
|
"hook-1-process-start",
|
|
|
|
"hook-2-process-start",
|
|
|
|
"hook-2-process-end",
|
|
|
|
"hook-1-process-end",
|
|
|
|
}))
|
|
|
|
})
|
|
|
|
})
|
2024-11-20 14:38:06 +03:00
|
|
|
|
|
|
|
var _ = Describe("Dialer connection timeouts", func() {
|
|
|
|
var client *redis.Client
|
|
|
|
|
|
|
|
const dialSimulatedDelay = 1 * time.Second
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
options := redisOptions()
|
|
|
|
options.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
// Simulated slow dialer.
|
|
|
|
// Note that the following sleep is deliberately not context-aware.
|
|
|
|
time.Sleep(dialSimulatedDelay)
|
|
|
|
return net.Dial("tcp", options.Addr)
|
|
|
|
}
|
|
|
|
options.MinIdleConns = 1
|
|
|
|
client = redis.NewClient(options)
|
|
|
|
})
|
|
|
|
|
|
|
|
AfterEach(func() {
|
|
|
|
err := client.Close()
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("does not contend on connection dial for concurrent commands", func() {
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
const concurrency = 10
|
|
|
|
|
|
|
|
durations := make(chan time.Duration, concurrency)
|
|
|
|
errs := make(chan error, concurrency)
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
wg.Add(concurrency)
|
|
|
|
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
err := client.Ping(ctx).Err()
|
|
|
|
durations <- time.Since(start)
|
|
|
|
errs <- err
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
close(durations)
|
|
|
|
close(errs)
|
|
|
|
|
|
|
|
// All commands should eventually succeed, after acquiring a connection.
|
|
|
|
for err := range errs {
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Each individual command should complete within the simulated dial duration bound.
|
|
|
|
for duration := range durations {
|
|
|
|
Expect(duration).To(BeNumerically("<", 2*dialSimulatedDelay))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Due to concurrent execution, the entire test suite should also complete within
|
|
|
|
// the same dial duration bound applied for individual commands.
|
|
|
|
Expect(time.Since(start)).To(BeNumerically("<", 2*dialSimulatedDelay))
|
|
|
|
})
|
|
|
|
})
|