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