github.com/phuslu/lru@v1.0.16-0.20240421170520-46288a2fd47c/ttl_cache_test.go (about)

     1  package lru
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"runtime"
     8  	"strings"
     9  	"sync"
    10  	"sync/atomic"
    11  	"testing"
    12  	"time"
    13  	"unsafe"
    14  )
    15  
    16  func TestTTLCacheCompactness(t *testing.T) {
    17  	if runtime.GOARCH != "amd64" {
    18  		return
    19  	}
    20  	compact := isamd64
    21  	defer func() {
    22  		isamd64 = compact
    23  	}()
    24  
    25  	for _, b := range []bool{true, false} {
    26  		isamd64 = b
    27  		cache := NewTTLCache[string, []byte](32 * 1024)
    28  		if length := cache.Len(); length != 0 {
    29  			t.Fatalf("bad cache length: %v", length)
    30  		}
    31  	}
    32  }
    33  
    34  func TestTTLCacheDefaultkey(t *testing.T) {
    35  	cache := NewTTLCache[string, int](1)
    36  	var k string
    37  	var i int = 10
    38  
    39  	if prev, replaced := cache.Set(k, i, 0); replaced {
    40  		t.Fatalf("value %v should not be replaced", prev)
    41  	}
    42  
    43  	if v, ok := cache.Get(k); !ok || v != i {
    44  		t.Fatalf("bad returned value: %v != %v", v, i)
    45  	}
    46  }
    47  
    48  func TestTTLCacheGetSet(t *testing.T) {
    49  	cache := NewTTLCache[int, int](128)
    50  
    51  	if v, ok := cache.Get(5); ok {
    52  		t.Fatalf("bad returned value: %v", v)
    53  	}
    54  
    55  	if _, replaced := cache.Set(5, 10, 0); replaced {
    56  		t.Fatal("should not have replaced")
    57  	}
    58  
    59  	if v, ok := cache.Get(5); !ok || v != 10 {
    60  		t.Fatalf("bad returned value: %v != %v", v, 10)
    61  	}
    62  
    63  	if v, replaced := cache.Set(5, 9, 0); v != 10 || !replaced {
    64  		t.Fatal("old value should be evicted")
    65  	}
    66  
    67  	if v, replaced := cache.Set(5, 9, 0); v != 9 || !replaced {
    68  		t.Fatal("old value should be evicted")
    69  	}
    70  
    71  	if v, ok := cache.Get(5); !ok || v != 9 {
    72  		t.Fatalf("bad returned value: %v != %v", v, 10)
    73  	}
    74  }
    75  
    76  func TestTTLCacheSetIfAbsent(t *testing.T) {
    77  	cache := NewTTLCache[int, int](128)
    78  
    79  	cache.Set(5, 5, 0)
    80  
    81  	if _, replaced := cache.SetIfAbsent(5, 10, 0); replaced {
    82  		t.Fatal("should not have replaced")
    83  	}
    84  
    85  	if v, ok := cache.Get(5); !ok || v != 5 {
    86  		t.Fatalf("bad returned value: %v = %v", v, 5)
    87  	}
    88  
    89  	cache.Delete(5)
    90  
    91  	if _, replaced := cache.SetIfAbsent(5, 10, 0); replaced {
    92  		t.Fatal("should not have replaced")
    93  	}
    94  
    95  	if v, ok := cache.Get(5); !ok || v != 10 {
    96  		t.Fatalf("bad returned value: %v = %v", v, 10)
    97  	}
    98  
    99  	cache.Delete(5)
   100  
   101  	if _, replaced := cache.SetIfAbsent(5, 10, 1*time.Second); replaced {
   102  		t.Fatal("should not have replaced")
   103  	}
   104  
   105  	if v, ok := cache.Get(5); !ok || v != 10 {
   106  		t.Fatalf("bad returned value: %v = %v", v, 10)
   107  	}
   108  
   109  	cache.Set(5, 5, 1*time.Second)
   110  	time.Sleep(2 * time.Second)
   111  
   112  	if _, replaced := cache.SetIfAbsent(5, 10, 1*time.Second); !replaced {
   113  		t.Fatal("should have replaced")
   114  	}
   115  
   116  	if v, ok := cache.Get(5); !ok || v != 10 {
   117  		t.Fatalf("bad returned value: %v = %v", v, 10)
   118  	}
   119  
   120  	cache.Set(5, 5, 1*time.Second)
   121  	time.Sleep(2 * time.Second)
   122  
   123  	if _, replaced := cache.SetIfAbsent(5, 10, 0); !replaced {
   124  		t.Fatal("should have replaced")
   125  	}
   126  
   127  	if v, ok := cache.Get(5); !ok || v != 10 {
   128  		t.Fatalf("bad returned value: %v = %v", v, 10)
   129  	}
   130  }
   131  
   132  func TestTTLCacheEviction(t *testing.T) {
   133  	cache := NewTTLCache[int, *int](256, WithShards[int, *int](1024))
   134  	if cache.mask+1 != uint32(cap(cache.shards)) {
   135  		t.Fatalf("bad shard mask: %v", cache.mask)
   136  	}
   137  
   138  	cache = NewTTLCache[int, *int](256, WithShards[int, *int](1))
   139  
   140  	evictedCounter := 0
   141  	for i := 0; i < 512; i++ {
   142  		if v, _ := cache.Set(i, &i, 0); v != nil {
   143  			evictedCounter++
   144  		}
   145  	}
   146  
   147  	if cache.Len() != 256 {
   148  		t.Fatalf("bad len: %v", cache.Len())
   149  	}
   150  
   151  	if evictedCounter != 256 {
   152  		t.Fatalf("bad evicted count: %v", evictedCounter)
   153  	}
   154  
   155  	for i := 0; i < 256; i++ {
   156  		if v, ok := cache.Get(i); ok || v != nil {
   157  			t.Fatalf("key %v value %v should be evicted", i, *v)
   158  		}
   159  	}
   160  
   161  	for i := 256; i < 512; i++ {
   162  		if v, ok := cache.Get(i); !ok {
   163  			t.Fatalf("key %v value %v should not be evicted", i, *v)
   164  		}
   165  	}
   166  
   167  	for i := 256; i < 384; i++ {
   168  		cache.Delete(i)
   169  		if v, ok := cache.Get(i); ok {
   170  			t.Fatalf("old key %v value %v should be deleted", i, *v)
   171  		}
   172  	}
   173  
   174  	for i := 384; i < 512; i++ {
   175  		if v, ok := cache.Get(i); !ok || v == nil {
   176  			t.Fatalf("old key %v value %v should not be deleted", i, *v)
   177  		}
   178  	}
   179  
   180  	if got, want := cache.Len(), 128; got != want {
   181  		t.Fatalf("curent cache length %v should be %v", got, want)
   182  	}
   183  
   184  	cache.Set(400, &evictedCounter, 0)
   185  
   186  	if got, want := len(cache.AppendKeys(nil)), 128; got != want {
   187  		t.Fatalf("curent cache keys length %v should be %v", got, want)
   188  	}
   189  }
   190  
   191  func TestTTLCachePeek(t *testing.T) {
   192  	cache := NewTTLCache[int, int](64)
   193  
   194  	cache.Set(10, 10, 0)
   195  	cache.Set(20, 20, time.Hour)
   196  	if v, expires, ok := cache.Peek(10); !ok || v != 10 || expires != 0 {
   197  		t.Errorf("10 should be set to 10: %v, %v", v, expires)
   198  	}
   199  
   200  	if v, expires, ok := cache.Peek(20); !ok || v != 20 || expires == 0 {
   201  		t.Errorf("20 should be set to 20: %v,", v)
   202  	}
   203  
   204  	if v, expires, ok := cache.Peek(30); ok || v != 0 || expires != 0 {
   205  		t.Errorf("30 should be set to 0: %v,", v)
   206  	}
   207  
   208  	for k := 3; k < 1024; k++ {
   209  		cache.Set(k, k, 0)
   210  	}
   211  	if v, _, ok := cache.Peek(10); ok || v == 10 {
   212  		t.Errorf("%v should not have updated recent-ness of 10", v)
   213  	}
   214  	if v, _, ok := cache.Peek(30); ok || v != 0 {
   215  		t.Errorf("%v should have updated recent-ness of 30", v)
   216  	}
   217  }
   218  
   219  func TestTTLCacheHasher(t *testing.T) {
   220  	cache := NewTTLCache[string, int](1024, WithHasher[string, int](func(key unsafe.Pointer, seed uintptr) (x uintptr) {
   221  		x = 5381
   222  		for _, c := range []byte(*(*string)(key)) {
   223  			x = x*33 + uintptr(c)
   224  		}
   225  		return
   226  	}))
   227  
   228  	if v, ok := cache.Get("abcde"); ok {
   229  		t.Fatalf("bad returned value: %v", v)
   230  	}
   231  
   232  	if _, replaced := cache.Set("abcde", 10, 0); replaced {
   233  		t.Fatal("should not have replaced")
   234  	}
   235  
   236  	if v, ok := cache.Get("abcde"); !ok || v != 10 {
   237  		t.Fatalf("bad returned value: %v != %v", v, 10)
   238  	}
   239  }
   240  
   241  func TestTTLCacheLoader(t *testing.T) {
   242  	cache := NewTTLCache[string, int](1024)
   243  	if v, err, ok := cache.GetOrLoad(context.Background(), "a", nil); ok || err == nil || v != 0 {
   244  		t.Errorf("cache.GetOrLoad(\"a\", nil) again should be return error: %v, %v, %v", v, err, ok)
   245  	}
   246  
   247  	cache = NewTTLCache[string, int](1024, WithLoader[string, int](func(ctx context.Context, key string) (int, time.Duration, error) {
   248  		if key == "" {
   249  			return 0, 0, fmt.Errorf("invalid key: %v", key)
   250  		}
   251  		i := int(key[0] - 'a' + 1)
   252  		return i, time.Duration(i) * time.Second, nil
   253  	}))
   254  
   255  	if v, err, ok := cache.GetOrLoad(context.Background(), "", nil); ok || err == nil || v != 0 {
   256  		t.Errorf("cache.GetOrLoad(\"a\", nil) again should be return error: %v, %v, %v", v, err, ok)
   257  	}
   258  
   259  	if v, err, ok := cache.GetOrLoad(context.Background(), "b", nil); ok || err != nil || v != 2 {
   260  		t.Errorf("cache.GetOrLoad(\"b\", nil) again should be return 2: %v, %v, %v", v, err, ok)
   261  	}
   262  
   263  	if v, err, ok := cache.GetOrLoad(context.Background(), "a", nil); ok || err != nil || v != 1 {
   264  		t.Errorf("cache.GetOrLoad(\"a\", nil) should be return 1: %v, %v, %v", v, err, ok)
   265  	}
   266  
   267  	if v, err, ok := cache.GetOrLoad(context.Background(), "a", nil); !ok || err != nil || v != 1 {
   268  		t.Errorf("cache.GetOrLoad(\"a\") again should be return 1: %v, %v, %v", v, err, ok)
   269  	}
   270  
   271  	time.Sleep(2 * time.Second)
   272  
   273  	if v, err, ok := cache.GetOrLoad(context.Background(), "a", nil); ok || err != nil || v != 1 {
   274  		t.Errorf("cache.GetOrLoad(\"a\") again should be return 1: %v, %v, %v", v, err, ok)
   275  	}
   276  }
   277  
   278  func TestTTLCacheLoaderPanic(t *testing.T) {
   279  	defer func() {
   280  		if r := recover(); r != nil {
   281  			if !strings.Contains(fmt.Sprint(r), "not_supported") {
   282  				t.Errorf("should be not_supported")
   283  			}
   284  		}
   285  	}()
   286  	_ = NewTTLCache[string, int](1024, WithLoader[string, int](func(ctx context.Context, key string) (int, error) {
   287  		return 1, nil
   288  	}))
   289  	t.Errorf("should be panic above")
   290  }
   291  
   292  func TestTTLCacheLoaderSingleflight(t *testing.T) {
   293  	var loads uint32
   294  
   295  	cache := NewTTLCache[string, int](1024, WithLoader[string, int](func(ctx context.Context, key string) (int, time.Duration, error) {
   296  		atomic.AddUint32(&loads, 1)
   297  		time.Sleep(100 * time.Millisecond)
   298  		return int(key[0] - 'a' + 1), time.Hour, nil
   299  	}))
   300  
   301  	var wg sync.WaitGroup
   302  	wg.Add(10)
   303  	for i := 0; i < 10; i++ {
   304  		go func(i int) {
   305  			defer wg.Done()
   306  			v, err, ok := cache.GetOrLoad(context.Background(), "a", nil)
   307  			if v != 1 || err != nil || !ok {
   308  				t.Errorf("a should be set to 1: %v,%v,%v", v, err, ok)
   309  			}
   310  		}(i)
   311  	}
   312  	wg.Wait()
   313  
   314  	if n := atomic.LoadUint32(&loads); n != 1 {
   315  		t.Errorf("a should be loaded only once: %v", n)
   316  	}
   317  }
   318  
   319  func TestTTLCacheSlidingGet(t *testing.T) {
   320  	cache := NewTTLCache[string, int](256, WithSliding[string, int](true), WithShards[string, int](1))
   321  
   322  	cache.Set("a", 1, 0)
   323  	cache.Set("b", 2, 3*time.Second)
   324  	cache.Set("c", 3, 3*time.Second)
   325  	cache.Set("d", 3, 1*time.Second)
   326  
   327  	if got, want := cache.AppendKeys(nil), 4; len(got) != want {
   328  		t.Fatalf("curent cache keys %v length should be %v", got, want)
   329  	}
   330  
   331  	if v, ok := cache.Get("a"); !ok || v != 1 {
   332  		t.Fatalf("a should be set to 1: %v,", v)
   333  	}
   334  
   335  	time.Sleep(2 * time.Second)
   336  	if v, ok := cache.Get("c"); !ok || v != 3 {
   337  		t.Errorf("c should be set to 3: %v,", v)
   338  	}
   339  	if v, ok := cache.Get("d"); ok || v != 0 {
   340  		t.Errorf("d should be set to 0: %v,", v)
   341  	}
   342  
   343  	if got, want := cache.AppendKeys(nil), 3; len(got) != want {
   344  		t.Fatalf("curent cache keys %v length should be %v", got, want)
   345  	}
   346  
   347  	cache.Set("c", 4, 3*time.Second)
   348  
   349  	time.Sleep(2 * time.Second)
   350  	if v, ok := cache.Get("c"); !ok || v != 4 {
   351  		t.Errorf("c should be still set to 4: %v,", v)
   352  	}
   353  
   354  	time.Sleep(1 * time.Second)
   355  
   356  	if got, want := cache.AppendKeys(nil), 2; len(got) != want {
   357  		t.Fatalf("curent cache keys %v length should be %v", got, want)
   358  	}
   359  
   360  }
   361  
   362  func TestTTLCacheStats(t *testing.T) {
   363  	cache := NewTTLCache[string, int](256, WithShards[string, int](1))
   364  
   365  	cache.Set("a", 1, 0)
   366  	cache.Set("b", 2, 3*time.Second)
   367  	cache.Set("c", 3, 3*time.Second)
   368  	cache.Set("d", 3, 2*time.Second)
   369  
   370  	stats := cache.Stats()
   371  	if got, want := stats.EntriesCount, uint64(4); got != want {
   372  		t.Fatalf("cache entries should be %v: %v", want, got)
   373  	}
   374  	if got, want := stats.GetCalls, uint64(0); got != want {
   375  		t.Fatalf("cache get calls should be %v: %v", want, got)
   376  	}
   377  	if got, want := stats.SetCalls, uint64(4); got != want {
   378  		t.Fatalf("cache set calls should be %v: %v", want, got)
   379  	}
   380  	if got, want := stats.Misses, uint64(0); got != want {
   381  		t.Fatalf("cache misses should be %v: %v", want, got)
   382  	}
   383  
   384  	cache.Get("a")
   385  	cache.Get("b")
   386  	cache.Get("x")
   387  	cache.Get("y")
   388  	cache.Get("z")
   389  	cache.Set("c", 13, 3*time.Second)
   390  
   391  	stats = cache.Stats()
   392  	if got, want := stats.EntriesCount, uint64(4); got != want {
   393  		t.Fatalf("cache entries should be %v: %v", want, got)
   394  	}
   395  	if got, want := stats.GetCalls, uint64(5); got != want {
   396  		t.Fatalf("cache get calls should be %v: %v", want, got)
   397  	}
   398  	if got, want := stats.SetCalls, uint64(5); got != want {
   399  		t.Fatalf("cache set calls should be %v: %v", want, got)
   400  	}
   401  	if got, want := stats.Misses, uint64(3); got != want {
   402  		t.Fatalf("cache misses should be %v: %v", want, got)
   403  	}
   404  }
   405  
   406  func BenchmarkTTLCacheRand(b *testing.B) {
   407  	cache := NewTTLCache[int64, int64](8192)
   408  
   409  	trace := make([]int64, b.N*2)
   410  	for i := 0; i < b.N*2; i++ {
   411  		trace[i] = rand.Int63() % 32768
   412  	}
   413  
   414  	b.ReportAllocs()
   415  	b.ResetTimer()
   416  
   417  	var hit, miss int
   418  	for i := 0; i < 2*b.N; i++ {
   419  		if i%2 == 0 {
   420  			cache.Set(trace[i], trace[i], 0)
   421  		} else {
   422  			if _, ok := cache.Get(trace[i]); ok {
   423  				hit++
   424  			} else {
   425  				miss++
   426  			}
   427  		}
   428  	}
   429  	b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss))
   430  }
   431  
   432  func BenchmarkTTLCacheFreq(b *testing.B) {
   433  	cache := NewTTLCache[int64, int64](8192)
   434  
   435  	trace := make([]int64, b.N*2)
   436  	for i := 0; i < b.N*2; i++ {
   437  		if i%2 == 0 {
   438  			trace[i] = rand.Int63() % 16384
   439  		} else {
   440  			trace[i] = rand.Int63() % 32768
   441  		}
   442  	}
   443  
   444  	b.ReportAllocs()
   445  	b.ResetTimer()
   446  
   447  	for i := 0; i < b.N; i++ {
   448  		cache.Set(trace[i], trace[i], 0)
   449  	}
   450  	var hit, miss int
   451  	for i := 0; i < b.N; i++ {
   452  		if _, ok := cache.Get(trace[i]); ok {
   453  			hit++
   454  		} else {
   455  			miss++
   456  		}
   457  	}
   458  	b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss))
   459  }
   460  
   461  func BenchmarkTTLCacheTTL(b *testing.B) {
   462  	cache := NewTTLCache[int64, int64](8192)
   463  
   464  	trace := make([]int64, b.N*2)
   465  	for i := 0; i < b.N*2; i++ {
   466  		if i%2 == 0 {
   467  			trace[i] = rand.Int63() % 16384
   468  		} else {
   469  			trace[i] = rand.Int63() % 32768
   470  		}
   471  	}
   472  
   473  	b.ReportAllocs()
   474  	b.ResetTimer()
   475  
   476  	for i := 0; i < b.N; i++ {
   477  		cache.Set(trace[i], trace[i], 60*time.Second)
   478  	}
   479  	var hit, miss int
   480  	for i := 0; i < b.N; i++ {
   481  		if _, ok := cache.Get(trace[i]); ok {
   482  			hit++
   483  		} else {
   484  			miss++
   485  		}
   486  	}
   487  	b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(hit+miss))
   488  }