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  }