github.com/lingyao2333/mo-zero@v1.4.1/core/stores/cache/cache_test.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"math"
     9  	"strconv"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/lingyao2333/mo-zero/core/errorx"
    14  	"github.com/lingyao2333/mo-zero/core/hash"
    15  	"github.com/lingyao2333/mo-zero/core/stores/redis"
    16  	"github.com/lingyao2333/mo-zero/core/stores/redis/redistest"
    17  	"github.com/lingyao2333/mo-zero/core/syncx"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  var _ Cache = (*mockedNode)(nil)
    22  
    23  type mockedNode struct {
    24  	vals        map[string][]byte
    25  	errNotFound error
    26  }
    27  
    28  func (mc *mockedNode) Del(keys ...string) error {
    29  	return mc.DelCtx(context.Background(), keys...)
    30  }
    31  
    32  func (mc *mockedNode) DelCtx(_ context.Context, keys ...string) error {
    33  	var be errorx.BatchError
    34  
    35  	for _, key := range keys {
    36  		if _, ok := mc.vals[key]; !ok {
    37  			be.Add(mc.errNotFound)
    38  		} else {
    39  			delete(mc.vals, key)
    40  		}
    41  	}
    42  
    43  	return be.Err()
    44  }
    45  
    46  func (mc *mockedNode) Get(key string, val interface{}) error {
    47  	return mc.GetCtx(context.Background(), key, val)
    48  }
    49  
    50  func (mc *mockedNode) GetCtx(ctx context.Context, key string, val interface{}) error {
    51  	bs, ok := mc.vals[key]
    52  	if ok {
    53  		return json.Unmarshal(bs, val)
    54  	}
    55  
    56  	return mc.errNotFound
    57  }
    58  
    59  func (mc *mockedNode) IsNotFound(err error) bool {
    60  	return errors.Is(err, mc.errNotFound)
    61  }
    62  
    63  func (mc *mockedNode) Set(key string, val interface{}) error {
    64  	return mc.SetCtx(context.Background(), key, val)
    65  }
    66  
    67  func (mc *mockedNode) SetCtx(ctx context.Context, key string, val interface{}) error {
    68  	data, err := json.Marshal(val)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	mc.vals[key] = data
    74  	return nil
    75  }
    76  
    77  func (mc *mockedNode) SetWithExpire(key string, val interface{}, expire time.Duration) error {
    78  	return mc.SetWithExpireCtx(context.Background(), key, val, expire)
    79  }
    80  
    81  func (mc *mockedNode) SetWithExpireCtx(ctx context.Context, key string, val interface{}, expire time.Duration) error {
    82  	return mc.Set(key, val)
    83  }
    84  
    85  func (mc *mockedNode) Take(val interface{}, key string, query func(val interface{}) error) error {
    86  	return mc.TakeCtx(context.Background(), val, key, query)
    87  }
    88  
    89  func (mc *mockedNode) TakeCtx(ctx context.Context, val interface{}, key string, query func(val interface{}) error) error {
    90  	if _, ok := mc.vals[key]; ok {
    91  		return mc.GetCtx(ctx, key, val)
    92  	}
    93  
    94  	if err := query(val); err != nil {
    95  		return err
    96  	}
    97  
    98  	return mc.SetCtx(ctx, key, val)
    99  }
   100  
   101  func (mc *mockedNode) TakeWithExpire(val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
   102  	return mc.TakeWithExpireCtx(context.Background(), val, key, query)
   103  }
   104  
   105  func (mc *mockedNode) TakeWithExpireCtx(ctx context.Context, val interface{}, key string, query func(val interface{}, expire time.Duration) error) error {
   106  	return mc.Take(val, key, func(val interface{}) error {
   107  		return query(val, 0)
   108  	})
   109  }
   110  
   111  func TestCache_SetDel(t *testing.T) {
   112  	const total = 1000
   113  	r1, clean1, err := redistest.CreateRedis()
   114  	assert.Nil(t, err)
   115  	defer clean1()
   116  	r2, clean2, err := redistest.CreateRedis()
   117  	assert.Nil(t, err)
   118  	defer clean2()
   119  	conf := ClusterConf{
   120  		{
   121  			RedisConf: redis.RedisConf{
   122  				Host: r1.Addr,
   123  				Type: redis.NodeType,
   124  			},
   125  			Weight: 100,
   126  		},
   127  		{
   128  			RedisConf: redis.RedisConf{
   129  				Host: r2.Addr,
   130  				Type: redis.NodeType,
   131  			},
   132  			Weight: 100,
   133  		},
   134  	}
   135  	c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
   136  	for i := 0; i < total; i++ {
   137  		if i%2 == 0 {
   138  			assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
   139  		} else {
   140  			assert.Nil(t, c.SetWithExpire(fmt.Sprintf("key/%d", i), i, 0))
   141  		}
   142  	}
   143  	for i := 0; i < total; i++ {
   144  		var val int
   145  		assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val))
   146  		assert.Equal(t, i, val)
   147  	}
   148  	assert.Nil(t, c.Del())
   149  	for i := 0; i < total; i++ {
   150  		assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i)))
   151  	}
   152  	for i := 0; i < total; i++ {
   153  		var val int
   154  		assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val)))
   155  		assert.Equal(t, 0, val)
   156  	}
   157  }
   158  
   159  func TestCache_OneNode(t *testing.T) {
   160  	const total = 1000
   161  	r, clean, err := redistest.CreateRedis()
   162  	assert.Nil(t, err)
   163  	defer clean()
   164  	conf := ClusterConf{
   165  		{
   166  			RedisConf: redis.RedisConf{
   167  				Host: r.Addr,
   168  				Type: redis.NodeType,
   169  			},
   170  			Weight: 100,
   171  		},
   172  	}
   173  	c := New(conf, syncx.NewSingleFlight(), NewStat("mock"), errPlaceholder)
   174  	for i := 0; i < total; i++ {
   175  		if i%2 == 0 {
   176  			assert.Nil(t, c.Set(fmt.Sprintf("key/%d", i), i))
   177  		} else {
   178  			assert.Nil(t, c.SetWithExpire(fmt.Sprintf("key/%d", i), i, 0))
   179  		}
   180  	}
   181  	for i := 0; i < total; i++ {
   182  		var val int
   183  		assert.Nil(t, c.Get(fmt.Sprintf("key/%d", i), &val))
   184  		assert.Equal(t, i, val)
   185  	}
   186  	assert.Nil(t, c.Del())
   187  	for i := 0; i < total; i++ {
   188  		assert.Nil(t, c.Del(fmt.Sprintf("key/%d", i)))
   189  	}
   190  	for i := 0; i < total; i++ {
   191  		var val int
   192  		assert.True(t, c.IsNotFound(c.Get(fmt.Sprintf("key/%d", i), &val)))
   193  		assert.Equal(t, 0, val)
   194  	}
   195  }
   196  
   197  func TestCache_Balance(t *testing.T) {
   198  	const (
   199  		numNodes = 100
   200  		total    = 10000
   201  	)
   202  	dispatcher := hash.NewConsistentHash()
   203  	maps := make([]map[string][]byte, numNodes)
   204  	for i := 0; i < numNodes; i++ {
   205  		maps[i] = map[string][]byte{
   206  			strconv.Itoa(i): []byte(strconv.Itoa(i)),
   207  		}
   208  	}
   209  	for i := 0; i < numNodes; i++ {
   210  		dispatcher.AddWithWeight(&mockedNode{
   211  			vals:        maps[i],
   212  			errNotFound: errPlaceholder,
   213  		}, 100)
   214  	}
   215  
   216  	c := cacheCluster{
   217  		dispatcher:  dispatcher,
   218  		errNotFound: errPlaceholder,
   219  	}
   220  	for i := 0; i < total; i++ {
   221  		assert.Nil(t, c.Set(strconv.Itoa(i), i))
   222  	}
   223  
   224  	counts := make(map[int]int)
   225  	for i, m := range maps {
   226  		counts[i] = len(m)
   227  	}
   228  	entropy := calcEntropy(counts, total)
   229  	assert.True(t, len(counts) > 1)
   230  	assert.True(t, entropy > .95, fmt.Sprintf("entropy should be greater than 0.95, but got %.2f", entropy))
   231  
   232  	for i := 0; i < total; i++ {
   233  		var val int
   234  		assert.Nil(t, c.Get(strconv.Itoa(i), &val))
   235  		assert.Equal(t, i, val)
   236  	}
   237  
   238  	for i := 0; i < total/10; i++ {
   239  		assert.Nil(t, c.Del(strconv.Itoa(i*10), strconv.Itoa(i*10+1), strconv.Itoa(i*10+2)))
   240  		assert.Nil(t, c.Del(strconv.Itoa(i*10+9)))
   241  	}
   242  
   243  	var count int
   244  	for i := 0; i < total/10; i++ {
   245  		var val int
   246  		if i%2 == 0 {
   247  			assert.Nil(t, c.Take(&val, strconv.Itoa(i*10), func(val interface{}) error {
   248  				*val.(*int) = i
   249  				count++
   250  				return nil
   251  			}))
   252  		} else {
   253  			assert.Nil(t, c.TakeWithExpire(&val, strconv.Itoa(i*10), func(val interface{}, expire time.Duration) error {
   254  				*val.(*int) = i
   255  				count++
   256  				return nil
   257  			}))
   258  		}
   259  		assert.Equal(t, i, val)
   260  	}
   261  	assert.Equal(t, total/10, count)
   262  }
   263  
   264  func TestCacheNoNode(t *testing.T) {
   265  	dispatcher := hash.NewConsistentHash()
   266  	c := cacheCluster{
   267  		dispatcher:  dispatcher,
   268  		errNotFound: errPlaceholder,
   269  	}
   270  	assert.NotNil(t, c.Del("foo"))
   271  	assert.NotNil(t, c.Del("foo", "bar", "any"))
   272  	assert.NotNil(t, c.Get("foo", nil))
   273  	assert.NotNil(t, c.Set("foo", nil))
   274  	assert.NotNil(t, c.SetWithExpire("foo", nil, time.Second))
   275  	assert.NotNil(t, c.Take(nil, "foo", func(val interface{}) error {
   276  		return nil
   277  	}))
   278  	assert.NotNil(t, c.TakeWithExpire(nil, "foo", func(val interface{}, duration time.Duration) error {
   279  		return nil
   280  	}))
   281  }
   282  
   283  func calcEntropy(m map[int]int, total int) float64 {
   284  	var entropy float64
   285  
   286  	for _, val := range m {
   287  		proba := float64(val) / float64(total)
   288  		entropy -= proba * math.Log2(proba)
   289  	}
   290  
   291  	return entropy / math.Log2(float64(len(m)))
   292  }