github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/cache/cache_test.go (about) 1 package cache_test 2 3 import ( 4 "context" 5 "errors" 6 "io" 7 "sync" 8 "sync/atomic" 9 "testing" 10 "time" 11 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 "github.com/redis/go-redis/v9" 15 16 "github.com/unionj-cloud/go-doudou/v2/toolkit/cache" 17 ) 18 19 func TestGinkgo(t *testing.T) { 20 RegisterFailHandler(Fail) 21 RunSpecs(t, "cache") 22 } 23 24 func perform(n int, cbs ...func(int)) { 25 var wg sync.WaitGroup 26 for _, cb := range cbs { 27 for i := 0; i < n; i++ { 28 wg.Add(1) 29 go func(cb func(int), i int) { 30 defer wg.Done() 31 defer GinkgoRecover() 32 33 cb(i) 34 }(cb, i) 35 } 36 } 37 wg.Wait() 38 } 39 40 var _ = Describe("Cache", func() { 41 ctx := context.TODO() 42 43 const key = "mykey" 44 var obj *Object 45 46 var rdb *redis.Ring 47 var mycache *cache.Cache 48 49 testCache := func() { 50 It("Gets and Sets nil", func() { 51 err := mycache.Set(&cache.Item{ 52 Key: key, 53 TTL: time.Hour, 54 }) 55 Expect(err).NotTo(HaveOccurred()) 56 57 err = mycache.Get(ctx, key, nil) 58 Expect(err).NotTo(HaveOccurred()) 59 60 Expect(mycache.Exists(ctx, key)).To(BeTrue()) 61 }) 62 63 It("Deletes key", func() { 64 err := mycache.Set(&cache.Item{ 65 Ctx: ctx, 66 Key: key, 67 TTL: time.Hour, 68 }) 69 Expect(err).NotTo(HaveOccurred()) 70 71 Expect(mycache.Exists(ctx, key)).To(BeTrue()) 72 73 err = mycache.Delete(ctx, key) 74 Expect(err).NotTo(HaveOccurred()) 75 76 err = mycache.Get(ctx, key, nil) 77 Expect(err).To(Equal(cache.ErrCacheMiss)) 78 79 Expect(mycache.Exists(ctx, key)).To(BeFalse()) 80 }) 81 82 It("Gets and Sets data", func() { 83 err := mycache.Set(&cache.Item{ 84 Ctx: ctx, 85 Key: key, 86 Value: obj, 87 TTL: time.Hour, 88 }) 89 Expect(err).NotTo(HaveOccurred()) 90 91 wanted := new(Object) 92 err = mycache.Get(ctx, key, wanted) 93 Expect(err).NotTo(HaveOccurred()) 94 Expect(wanted).To(Equal(obj)) 95 96 Expect(mycache.Exists(ctx, key)).To(BeTrue()) 97 }) 98 99 It("Sets string as is", func() { 100 value := "str_value" 101 102 err := mycache.Set(&cache.Item{ 103 Ctx: ctx, 104 Key: key, 105 Value: value, 106 }) 107 Expect(err).NotTo(HaveOccurred()) 108 109 var dst string 110 err = mycache.Get(ctx, key, &dst) 111 Expect(err).NotTo(HaveOccurred()) 112 Expect(dst).To(Equal(value)) 113 }) 114 115 It("Sets bytes as is", func() { 116 value := []byte("str_value") 117 118 err := mycache.Set(&cache.Item{ 119 Ctx: ctx, 120 Key: key, 121 Value: value, 122 }) 123 Expect(err).NotTo(HaveOccurred()) 124 125 var dst []byte 126 err = mycache.Get(ctx, key, &dst) 127 Expect(err).NotTo(HaveOccurred()) 128 Expect(dst).To(Equal(value)) 129 }) 130 131 It("can be used with Incr", func() { 132 if rdb == nil { 133 return 134 } 135 136 value := "123" 137 138 err := mycache.Set(&cache.Item{ 139 Ctx: ctx, 140 Key: key, 141 Value: value, 142 }) 143 Expect(err).NotTo(HaveOccurred()) 144 145 n, err := rdb.Incr(ctx, key).Result() 146 Expect(err).NotTo(HaveOccurred()) 147 Expect(n).To(Equal(int64(124))) 148 }) 149 150 Describe("Once func", func() { 151 It("calls Func when cache fails", func() { 152 err := mycache.Set(&cache.Item{ 153 Ctx: ctx, 154 Key: key, 155 Value: int64(0), 156 }) 157 Expect(err).NotTo(HaveOccurred()) 158 159 var got bool 160 err = mycache.Get(ctx, key, &got) 161 Expect(err).To(MatchError("msgpack: invalid code=d3 decoding bool")) 162 163 err = mycache.Once(&cache.Item{ 164 Ctx: ctx, 165 Key: key, 166 Value: &got, 167 Do: func(*cache.Item) (interface{}, error) { 168 return true, nil 169 }, 170 }) 171 Expect(err).NotTo(HaveOccurred()) 172 Expect(got).To(BeTrue()) 173 174 got = false 175 err = mycache.Get(ctx, key, &got) 176 Expect(err).NotTo(HaveOccurred()) 177 Expect(got).To(BeTrue()) 178 }) 179 180 It("does not cache when Func fails", func() { 181 perform(100, func(int) { 182 var got bool 183 err := mycache.Once(&cache.Item{ 184 Ctx: ctx, 185 Key: key, 186 Value: &got, 187 Do: func(*cache.Item) (interface{}, error) { 188 return nil, io.EOF 189 }, 190 }) 191 Expect(err).To(Equal(io.EOF)) 192 Expect(got).To(BeFalse()) 193 }) 194 195 var got bool 196 err := mycache.Get(ctx, key, &got) 197 Expect(err).To(Equal(cache.ErrCacheMiss)) 198 199 err = mycache.Once(&cache.Item{ 200 Ctx: ctx, 201 Key: key, 202 Value: &got, 203 Do: func(*cache.Item) (interface{}, error) { 204 return true, nil 205 }, 206 }) 207 Expect(err).NotTo(HaveOccurred()) 208 Expect(got).To(BeTrue()) 209 }) 210 211 It("works with Value", func() { 212 var callCount int64 213 perform(100, func(int) { 214 got := new(Object) 215 err := mycache.Once(&cache.Item{ 216 Ctx: ctx, 217 Key: key, 218 Value: got, 219 Do: func(*cache.Item) (interface{}, error) { 220 atomic.AddInt64(&callCount, 1) 221 return obj, nil 222 }, 223 }) 224 Expect(err).NotTo(HaveOccurred()) 225 Expect(got).To(Equal(obj)) 226 }) 227 Expect(callCount).To(Equal(int64(1))) 228 }) 229 230 It("works with ptr and non-ptr", func() { 231 var callCount int64 232 perform(100, func(int) { 233 got := new(Object) 234 err := mycache.Once(&cache.Item{ 235 Ctx: ctx, 236 Key: key, 237 Value: got, 238 Do: func(*cache.Item) (interface{}, error) { 239 atomic.AddInt64(&callCount, 1) 240 return *obj, nil 241 }, 242 }) 243 Expect(err).NotTo(HaveOccurred()) 244 Expect(got).To(Equal(obj)) 245 }) 246 Expect(callCount).To(Equal(int64(1))) 247 }) 248 249 It("works with bool", func() { 250 var callCount int64 251 perform(100, func(int) { 252 var got bool 253 err := mycache.Once(&cache.Item{ 254 Ctx: ctx, 255 Key: key, 256 Value: &got, 257 Do: func(*cache.Item) (interface{}, error) { 258 atomic.AddInt64(&callCount, 1) 259 return true, nil 260 }, 261 }) 262 Expect(err).NotTo(HaveOccurred()) 263 Expect(got).To(BeTrue()) 264 }) 265 Expect(callCount).To(Equal(int64(1))) 266 }) 267 268 It("works without Value and nil result", func() { 269 var callCount int64 270 perform(100, func(int) { 271 err := mycache.Once(&cache.Item{ 272 Ctx: ctx, 273 Key: key, 274 Do: func(*cache.Item) (interface{}, error) { 275 atomic.AddInt64(&callCount, 1) 276 return nil, nil 277 }, 278 }) 279 Expect(err).NotTo(HaveOccurred()) 280 }) 281 Expect(callCount).To(Equal(int64(1))) 282 }) 283 284 It("works without Value and error result", func() { 285 var callCount int64 286 perform(100, func(int) { 287 err := mycache.Once(&cache.Item{ 288 Ctx: ctx, 289 Key: key, 290 Do: func(*cache.Item) (interface{}, error) { 291 time.Sleep(100 * time.Millisecond) 292 atomic.AddInt64(&callCount, 1) 293 return nil, errors.New("error stub") 294 }, 295 }) 296 Expect(err).To(MatchError("error stub")) 297 }) 298 Expect(callCount).To(Equal(int64(1))) 299 }) 300 301 It("does not cache error result", func() { 302 var callCount int64 303 do := func(sleep time.Duration) (int, error) { 304 var n int 305 err := mycache.Once(&cache.Item{ 306 Ctx: ctx, 307 Key: key, 308 Value: &n, 309 Do: func(*cache.Item) (interface{}, error) { 310 time.Sleep(sleep) 311 312 n := atomic.AddInt64(&callCount, 1) 313 if n == 1 { 314 return nil, errors.New("error stub") 315 } 316 return 42, nil 317 }, 318 }) 319 if err != nil { 320 return 0, err 321 } 322 return n, nil 323 } 324 325 perform(100, func(int) { 326 n, err := do(100 * time.Millisecond) 327 Expect(err).To(MatchError("error stub")) 328 Expect(n).To(Equal(0)) 329 }) 330 331 perform(100, func(int) { 332 n, err := do(0) 333 Expect(err).NotTo(HaveOccurred()) 334 Expect(n).To(Equal(42)) 335 }) 336 337 Expect(callCount).To(Equal(int64(2))) 338 }) 339 340 It("skips Set when TTL = -1", func() { 341 key := "skip-set" 342 343 var value string 344 err := mycache.Once(&cache.Item{ 345 Ctx: ctx, 346 Key: key, 347 Value: &value, 348 Do: func(item *cache.Item) (interface{}, error) { 349 item.TTL = -1 350 return "hello", nil 351 }, 352 }) 353 Expect(err).NotTo(HaveOccurred()) 354 Expect(value).To(Equal("hello")) 355 356 if rdb != nil { 357 exists, err := rdb.Exists(ctx, key).Result() 358 Expect(err).NotTo(HaveOccurred()) 359 Expect(exists).To(Equal(int64(0))) 360 } 361 }) 362 }) 363 } 364 365 BeforeEach(func() { 366 obj = &Object{ 367 Str: "mystring", 368 Num: 42, 369 } 370 }) 371 372 Context("without LocalCache", func() { 373 BeforeEach(func() { 374 rdb = newRing() 375 mycache = newCache(rdb) 376 }) 377 378 testCache() 379 }) 380 381 Context("with LocalCache", func() { 382 BeforeEach(func() { 383 rdb = newRing() 384 mycache = newCacheWithLocal(rdb) 385 }) 386 387 testCache() 388 }) 389 390 Context("with LocalCache and without Redis", func() { 391 BeforeEach(func() { 392 rdb = nil 393 mycache = cache.New(&cache.Options{ 394 LocalCache: cache.NewTinyLFU(1000, time.Minute), 395 }) 396 }) 397 398 testCache() 399 }) 400 }) 401 402 func newRing() *redis.Ring { 403 ctx := context.TODO() 404 ring := redis.NewRing(&redis.RingOptions{ 405 Addrs: map[string]string{ 406 "server1": ":6379", 407 }, 408 }) 409 _ = ring.ForEachShard(ctx, func(ctx context.Context, client *redis.Client) error { 410 return client.FlushDB(ctx).Err() 411 }) 412 return ring 413 } 414 415 func newCache(rdb *redis.Ring) *cache.Cache { 416 return cache.New(&cache.Options{ 417 Redis: rdb, 418 }) 419 } 420 421 func newCacheWithLocal(rdb *redis.Ring) *cache.Cache { 422 return cache.New(&cache.Options{ 423 Redis: rdb, 424 LocalCache: cache.NewTinyLFU(1000, time.Minute), 425 }) 426 }