diff options
Diffstat (limited to 'store_test.go')
-rw-r--r-- | store_test.go | 388 |
1 files changed, 357 insertions, 31 deletions
diff --git a/store_test.go b/store_test.go index 89f4de6..e2c44e9 100644 --- a/store_test.go +++ b/store_test.go @@ -3,6 +3,7 @@ package cache import ( "bytes" "encoding/binary" + "errors" "strconv" "testing" "time" @@ -26,10 +27,30 @@ func TestStoreGetSet(t *testing.T) { store := setupTestStore(t) want := []byte("Value") + store.Set([]byte("Key"), want, 0) + got, ttl, ok := store.Get([]byte("Key")) + if !ok { + t.Fatalf("expected key to exist") + } + if !bytes.Equal(want, got) { + t.Errorf("got %v, want %v", got, want) + } + if ttl.Round(time.Second) != 0 { + t.Errorf("ttl same: got %v expected %v", ttl.Round(time.Second), 1*time.Hour) + } + + }) + + t.Run("Exists Non Expiry", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + + want := []byte("Value") store.Set([]byte("Key"), want, 1*time.Hour) got, ttl, ok := store.Get([]byte("Key")) if !ok { - t.Errorf("expected key to exist") + t.Fatalf("expected key to exist") } if !bytes.Equal(want, got) { t.Errorf("got %v, want %v", got, want) @@ -71,12 +92,13 @@ func TestStoreGetSet(t *testing.T) { want := []byte("Value") store.Set([]byte("Key"), want, 0) got, _, ok := store.Get([]byte("Key")) + if !ok { + t.Fatal("expected key to exist") + } if !bytes.Equal(want, got) { t.Errorf("got %v, want %v", got, want) } - if !ok { - t.Errorf("expected key to exist") - } + }) t.Run("Resize", func(t *testing.T) { @@ -152,49 +174,353 @@ func TestStoreClear(t *testing.T) { } } +func TestStoreUpdateInPlace(t *testing.T) { + t.Parallel() + + t.Run("Exists", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + + want := []byte("Value") + store.Set([]byte("Key"), []byte("Initial"), 1*time.Hour) + + processFunc := func(v []byte) ([]byte, error) { + return want, nil + } + + if err := store.UpdateInPlace([]byte("Key"), processFunc, 1*time.Hour); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + got, _, ok := store.Get([]byte("Key")) + if !ok { + t.Fatalf("expected key to exist") + } + if !bytes.Equal(want, got) { + t.Errorf("got %v, want %v", got, want) + } + }) + + t.Run("Not Exists", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + + processFunc := func(v []byte) ([]byte, error) { + return []byte("Value"), nil + } + + if err := store.UpdateInPlace([]byte("Key"), processFunc, 1*time.Hour); !errors.Is(err, ErrKeyNotFound) { + t.Fatalf("expected error: %v, got: %v", ErrKeyNotFound, err) + } + }) +} + +func TestStoreMemoize(t *testing.T) { + t.Parallel() + + t.Run("Cache Miss", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + + factoryFunc := func() ([]byte, error) { + return []byte("Value"), nil + } + + got, err := store.Memoize([]byte("Key"), factoryFunc, 1*time.Hour) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(got, []byte("Value")) { + t.Fatalf("expected: %v, got: %v", "Value", got) + } + + got, _, ok := store.Get([]byte("Key")) + if !ok { + t.Fatalf("expected key to exist") + } + if !bytes.Equal(got, []byte("Value")) { + t.Fatalf("expected: %v, got: %v", "Value", got) + } + }) + + t.Run("Cache Hit", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + + store.Set([]byte("Key"), []byte("Value"), 1*time.Hour) + + factoryFunc := func() ([]byte, error) { + return []byte("NewValue"), nil + } + + got, err := store.Memoize([]byte("Key"), factoryFunc, 1*time.Hour) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(got, []byte("Value")) { + t.Fatalf("expected: %v, got: %v", "Value", got) + } + }) +} + +func TestStoreCleanup(t *testing.T) { + t.Parallel() + + t.Run("Cleanup Expired", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + + store.Set([]byte("1"), []byte("1"), 500*time.Millisecond) + store.Set([]byte("2"), []byte("2"), 1*time.Hour) + + time.Sleep(600 * time.Millisecond) + + store.Cleanup() + + if _, _, ok := store.Get([]byte("1")); ok { + t.Fatalf("expected 1 to not exist") + } + + if _, _, ok := store.Get([]byte("2")); !ok { + t.Fatalf("expected 2 to exist") + } + }) + + t.Run("No Cleanup", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + + store.Set([]byte("Key"), []byte("Value"), 1*time.Hour) + + // No cleanup should occur + store.Cleanup() + + if _, _, ok := store.Get([]byte("Key")); !ok { + t.Fatalf("expected key to exist") + } + }) +} + +func TestStoreEvict(t *testing.T) { + t.Parallel() + + t.Run("Evict FIFO", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + if err := store.Policy.SetPolicy(PolicyFIFO); err != nil { + t.Fatalf("unexpected error: %v", err) + } + store.MaxCost = 5 + + store.Set([]byte("1"), []byte("1"), 0) + store.Set([]byte("2"), []byte("2"), 0) + + // Trigger eviction + store.Set([]byte("3"), []byte("3"), 0) + store.Evict() + + if _, _, ok := store.Get([]byte("1")); ok { + t.Fatalf("expected key 1 to not exist") + } + + if _, _, ok := store.Get([]byte("2")); !ok { + t.Fatalf("expected key 2 to exist") + } + }) + + t.Run("No Evict", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + if err := store.Policy.SetPolicy(PolicyFIFO); err != nil { + t.Fatalf("unexpected error: %v", err) + } + store.MaxCost = 10 + + store.Set([]byte("1"), []byte("1"), 0) + store.Set([]byte("2"), []byte("2"), 0) + + // No eviction should occur + store.Set([]byte("3"), []byte("3"), 0) + store.Evict() + + if _, _, ok := store.Get([]byte("1")); !ok { + t.Fatalf("expected key 1 to exist") + } + + if _, _, ok := store.Get([]byte("2")); !ok { + t.Fatalf("expected key 2 to exist") + } + }) + + t.Run("No Evict PolicyNone", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + if err := store.Policy.SetPolicy(PolicyNone); err != nil { + t.Fatalf("unexpected error: %v", err) + } + store.MaxCost = 5 + + store.Set([]byte("1"), []byte("1"), 0) + store.Set([]byte("2"), []byte("2"), 0) + + // No eviction should occur + store.Set([]byte("3"), []byte("3"), 0) + store.Evict() + + if _, _, ok := store.Get([]byte("1")); !ok { + t.Fatalf("expected key 1 to exist") + } + + if _, _, ok := store.Get([]byte("2")); !ok { + t.Fatalf("expected key 2 to exist") + } + }) + + t.Run("No Evict MaxCost Zero", func(t *testing.T) { + t.Parallel() + + store := setupTestStore(t) + if err := store.Policy.SetPolicy(PolicyFIFO); err != nil { + t.Fatalf("unexpected error: %v", err) + } + store.MaxCost = 0 + + store.Set([]byte("1"), []byte("1"), 0) + store.Set([]byte("2"), []byte("2"), 0) + + store.Evict() + + if _, _, ok := store.Get([]byte("1")); !ok { + t.Fatalf("expected key 1 to exist") + } + + if _, _, ok := store.Get([]byte("2")); !ok { + t.Fatalf("expected key 2 to exist") + } + }) +} + func BenchmarkStoreGet(b *testing.B) { - for n := 1; n <= 10000; n *= 10 { - b.Run(strconv.Itoa(n), func(b *testing.B) { - want := setupTestStore(b) + policy := map[string]EvictionPolicyType{ + "None": PolicyNone, + "FIFO": PolicyFIFO, + "LRU": PolicyLRU, + "LFU": PolicyLFU, + "LTR": PolicyLTR, + } + for k, v := range policy { + b.Run(k, func(b *testing.B) { + for n := 1; n <= 10000; n *= 10 { + b.Run(strconv.Itoa(n), func(b *testing.B) { + want := setupTestStore(b) - for i := range n - 1 { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, uint64(i)) - want.Set(buf, buf, 0) - } + if err := want.Policy.SetPolicy(v); err != nil { + b.Fatalf("unexpected error: %v", err) + } - key := []byte("Key") - want.Set(key, []byte("Store"), 0) - b.ReportAllocs() + for i := range n - 1 { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + want.Set(buf, buf, 0) + } - b.ResetTimer() + key := []byte("Key") + want.Set(key, []byte("Store"), 0) + b.ReportAllocs() - for b.Loop() { - want.Get(key) + b.ResetTimer() + + for b.Loop() { + want.Get(key) + } + }) } }) } } func BenchmarkStoreSet(b *testing.B) { - for n := 1; n <= 10000; n *= 10 { - b.Run(strconv.Itoa(n), func(b *testing.B) { - want := setupTestStore(b) + policy := map[string]EvictionPolicyType{ + "None": PolicyNone, + "FIFO": PolicyFIFO, + "LRU": PolicyLRU, + "LFU": PolicyLFU, + "LTR": PolicyLTR, + } + for k, v := range policy { + b.Run(k, func(b *testing.B) { + for n := 1; n <= 10000; n *= 10 { + b.Run(strconv.Itoa(n), func(b *testing.B) { + want := setupTestStore(b) - for i := range n - 1 { - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, uint64(i)) - want.Set(buf, buf, 0) + if err := want.Policy.SetPolicy(v); err != nil { + b.Fatalf("unexpected error: %v", err) + } + + for i := range n - 1 { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + want.Set(buf, buf, 0) + } + + key := []byte("Key") + store := []byte("Store") + + b.ReportAllocs() + b.ResetTimer() + + for b.Loop() { + want.Set(key, store, 0) + } + }) } + }) + } +} - key := []byte("Key") - store := []byte("Store") +func BenchmarkStoreSetInsert(b *testing.B) { + policy := map[string]EvictionPolicyType{ + "None": PolicyNone, + "FIFO": PolicyFIFO, + "LRU": PolicyLRU, + "LFU": PolicyLFU, + "LTR": PolicyLTR, + } + for k, v := range policy { + b.Run(k, func(b *testing.B) { + for n := 1; n <= 10000; n *= 10 { + b.Run(strconv.Itoa(n), func(b *testing.B) { + want := setupTestStore(b) - b.ReportAllocs() - b.ResetTimer() + if err := want.Policy.SetPolicy(v); err != nil { + b.Fatalf("unexpected error: %v", err) + } - for b.Loop() { - want.Set(key, store, 0) + list := make([][]byte, n) + for i := range n { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + list = append(list, buf) + } + + b.ReportAllocs() + b.ResetTimer() + + for b.Loop() { + for _, k := range list { + want.Set(k, k, 0) + } + } + }) } }) } |