redis_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package redis_test
  2. import (
  3. "bytes"
  4. "net"
  5. "time"
  6. "github.com/go-redis/redis"
  7. . "github.com/onsi/ginkgo"
  8. . "github.com/onsi/gomega"
  9. )
  10. var _ = Describe("Client", func() {
  11. var client *redis.Client
  12. BeforeEach(func() {
  13. client = redis.NewClient(redisOptions())
  14. Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
  15. })
  16. AfterEach(func() {
  17. client.Close()
  18. })
  19. It("should Stringer", func() {
  20. Expect(client.String()).To(Equal("Redis<:6380 db:15>"))
  21. })
  22. It("should ping", func() {
  23. val, err := client.Ping().Result()
  24. Expect(err).NotTo(HaveOccurred())
  25. Expect(val).To(Equal("PONG"))
  26. })
  27. It("should return pool stats", func() {
  28. Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{}))
  29. })
  30. It("should support custom dialers", func() {
  31. custom := redis.NewClient(&redis.Options{
  32. Addr: ":1234",
  33. Dialer: func() (net.Conn, error) {
  34. return net.Dial("tcp", redisAddr)
  35. },
  36. })
  37. val, err := custom.Ping().Result()
  38. Expect(err).NotTo(HaveOccurred())
  39. Expect(val).To(Equal("PONG"))
  40. Expect(custom.Close()).NotTo(HaveOccurred())
  41. })
  42. It("should close", func() {
  43. Expect(client.Close()).NotTo(HaveOccurred())
  44. err := client.Ping().Err()
  45. Expect(err).To(MatchError("redis: client is closed"))
  46. })
  47. It("should close pubsub without closing the client", func() {
  48. pubsub := client.Subscribe()
  49. Expect(pubsub.Close()).NotTo(HaveOccurred())
  50. _, err := pubsub.Receive()
  51. Expect(err).To(MatchError("redis: client is closed"))
  52. Expect(client.Ping().Err()).NotTo(HaveOccurred())
  53. })
  54. It("should close Tx without closing the client", func() {
  55. err := client.Watch(func(tx *redis.Tx) error {
  56. _, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
  57. pipe.Ping()
  58. return nil
  59. })
  60. return err
  61. })
  62. Expect(err).NotTo(HaveOccurred())
  63. Expect(client.Ping().Err()).NotTo(HaveOccurred())
  64. })
  65. It("should close pipeline without closing the client", func() {
  66. pipeline := client.Pipeline()
  67. Expect(pipeline.Close()).NotTo(HaveOccurred())
  68. pipeline.Ping()
  69. _, err := pipeline.Exec()
  70. Expect(err).To(MatchError("redis: client is closed"))
  71. Expect(client.Ping().Err()).NotTo(HaveOccurred())
  72. })
  73. It("should close pubsub when client is closed", func() {
  74. pubsub := client.Subscribe()
  75. Expect(client.Close()).NotTo(HaveOccurred())
  76. _, err := pubsub.Receive()
  77. Expect(err).To(MatchError("redis: client is closed"))
  78. Expect(pubsub.Close()).NotTo(HaveOccurred())
  79. })
  80. It("should close pipeline when client is closed", func() {
  81. pipeline := client.Pipeline()
  82. Expect(client.Close()).NotTo(HaveOccurred())
  83. Expect(pipeline.Close()).NotTo(HaveOccurred())
  84. })
  85. It("should select DB", func() {
  86. db2 := redis.NewClient(&redis.Options{
  87. Addr: redisAddr,
  88. DB: 2,
  89. })
  90. Expect(db2.FlushDB().Err()).NotTo(HaveOccurred())
  91. Expect(db2.Get("db").Err()).To(Equal(redis.Nil))
  92. Expect(db2.Set("db", 2, 0).Err()).NotTo(HaveOccurred())
  93. n, err := db2.Get("db").Int64()
  94. Expect(err).NotTo(HaveOccurred())
  95. Expect(n).To(Equal(int64(2)))
  96. Expect(client.Get("db").Err()).To(Equal(redis.Nil))
  97. Expect(db2.FlushDB().Err()).NotTo(HaveOccurred())
  98. Expect(db2.Close()).NotTo(HaveOccurred())
  99. })
  100. It("processes custom commands", func() {
  101. cmd := redis.NewCmd("PING")
  102. client.Process(cmd)
  103. // Flush buffers.
  104. Expect(client.Echo("hello").Err()).NotTo(HaveOccurred())
  105. Expect(cmd.Err()).NotTo(HaveOccurred())
  106. Expect(cmd.Val()).To(Equal("PONG"))
  107. })
  108. It("should retry command on network error", func() {
  109. Expect(client.Close()).NotTo(HaveOccurred())
  110. client = redis.NewClient(&redis.Options{
  111. Addr: redisAddr,
  112. MaxRetries: 1,
  113. })
  114. // Put bad connection in the pool.
  115. cn, err := client.Pool().Get()
  116. Expect(err).NotTo(HaveOccurred())
  117. cn.SetNetConn(&badConn{})
  118. client.Pool().Put(cn)
  119. err = client.Ping().Err()
  120. Expect(err).NotTo(HaveOccurred())
  121. })
  122. It("should retry with backoff", func() {
  123. clientNoRetry := redis.NewClient(&redis.Options{
  124. Addr: ":1234",
  125. MaxRetries: 0,
  126. })
  127. defer clientNoRetry.Close()
  128. clientRetry := redis.NewClient(&redis.Options{
  129. Addr: ":1234",
  130. MaxRetries: 5,
  131. MaxRetryBackoff: 128 * time.Millisecond,
  132. })
  133. defer clientRetry.Close()
  134. startNoRetry := time.Now()
  135. err := clientNoRetry.Ping().Err()
  136. Expect(err).To(HaveOccurred())
  137. elapseNoRetry := time.Since(startNoRetry)
  138. startRetry := time.Now()
  139. err = clientRetry.Ping().Err()
  140. Expect(err).To(HaveOccurred())
  141. elapseRetry := time.Since(startRetry)
  142. Expect(elapseRetry).To(BeNumerically(">", elapseNoRetry, 10*time.Millisecond))
  143. })
  144. It("should update conn.UsedAt on read/write", func() {
  145. cn, err := client.Pool().Get()
  146. Expect(err).NotTo(HaveOccurred())
  147. Expect(cn.UsedAt).NotTo(BeZero())
  148. createdAt := cn.UsedAt()
  149. client.Pool().Put(cn)
  150. Expect(cn.UsedAt().Equal(createdAt)).To(BeTrue())
  151. err = client.Ping().Err()
  152. Expect(err).NotTo(HaveOccurred())
  153. cn, err = client.Pool().Get()
  154. Expect(err).NotTo(HaveOccurred())
  155. Expect(cn).NotTo(BeNil())
  156. Expect(cn.UsedAt().After(createdAt)).To(BeTrue())
  157. })
  158. It("should process command with special chars", func() {
  159. set := client.Set("key", "hello1\r\nhello2\r\n", 0)
  160. Expect(set.Err()).NotTo(HaveOccurred())
  161. Expect(set.Val()).To(Equal("OK"))
  162. get := client.Get("key")
  163. Expect(get.Err()).NotTo(HaveOccurred())
  164. Expect(get.Val()).To(Equal("hello1\r\nhello2\r\n"))
  165. })
  166. It("should handle big vals", func() {
  167. bigVal := bytes.Repeat([]byte{'*'}, 2e6)
  168. err := client.Set("key", bigVal, 0).Err()
  169. Expect(err).NotTo(HaveOccurred())
  170. // Reconnect to get new connection.
  171. Expect(client.Close()).NotTo(HaveOccurred())
  172. client = redis.NewClient(redisOptions())
  173. got, err := client.Get("key").Bytes()
  174. Expect(err).NotTo(HaveOccurred())
  175. Expect(got).To(Equal(bigVal))
  176. })
  177. It("should call WrapProcess", func() {
  178. var fnCalled bool
  179. client.WrapProcess(func(old func(redis.Cmder) error) func(redis.Cmder) error {
  180. return func(cmd redis.Cmder) error {
  181. fnCalled = true
  182. return old(cmd)
  183. }
  184. })
  185. Expect(client.Ping().Err()).NotTo(HaveOccurred())
  186. Expect(fnCalled).To(BeTrue())
  187. })
  188. It("should call WrapProcess after WithContext", func() {
  189. var fn1Called, fn2Called bool
  190. client.WrapProcess(func(old func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
  191. return func(cmd redis.Cmder) error {
  192. fn1Called = true
  193. return old(cmd)
  194. }
  195. })
  196. client2 := client.WithContext(client.Context())
  197. client2.WrapProcess(func(old func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
  198. return func(cmd redis.Cmder) error {
  199. fn2Called = true
  200. return old(cmd)
  201. }
  202. })
  203. Expect(client2.Ping().Err()).NotTo(HaveOccurred())
  204. Expect(fn2Called).To(BeTrue())
  205. Expect(fn1Called).To(BeTrue())
  206. })
  207. })
  208. var _ = Describe("Client timeout", func() {
  209. var opt *redis.Options
  210. var client *redis.Client
  211. AfterEach(func() {
  212. Expect(client.Close()).NotTo(HaveOccurred())
  213. })
  214. testTimeout := func() {
  215. It("Ping timeouts", func() {
  216. err := client.Ping().Err()
  217. Expect(err).To(HaveOccurred())
  218. Expect(err.(net.Error).Timeout()).To(BeTrue())
  219. })
  220. It("Pipeline timeouts", func() {
  221. _, err := client.Pipelined(func(pipe redis.Pipeliner) error {
  222. pipe.Ping()
  223. return nil
  224. })
  225. Expect(err).To(HaveOccurred())
  226. Expect(err.(net.Error).Timeout()).To(BeTrue())
  227. })
  228. It("Subscribe timeouts", func() {
  229. if opt.WriteTimeout == 0 {
  230. return
  231. }
  232. pubsub := client.Subscribe()
  233. defer pubsub.Close()
  234. err := pubsub.Subscribe("_")
  235. Expect(err).To(HaveOccurred())
  236. Expect(err.(net.Error).Timeout()).To(BeTrue())
  237. })
  238. It("Tx timeouts", func() {
  239. err := client.Watch(func(tx *redis.Tx) error {
  240. return tx.Ping().Err()
  241. })
  242. Expect(err).To(HaveOccurred())
  243. Expect(err.(net.Error).Timeout()).To(BeTrue())
  244. })
  245. It("Tx Pipeline timeouts", func() {
  246. err := client.Watch(func(tx *redis.Tx) error {
  247. _, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
  248. pipe.Ping()
  249. return nil
  250. })
  251. return err
  252. })
  253. Expect(err).To(HaveOccurred())
  254. Expect(err.(net.Error).Timeout()).To(BeTrue())
  255. })
  256. }
  257. Context("read timeout", func() {
  258. BeforeEach(func() {
  259. opt = redisOptions()
  260. opt.ReadTimeout = time.Nanosecond
  261. opt.WriteTimeout = -1
  262. client = redis.NewClient(opt)
  263. })
  264. testTimeout()
  265. })
  266. Context("write timeout", func() {
  267. BeforeEach(func() {
  268. opt = redisOptions()
  269. opt.ReadTimeout = -1
  270. opt.WriteTimeout = time.Nanosecond
  271. client = redis.NewClient(opt)
  272. })
  273. testTimeout()
  274. })
  275. })
  276. var _ = Describe("Client OnConnect", func() {
  277. var client *redis.Client
  278. BeforeEach(func() {
  279. opt := redisOptions()
  280. opt.DB = 0
  281. opt.OnConnect = func(cn *redis.Conn) error {
  282. return cn.ClientSetName("on_connect").Err()
  283. }
  284. client = redis.NewClient(opt)
  285. })
  286. AfterEach(func() {
  287. Expect(client.Close()).NotTo(HaveOccurred())
  288. })
  289. It("calls OnConnect", func() {
  290. name, err := client.ClientGetName().Result()
  291. Expect(err).NotTo(HaveOccurred())
  292. Expect(name).To(Equal("on_connect"))
  293. })
  294. })