summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md16
-rw-r--r--conn.go183
-rw-r--r--conn_test.go37
-rw-r--r--encoding.go13
-rw-r--r--encoding_test.go89
-rw-r--r--evict_test.go14
-rw-r--r--examples/basic_usage/main.go12
-rw-r--r--examples/eviction_policy/main.go53
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--internal/pausedtimer/timer_test.go21
-rw-r--r--store.go15
-rw-r--r--store_test.go91
13 files changed, 384 insertions, 166 deletions
diff --git a/README.md b/README.md
index f4dba3a..6d0054c 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
# Cache
+An deamonless in-memory caching library with persistant snapshots.
+
## Features
- **In-Memory Cache**: Fast access to cached data.
- **Uses Generics with serialization**: To make it type safe and support several types via msgpack.
-s
+
- **File-Backed Storage**: Persistent storage of cache data.
- **Eviction Policies**: Support for FIFO, LRU, LFU, and LTR eviction policies.
@@ -86,13 +88,21 @@ func main() {
}
// Get a value by key
-
value, ttl, err := db.GetValue("key")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Value: %s, TTL: %v\n", value, ttl)
+
+ // Get a value with a pointer by key(Useful if the value type is an interface).
+ var value string
+ value, ttl, err := db.GetValue(&value)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("Value: %s, TTL: %v\n", value, ttl)
}
```
@@ -128,7 +138,7 @@ The Cache Library supports the following configuration options:
### Additional Methods
-- `Get`: Retrieves a value from the cache by key and returns its TTL.
+- `Get`: Retrieves a value from the cache by key and returns its TTL. It take an out pointer.
- `GetValue`: Retrieves a value from the cache by key and returns the value and its TTL.
diff --git a/conn.go b/conn.go
index ecd92aa..15f06f1 100644
--- a/conn.go
+++ b/conn.go
@@ -2,8 +2,8 @@ package cache
import (
"errors"
+ "fmt"
"io"
- "log"
"os"
"sync"
"time"
@@ -12,19 +12,20 @@ import (
"github.com/vmihailenco/msgpack/v5"
)
-// db represents a cache database with file-backed storage and in-memory operation.
-type db struct {
+// cache represents a cache database with file-backed storage and in-memory operation.
+type cache struct {
File io.WriteSeeker
Store store
Stop chan struct{}
wg sync.WaitGroup
+ err error
}
// Option is a function type for configuring the db.
-type Option func(*db) error
+type Option func(*cache) error
// openFile opens a file-backed cache database with the given options.
-func openFile(filename string, options ...Option) (*db, error) {
+func openFile(filename string, options ...Option) (*cache, error) {
ret, err := openMem(options...)
if err != nil {
return nil, err
@@ -42,7 +43,9 @@ func openFile(filename string, options ...Option) (*db, error) {
if fileInfo.Size() == 0 {
ret.File = file
- ret.Flush()
+ if err := ret.Flush(); err != nil {
+ return nil, err
+ }
} else {
err := ret.Store.LoadSnapshot(file)
if err != nil {
@@ -56,9 +59,10 @@ func openFile(filename string, options ...Option) (*db, error) {
}
// openMem initializes an in-memory cache database with the given options.
-func openMem(options ...Option) (*db, error) {
- ret := &db{}
+func openMem(options ...Option) (*cache, error) {
+ ret := &cache{}
ret.Store.Init()
+
if err := ret.SetConfig(options...); err != nil {
return nil, err
}
@@ -66,21 +70,22 @@ func openMem(options ...Option) (*db, error) {
return ret, nil
}
-// Start begins the background worker for periodic tasks.
-func (d *db) Start() {
- d.Stop = make(chan struct{})
- d.wg.Add(1)
+// start begins the background worker for periodic tasks.
+func (c *cache) start() {
+ c.Stop = make(chan struct{})
+
+ c.wg.Add(1)
- go d.backgroundWorker()
+ go c.backgroundWorker()
}
// SetConfig applies configuration options to the db.
-func (d *db) SetConfig(options ...Option) error {
- d.Store.Lock.Lock()
- defer d.Store.Lock.Unlock()
+func (c *cache) SetConfig(options ...Option) error {
+ c.Store.Lock.Lock()
+ defer c.Store.Lock.Unlock()
for _, opt := range options {
- if err := opt(d); err != nil {
+ if err := opt(c); err != nil {
return err
}
}
@@ -90,14 +95,14 @@ func (d *db) SetConfig(options ...Option) error {
// WithPolicy sets the eviction policy for the cache.
func WithPolicy(e EvictionPolicyType) Option {
- return func(d *db) error {
+ return func(d *cache) error {
return d.Store.Policy.SetPolicy(e)
}
}
// WithMaxCost sets the maximum cost for the cache.
func WithMaxCost(maxCost uint64) Option {
- return func(d *db) error {
+ return func(d *cache) error {
d.Store.MaxCost = maxCost
return nil
@@ -106,7 +111,7 @@ func WithMaxCost(maxCost uint64) Option {
// SetSnapshotTime sets the interval for taking snapshots of the cache.
func SetSnapshotTime(t time.Duration) Option {
- return func(d *db) error {
+ return func(d *cache) error {
d.Store.SnapshotTicker.Reset(t)
return nil
@@ -115,7 +120,7 @@ func SetSnapshotTime(t time.Duration) Option {
// SetCleanupTime sets the interval for cleaning up expired entries.
func SetCleanupTime(t time.Duration) Option {
- return func(d *db) error {
+ return func(d *cache) error {
d.Store.CleanupTicker.Reset(t)
return nil
@@ -123,98 +128,115 @@ func SetCleanupTime(t time.Duration) Option {
}
// backgroundWorker performs periodic tasks such as snapshotting and cleanup.
-func (d *db) backgroundWorker() {
- defer d.wg.Done()
+func (c *cache) backgroundWorker() {
+ defer c.wg.Done()
defer func() {
if r := recover(); r != nil {
- log.Printf("Recovered from panic in background worker: %v", r)
+ c.err = fmt.Errorf("panic occurred: %v", r)
}
}()
- d.Store.SnapshotTicker.Resume()
- defer d.Store.SnapshotTicker.Stop()
+ c.Store.SnapshotTicker.Resume()
+ defer c.Store.SnapshotTicker.Stop()
- d.Store.CleanupTicker.Resume()
- defer d.Store.CleanupTicker.Stop()
+ c.Store.CleanupTicker.Resume()
+ defer c.Store.CleanupTicker.Stop()
+
+ c.Store.Cleanup()
+ c.Store.Evict()
for {
select {
- case <-d.Stop:
+ case <-c.Stop:
return
- case <-d.Store.SnapshotTicker.C:
- d.Flush()
- case <-d.Store.CleanupTicker.C:
- d.Store.Cleanup()
- d.Store.Evict()
+ case <-c.Store.SnapshotTicker.C:
+ if err := c.Flush(); err != nil {
+ c.err = err
+ }
+ case <-c.Store.CleanupTicker.C:
+ c.Store.Cleanup()
+ c.Store.Evict()
}
}
}
+func (c *cache) Error() error {
+ return c.err
+}
+
+func (c *cache) Cost() uint64 {
+ return c.Store.Cost
+}
+
// Close stops the background worker and cleans up resources.
-func (d *db) Close() error {
- close(d.Stop)
- d.wg.Wait()
- err := d.Flush()
- d.Clear()
+func (c *cache) Close() error {
+ close(c.Stop)
+ c.wg.Wait()
+
+ err := c.Flush()
+ c.Clear()
var err1 error
- if d.File != nil {
- closer, ok := d.File.(io.Closer)
+
+ if c.File != nil {
+ closer, ok := c.File.(io.Closer)
if ok {
err1 = closer.Close()
}
}
+
if err != nil {
return err
}
+
return err1
}
// Flush writes the current state of the store to the file.
-func (d *db) Flush() error {
- if d.File != nil {
- return d.Store.Snapshot(d.File)
+func (c *cache) Flush() error {
+ if c.File != nil {
+ return c.Store.Snapshot(c.File)
}
return nil
}
// Clear removes all entries from the in-memory store.
-func (d *db) Clear() {
- d.Store.Clear()
+func (c *cache) Clear() {
+ c.Store.Clear()
}
var ErrKeyNotFound = errors.New("key not found") // ErrKeyNotFound is returned when a key is not found in the cache.
-// The Cache database. Can be initialized by either OpenFile or OpenMem. Uses per DB Locks.
-// DB represents a generic cache database with key-value pairs.
-type DB[K any, V any] struct {
- *db
+// The Cache database. Can be initialized by either OpenFile or OpenMem. Uses per Cache Locks.
+// Cache represents a generic cache database with key-value pairs.
+type Cache[K any, V any] struct {
+ *cache
}
// OpenFile opens a file-backed cache database with the specified options.
-func OpenFile[K any, V any](filename string, options ...Option) (DB[K, V], error) {
+func OpenFile[K any, V any](filename string, options ...Option) (Cache[K, V], error) {
ret, err := openFile(filename, options...)
if err != nil {
- return zero[DB[K, V]](), err
+ return zero[Cache[K, V]](), err
}
- ret.Start()
+ ret.start()
- return DB[K, V]{db: ret}, nil
+ return Cache[K, V]{cache: ret}, nil
}
// OpenMem initializes an in-memory cache database with the specified options.
-func OpenMem[K any, V any](options ...Option) (DB[K, V], error) {
+func OpenMem[K any, V any](options ...Option) (Cache[K, V], error) {
ret, err := openMem(options...)
if err != nil {
- return zero[DB[K, V]](), err
+ return zero[Cache[K, V]](), err
}
- ret.Start()
+ ret.start()
- return DB[K, V]{db: ret}, nil
+ return Cache[K, V]{cache: ret}, nil
}
// marshal serializes a value using msgpack.
@@ -228,13 +250,17 @@ func unmarshal[T any](data []byte, v *T) error {
}
// Get retrieves a value from the cache by key and returns its TTL.
-func (h *DB[K, V]) Get(key K, value *V) (time.Duration, error) {
+func (c *Cache[K, V]) Get(key K, value *V) (time.Duration, error) {
keyData, err := marshal(key)
if err != nil {
return 0, err
}
- v, ttl, ok := h.Store.Get(keyData)
+ if err := c.err; err != nil {
+ return 0, err
+ }
+
+ v, ttl, ok := c.Store.Get(keyData)
if !ok {
return 0, ErrKeyNotFound
}
@@ -249,15 +275,15 @@ func (h *DB[K, V]) Get(key K, value *V) (time.Duration, error) {
}
// GetValue retrieves a value from the cache by key and returns the value and its TTL.
-func (h *DB[K, V]) GetValue(key K) (V, time.Duration, error) {
+func (c *Cache[K, V]) GetValue(key K) (V, time.Duration, error) {
value := zero[V]()
- ttl, err := h.Get(key, &value)
+ ttl, err := c.Get(key, &value)
return value, ttl, err
}
// Set adds a key-value pair to the cache with a specified TTL.
-func (h *DB[K, V]) Set(key K, value V, ttl time.Duration) error {
+func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) error {
keyData, err := marshal(key)
if err != nil {
return err
@@ -268,19 +294,27 @@ func (h *DB[K, V]) Set(key K, value V, ttl time.Duration) error {
return err
}
- h.Store.Set(keyData, valueData, ttl)
+ if err := c.err; err != nil {
+ return err
+ }
+
+ c.Store.Set(keyData, valueData, ttl)
return nil
}
// Delete removes a key-value pair from the cache.
-func (h *DB[K, V]) Delete(key K) error {
+func (c *Cache[K, V]) Delete(key K) error {
keyData, err := marshal(key)
if err != nil {
return err
}
- ok := h.Store.Delete(keyData)
+ if err := c.err; err != nil {
+ return err
+ }
+
+ ok := c.Store.Delete(keyData)
if !ok {
return ErrKeyNotFound
}
@@ -290,13 +324,17 @@ func (h *DB[K, V]) Delete(key K) error {
// UpdateInPlace retrieves a value from the cache, processes it using the provided function,
// and then sets the result back into the cache with the same key.
-func (h *DB[K, V]) UpdateInPlace(key K, processFunc func(V) (V, error), ttl time.Duration) error {
+func (c *Cache[K, V]) UpdateInPlace(key K, processFunc func(V) (V, error), ttl time.Duration) error {
keyData, err := marshal(key)
if err != nil {
return err
}
- return h.Store.UpdateInPlace(keyData, func(data []byte) ([]byte, error) {
+ if err := c.err; err != nil {
+ return err
+ }
+
+ return c.Store.UpdateInPlace(keyData, func(data []byte) ([]byte, error) {
var value V
if err := unmarshal(data, &value); err != nil {
return nil, err
@@ -313,13 +351,17 @@ func (h *DB[K, V]) UpdateInPlace(key K, processFunc func(V) (V, error), ttl time
// Memorize attempts to retrieve a value from the cache. If the retrieval fails,
// it sets the result of the factory function into the cache and returns that result.
-func (h *DB[K, V]) Memorize(key K, factoryFunc func() (V, error), ttl time.Duration) (V, error) {
+func (c *Cache[K, V]) Memorize(key K, factoryFunc func() (V, error), ttl time.Duration) (V, error) {
keyData, err := marshal(key)
if err != nil {
return zero[V](), err
}
- data, err := h.Store.Memorize(keyData, func() ([]byte, error) {
+ if err := c.err; err != nil {
+ return zero[V](), err
+ }
+
+ data, err := c.Store.Memorize(keyData, func() ([]byte, error) {
value, err := factoryFunc()
if err != nil {
return nil, err
@@ -327,7 +369,6 @@ func (h *DB[K, V]) Memorize(key K, factoryFunc func() (V, error), ttl time.Durat
return marshal(value)
}, ttl)
-
if err != nil {
return zero[V](), err
}
diff --git a/conn_test.go b/conn_test.go
index c80072a..47bdde4 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -1,22 +1,24 @@
package cache
import (
+ "errors"
"strconv"
"testing"
"time"
-
- "errors"
)
-func setupTestDB[K any, V any](tb testing.TB) *DB[K, V] {
+func setupTestDB[K any, V any](tb testing.TB) *Cache[K, V] {
tb.Helper()
db, err := OpenMem[K, V]()
if err != nil {
tb.Fatalf("unexpected error: %v", err)
}
+
tb.Cleanup(func() {
- db.Close()
+ if err := db.Close(); err != nil {
+ tb.Fatalf("unexpected error: %v", err)
+ }
})
return &db
@@ -40,6 +42,7 @@ func TestDBGetSet(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if want != got {
t.Fatalf("expected: %v, got: %v", want, got)
}
@@ -77,6 +80,7 @@ func TestDBGetSet(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if want != got {
t.Fatalf("expected: %v, got: %v", want, got)
}
@@ -107,6 +111,7 @@ func TestDBDelete(t *testing.T) {
db := setupTestDB[string, string](t)
want := "Value"
+
if err := db.Set("Key", want, 0); err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -140,7 +145,10 @@ func TestDBUpdateInPlace(t *testing.T) {
store := setupTestDB[string, string](t)
want := "Value"
- store.Set("Key", "Initial", 1*time.Hour)
+
+ if err := store.Set("Key", "Initial", 1*time.Hour); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
processFunc := func(v string) (string, error) {
return want, nil
@@ -154,6 +162,7 @@ func TestDBUpdateInPlace(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if want != got {
t.Errorf("got %v, want %v", got, want)
}
@@ -194,6 +203,7 @@ func TestDBMemoize(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if got != "Value" {
t.Fatalf("expected: %v, got: %v", "Value", got)
}
@@ -202,6 +212,7 @@ func TestDBMemoize(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if want != got {
t.Errorf("got %v, want %v", got, want)
}
@@ -214,7 +225,9 @@ func TestDBMemoize(t *testing.T) {
want := "NewValue"
- store.Set("Key", "Value", 1*time.Hour)
+ if err := store.Set("Key", "Value", 1*time.Hour); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
factoryFunc := func() (string, error) {
return want, nil
@@ -224,6 +237,7 @@ func TestDBMemoize(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if got != "Value" {
t.Fatalf("expected: %v, got: %v", "Value", got)
}
@@ -231,7 +245,7 @@ func TestDBMemoize(t *testing.T) {
}
func BenchmarkDBGet(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
db := setupTestDB[int, int](b)
for i := range n {
@@ -242,8 +256,6 @@ func BenchmarkDBGet(b *testing.B) {
b.ReportAllocs()
- b.ResetTimer()
-
for b.Loop() {
if _, _, err := db.GetValue(n - 1); err != nil {
b.Fatalf("unexpected error: %v", err)
@@ -254,7 +266,7 @@ func BenchmarkDBGet(b *testing.B) {
}
func BenchmarkDBSet(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
db := setupTestDB[int, int](b)
for i := range n - 1 {
@@ -264,7 +276,6 @@ func BenchmarkDBSet(b *testing.B) {
}
b.ReportAllocs()
- b.ResetTimer()
for b.Loop() {
if err := db.Set(n, n, 0); err != nil {
@@ -276,7 +287,7 @@ func BenchmarkDBSet(b *testing.B) {
}
func BenchmarkDBDelete(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
db := setupTestDB[int, int](b)
for i := range n - 1 {
@@ -286,12 +297,12 @@ func BenchmarkDBDelete(b *testing.B) {
}
b.ReportAllocs()
- b.ResetTimer()
for b.Loop() {
if err := db.Set(n, n, 0); err != nil {
b.Fatalf("unexpected error: %v", err)
}
+
if err := db.Delete(n); err != nil {
b.Fatalf("unexpected error: %v", err)
}
diff --git a/encoding.go b/encoding.go
index fe376b2..a8778ab 100644
--- a/encoding.go
+++ b/encoding.go
@@ -121,6 +121,7 @@ func (d *decoder) DecodeTime() (time.Time, error) {
if t.IsZero() {
t = zero[time.Time]()
}
+
return t, nil
}
@@ -231,17 +232,21 @@ func (d *decoder) DecodeStore(s *store) error {
}
func (s *store) Snapshot(w io.WriteSeeker) error {
- s.Lock.Lock()
- defer s.Lock.Unlock()
+ s.Lock.RLock()
+ defer s.Lock.RUnlock()
if _, err := w.Seek(0, io.SeekStart); err != nil {
return err
}
wr := newEncoder(w)
- defer wr.Flush()
- return wr.EncodeStore(s)
+ err := wr.EncodeStore(s)
+ if err != nil {
+ return err
+ }
+
+ return wr.Flush()
}
func (s *store) LoadSnapshot(r io.ReadSeeker) error {
diff --git a/encoding_test.go b/encoding_test.go
index dadde12..43abb46 100644
--- a/encoding_test.go
+++ b/encoding_test.go
@@ -10,6 +10,8 @@ import (
)
func TestDecodeUint64Error(t *testing.T) {
+ t.Parallel()
+
buf := bytes.NewReader([]byte{0xFF})
decoder := newDecoder(buf)
@@ -21,6 +23,8 @@ func TestDecodeUint64Error(t *testing.T) {
}
func TestEncodeDecodeUint64(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
value uint64
@@ -32,6 +36,8 @@ func TestEncodeDecodeUint64(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
var buf bytes.Buffer
e := newEncoder(&buf)
@@ -58,6 +64,8 @@ func TestEncodeDecodeUint64(t *testing.T) {
}
func TestEncodeDecodeTime(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
value time.Time
@@ -69,12 +77,15 @@ func TestEncodeDecodeTime(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
var buf bytes.Buffer
e := newEncoder(&buf)
if err := e.EncodeTime(tt.value); err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if err := e.Flush(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -89,18 +100,20 @@ func TestEncodeDecodeTime(t *testing.T) {
if tt.value.Unix() != decodedValue.Unix() {
t.Errorf("expected %v, got %v", tt.value, decodedValue)
}
-
})
}
}
func TestDecodeBytesError(t *testing.T) {
+ t.Parallel()
+
var buf bytes.Buffer
e := newEncoder(&buf)
if err := e.EncodeBytes([]byte("DEADBEEF")); err != nil {
t.Errorf("unexpected error: %v", err)
}
+
if err := e.Flush(); err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -113,6 +126,8 @@ func TestDecodeBytesError(t *testing.T) {
}
func TestEncodeDecodeBytes(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
value []byte
@@ -124,12 +139,15 @@ func TestEncodeDecodeBytes(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
var buf bytes.Buffer
e := newEncoder(&buf)
if err := e.EncodeBytes(tt.value); err != nil {
t.Errorf("unexpected error: %v", err)
}
+
if err := e.Flush(); err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -149,6 +167,8 @@ func TestEncodeDecodeBytes(t *testing.T) {
}
func TestEncodeDecodeNode(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
value *node
@@ -187,6 +207,8 @@ func TestEncodeDecodeNode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
var buf bytes.Buffer
e := newEncoder(&buf)
@@ -208,15 +230,21 @@ func TestEncodeDecodeNode(t *testing.T) {
if tt.value.Hash != decodedValue.Hash {
t.Errorf("expected %v, got %v", tt.value.Hash, decodedValue.Hash)
}
- if !tt.value.Expiration.Equal(decodedValue.Expiration) && tt.value.Expiration.Sub(decodedValue.Expiration) > time.Second {
- t.Errorf("expected %v to be within %v of %v", decodedValue.Expiration, time.Second, tt.value.Expiration)
+
+ if !tt.value.Expiration.Equal(decodedValue.Expiration) &&
+ tt.value.Expiration.Sub(decodedValue.Expiration) > time.Second {
+ t.Errorf("expected %v to be within %v of %v",
+ decodedValue.Expiration, time.Second, tt.value.Expiration)
}
+
if tt.value.Access != decodedValue.Access {
t.Errorf("expected %v, got %v", tt.value.Access, decodedValue.Access)
}
+
if !bytes.Equal(tt.value.Key, decodedValue.Key) {
t.Errorf("expected %v, got %v", tt.value.Key, decodedValue.Key)
}
+
if !bytes.Equal(tt.value.Value, decodedValue.Value) {
t.Errorf("expected %v, got %v", tt.value.Value, decodedValue.Value)
}
@@ -225,6 +253,8 @@ func TestEncodeDecodeNode(t *testing.T) {
}
func TestEncodeDecodeStrorage(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
store map[string]string
@@ -264,6 +294,8 @@ func TestEncodeDecodeStrorage(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
var buf bytes.Buffer
e := newEncoder(&buf)
@@ -281,6 +313,7 @@ func TestEncodeDecodeStrorage(t *testing.T) {
if err := e.EncodeStore(want); err != nil {
t.Errorf("unexpected error: %v", err)
}
+
if err := e.Flush(); err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -295,9 +328,11 @@ func TestEncodeDecodeStrorage(t *testing.T) {
if want.MaxCost != got.MaxCost {
t.Errorf("expected %v, got %v", want.MaxCost, got.MaxCost)
}
+
if want.Length != got.Length {
t.Errorf("expected %v, got %v", want.Length, got.Length)
}
+
if want.Policy.Type != got.Policy.Type {
t.Errorf("expected %v, got %v", want.Policy.Type, got.Policy.Type)
}
@@ -314,6 +349,7 @@ func TestEncodeDecodeStrorage(t *testing.T) {
if !ok {
t.Fatalf("expected condition to be true")
}
+
if !bytes.Equal([]byte(v), gotVal) {
t.Fatalf("expected %v, got %v", []byte(v), gotVal)
}
@@ -322,14 +358,27 @@ func TestEncodeDecodeStrorage(t *testing.T) {
}
}
-func BenchmarkStoreLoadSnapshot(b *testing.B) {
- file, err := os.CreateTemp(b.TempDir(), "benchmark_test_")
+func createTestFile(tb testing.TB, pattern string) *os.File {
+ tb.Helper()
+
+ file, err := os.CreateTemp(tb.TempDir(), pattern)
if err != nil {
- b.Fatal(err)
+ tb.Fatal(err)
}
- defer os.Remove(file.Name())
- defer file.Close()
+ tb.Cleanup(func() {
+ if err := os.Remove(file.Name()); err != nil {
+ tb.Fatalf("unexpected error: %v", err)
+ }
+
+ _ = file.Close()
+ })
+
+ return file
+}
+
+func BenchmarkStoreSnapshot(b *testing.B) {
+ file := createTestFile(b, "benchmark_test_")
for n := 1; n <= 10000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
@@ -341,7 +390,7 @@ func BenchmarkStoreLoadSnapshot(b *testing.B) {
want.Set(buf, buf, 0)
}
- if err = want.Snapshot(file); err != nil {
+ if err := want.Snapshot(file); err != nil {
b.Fatalf("unexpected error: %v", err)
}
@@ -349,11 +398,10 @@ func BenchmarkStoreLoadSnapshot(b *testing.B) {
if err != nil {
b.Fatalf("unexpected error: %v", err)
}
+
b.SetBytes(int64(fileInfo.Size()))
b.ReportAllocs()
- b.ResetTimer()
-
for b.Loop() {
if err := want.Snapshot(file); err != nil {
b.Fatalf("unexpected error: %v", err)
@@ -363,15 +411,8 @@ func BenchmarkStoreLoadSnapshot(b *testing.B) {
}
}
-func BenchmarkStoreLoadSnapsot(b *testing.B) {
- file, err := os.CreateTemp(b.TempDir(), "benchmark_test_")
-
- if err != nil {
- b.Errorf("unexpected error: %v", err)
- }
-
- defer os.Remove(file.Name())
- defer file.Close()
+func BenchmarkStoreLoadSnapshot(b *testing.B) {
+ file := createTestFile(b, "benchmark_test_")
for n := 1; n <= 10000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
@@ -383,19 +424,21 @@ func BenchmarkStoreLoadSnapsot(b *testing.B) {
want.Set(buf, buf, 0)
}
- if err = want.Snapshot(file); err != nil {
+ if err := want.Snapshot(file); err != nil {
b.Fatalf("unexpected error: %v", err)
}
+
fileInfo, err := file.Stat()
if err != nil {
b.Fatalf("unexpected error: %v", err)
}
+
b.SetBytes(int64(fileInfo.Size()))
b.ReportAllocs()
- b.ResetTimer()
-
for b.Loop() {
+ want.Clear()
+
if err := want.LoadSnapshot(file); err != nil {
b.Fatalf("unexpected error: %v", err)
}
diff --git a/evict_test.go b/evict_test.go
index d771844..4a24a74 100644
--- a/evict_test.go
+++ b/evict_test.go
@@ -30,7 +30,9 @@ func createPolicy(tb testing.TB, policyType EvictionPolicyType, flag bool) evict
case PolicyLFU:
return &lfuPolicy{List: createSentinel(tb), Lock: &sync.RWMutex{}}
}
+
tb.Fatalf("unknown policy type: %v", policyType)
+
return nil
}
@@ -45,6 +47,7 @@ func getListOrder(tb testing.TB, evict *node) []*node {
for _, n := range order {
tb.Helper()
+
if n != n.EvictPrev.EvictNext {
tb.Fatalf("expected %#v, got %#v", n, n.EvictPrev.EvictNext)
}
@@ -286,7 +289,6 @@ func TestPolicyEvict(t *testing.T) {
actions: func(policy evictOrderedPolicy, nodes []*node) {
policy.OnInsert(nodes[0])
policy.OnInsert(nodes[1])
-
},
expected: func(nodes []*node) *node {
return nodes[0]
@@ -299,7 +301,6 @@ func TestPolicyEvict(t *testing.T) {
actions: func(policy evictOrderedPolicy, nodes []*node) {
policy.OnInsert(nodes[0])
policy.OnInsert(nodes[1])
-
},
expected: func(nodes []*node) *node {
return nil
@@ -326,7 +327,6 @@ func TestPolicyEvict(t *testing.T) {
actions: func(policy evictOrderedPolicy, nodes []*node) {
policy.OnInsert(nodes[0])
policy.OnInsert(nodes[1])
-
},
expected: func(nodes []*node) *node {
return nodes[0]
@@ -340,7 +340,6 @@ func TestPolicyEvict(t *testing.T) {
policy.OnInsert(nodes[1])
policy.OnAccess(nodes[0])
-
},
expected: func(nodes []*node) *node {
return nodes[1]
@@ -380,7 +379,6 @@ func TestPolicyEvict(t *testing.T) {
actions: func(policy evictOrderedPolicy, nodes []*node) {
policy.OnInsert(nodes[0])
policy.OnInsert(nodes[1])
-
},
expected: func(nodes []*node) *node {
return nodes[0]
@@ -394,7 +392,6 @@ func TestPolicyEvict(t *testing.T) {
policy.OnInsert(nodes[1])
policy.OnAccess(nodes[0])
-
},
expected: func(nodes []*node) *node {
return nodes[1]
@@ -628,6 +625,7 @@ func TestSetPolicyMultipleTimes(t *testing.T) {
if err != nil {
t.Errorf("expected no error, got %v", err)
}
+
if policy.Type != PolicyFIFO {
t.Errorf("expected policy type %v, got %v", PolicyFIFO, policy.Type)
}
@@ -637,6 +635,7 @@ func TestSetPolicyMultipleTimes(t *testing.T) {
if err != nil {
t.Errorf("expected no error, got %v", err)
}
+
if policy.Type != PolicyLRU {
t.Errorf("expected policy type %v, got %v", PolicyLRU, policy.Type)
}
@@ -646,6 +645,7 @@ func TestSetPolicyMultipleTimes(t *testing.T) {
if err != nil {
t.Errorf("expected no error, got %v", err)
}
+
if policy.Type != PolicyLFU {
t.Errorf("expected policy type %v, got %v", PolicyLFU, policy.Type)
}
@@ -655,6 +655,7 @@ func TestSetPolicyMultipleTimes(t *testing.T) {
if err != nil {
t.Errorf("expected no error, got %v", err)
}
+
if policy.Type != PolicyLTR {
t.Errorf("expected policy type %v, got %v", PolicyLTR, policy.Type)
}
@@ -664,6 +665,7 @@ func TestSetPolicyMultipleTimes(t *testing.T) {
if err != nil {
t.Errorf("expected no error, got %v", err)
}
+
if policy.Type != PolicyNone {
t.Errorf("expected policy type %v, got %v", PolicyNone, policy.Type)
}
diff --git a/examples/basic_usage/main.go b/examples/basic_usage/main.go
index 73e88ea..5b44cd9 100644
--- a/examples/basic_usage/main.go
+++ b/examples/basic_usage/main.go
@@ -14,11 +14,16 @@ func main() {
fmt.Println("Error:", err)
return
}
- defer db.Close()
+
+ defer func() {
+ err := db.Close()
+ if err != nil {
+ fmt.Println("Error:", err)
+ }
+ }()
// Set a value with a TTL of 5 seconds
- err = db.Set("key1", "value1", 5*time.Second)
- if err != nil {
+ if err = db.Set("key1", "value1", 5*time.Second); err != nil {
fmt.Println("Set Error:", err)
return
}
@@ -33,6 +38,7 @@ func main() {
// Wait for 6 seconds and try to get the value again
time.Sleep(6 * time.Second)
+
value, ttl, err = db.GetValue("key1")
if err != nil {
fmt.Println("Get Error after TTL:", err)
diff --git a/examples/eviction_policy/main.go b/examples/eviction_policy/main.go
index 5b5599a..fa56b8f 100644
--- a/examples/eviction_policy/main.go
+++ b/examples/eviction_policy/main.go
@@ -2,30 +2,65 @@ package main
import (
"fmt"
+ "os"
+ "time"
"github.com/marcthe12/cache"
)
func main() {
// Create an in-memory cache with LRU eviction policy
- db, err := cache.OpenMem[string, string](cache.WithPolicy(cache.PolicyLRU))
+ db, err := cache.OpenMem[string, string](
+ cache.WithPolicy(cache.PolicyLRU),
+ cache.WithMaxCost(30),
+ cache.SetCleanupTime(1*time.Second),
+ )
if err != nil {
fmt.Println("Error:", err)
- return
+ os.Exit(1)
}
- defer db.Close()
+
+ defer func() {
+ err := db.Close()
+ if err != nil {
+ fmt.Println("Error:", err)
+ }
+ }()
// Set values
- db.Set("key1", "value1", 0)
- db.Set("key2", "value2", 0)
- db.Set("key3", "value3", 0)
+ if err := db.Set("key1", "value1", 0); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ if err := db.Set("key2", "value2", 0); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ if err := db.Set("key3", "value3", 0); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
// Access some keys
- db.GetValue("key1")
- db.GetValue("key2")
+ if _, _, err := db.GetValue("key1"); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ if _, _, err := db.GetValue("key2"); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
// Add another key to trigger eviction
- db.Set("key4", "value4", 0)
+ if err := db.Set("key4", "value4", 0); err != nil {
+ fmt.Println("Error:", err)
+ os.Exit(1)
+ }
+
+ time.Sleep(2 * time.Second)
// Check which keys are present
for _, key := range []string{"key1", "key2", "key3", "key4"} {
diff --git a/go.mod b/go.mod
index 0f17cbf..58abdf7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/marcthe12/cache
go 1.24.0
require (
- github.com/rogpeppe/go-internal v1.14.0
+ github.com/rogpeppe/go-internal v1.14.1
github.com/vmihailenco/msgpack/v5 v5.4.1
)
diff --git a/go.sum b/go.sum
index 48b0668..e3d5b64 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.14.0 h1:unbRd941gNa8SS77YznHXOYVBDgWcF9xhzECdm8juZc=
-github.com/rogpeppe/go-internal v1.14.0/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
diff --git a/internal/pausedtimer/timer_test.go b/internal/pausedtimer/timer_test.go
index 924c07c..5c54133 100644
--- a/internal/pausedtimer/timer_test.go
+++ b/internal/pausedtimer/timer_test.go
@@ -6,35 +6,47 @@ import (
)
func TestNew(t *testing.T) {
+ t.Parallel()
+
d := 1 * time.Second
timer := New(d)
+
if timer.duration != d {
t.Errorf("expected duration %#v, got %v", d, timer.duration)
}
+
if timer.Ticker == nil {
t.Error("expected Ticker to be non-nil")
}
}
func TestPauseTimerZeroDuration(t *testing.T) {
+ t.Parallel()
+
timer := New(0)
if timer.GetDuration() != 0 {
t.Errorf("expected duration %v, got %v", time.Duration(0), timer.GetDuration())
}
+
if timer.Ticker == nil {
t.Error("expected Ticker to be non-nil")
}
}
func TestPauseTimerResetToZero(t *testing.T) {
+ t.Parallel()
+
timer := New(1 * time.Second)
timer.Reset(0)
+
if timer.GetDuration() != 0 {
t.Errorf("expected duration %v, got %v", time.Duration(0), timer.GetDuration())
}
}
func TestPauseTimerPauseAndResume(t *testing.T) {
+ t.Parallel()
+
d := 1 * time.Second
timer := New(d)
timer.Stop() // Simulate pause
@@ -51,27 +63,36 @@ func TestPauseTimerPauseAndResume(t *testing.T) {
}
func TestPauseTimerReset(t *testing.T) {
+ t.Parallel()
+
d := 1 * time.Second
timer := New(d)
got := 2 * time.Second
timer.Reset(got)
+
if timer.duration != got {
t.Errorf("expected duration %v, got %v", got, timer.duration)
}
}
func TestPauseTimerResume(t *testing.T) {
+ t.Parallel()
+
d := 1 * time.Second
timer := NewStopped(d)
timer.Resume()
+
if timer.duration != d {
t.Errorf("expected duration %v, got %v", d, timer.duration)
}
}
func TestPauseTimerGetDuration(t *testing.T) {
+ t.Parallel()
+
d := 1 * time.Second
timer := New(d)
+
if timer.GetDuration() != d {
t.Errorf("expected duration %v, got %v", d, timer.GetDuration())
}
diff --git a/store.go b/store.go
index 20241ac..aa44dc4 100644
--- a/store.go
+++ b/store.go
@@ -10,7 +10,7 @@ import (
const (
initialBucketSize uint64 = 8
- loadFactor float64 = 0.75
+ loadFactor float64 = 0.9
)
// node represents an entry in the cache with metadata for eviction and expiration.
@@ -142,8 +142,6 @@ func (s *store) Get(key []byte) ([]byte, time.Duration, bool) {
v, _, _ := s.lookup(key)
if v != nil {
if !v.IsValid() {
- //deleteNode(s, v)
-
return nil, 0, false
}
@@ -196,9 +194,11 @@ func (s *store) Cleanup() {
for v := s.EvictList.EvictNext; v != &s.EvictList; {
n := v.EvictNext
+
if !v.IsValid() {
deleteNode(s, v)
}
+
v = n
}
}
@@ -220,6 +220,7 @@ func (s *store) Evict() bool {
if n == nil {
break
}
+
deleteNode(s, n)
}
@@ -231,7 +232,7 @@ func (s *store) insert(key []byte, value []byte, ttl time.Duration) {
idx, hash := lookupIdx(s, key)
bucket := &s.Bucket[idx]
- if float64(s.Length)/float64(len(s.Bucket)) > float64(loadFactor) {
+ if float64(s.Length) > loadFactor*float64(len(s.Bucket)) {
s.Resize()
// resize may invalidate pointer to bucket
idx, _ = lookupIdx(s, key)
@@ -270,14 +271,17 @@ func (s *store) Set(key []byte, value []byte, ttl time.Duration) {
v, _, _ := s.lookup(key)
if v != nil {
cost := v.Cost()
+
v.Value = value
if ttl != 0 {
v.Expiration = time.Now().Add(ttl)
} else {
v.Expiration = zero[time.Time]()
}
+
s.Cost = s.Cost + v.Cost() - cost
s.Policy.OnUpdate(v)
+
return
}
@@ -330,12 +334,14 @@ func (s *store) UpdateInPlace(key []byte, processFunc func([]byte) ([]byte, erro
}
cost := v.Cost()
+
v.Value = value
if ttl != 0 {
v.Expiration = time.Now().Add(ttl)
} else {
v.Expiration = zero[time.Time]()
}
+
s.Cost = s.Cost + v.Cost() - cost
s.Policy.OnUpdate(v)
@@ -360,5 +366,6 @@ func (s *store) Memorize(key []byte, factory func() ([]byte, error), ttl time.Du
}
s.insert(key, value, ttl)
+
return value, nil
}
diff --git a/store_test.go b/store_test.go
index 4b9d2f3..2dc8a87 100644
--- a/store_test.go
+++ b/store_test.go
@@ -19,14 +19,19 @@ func setupTestStore(tb testing.TB) *store {
}
func TestNodeIsValid(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
node *node
isValid bool
}{
{
- name: "Valid node with non-zero expiration",
- node: &node{Key: []byte("key1"), Value: []byte("value1"), Expiration: time.Now().Add(10 * time.Minute)},
+ name: "Valid node with non-zero expiration",
+ node: &node{
+ Key: []byte("key1"), Value: []byte("value1"),
+ Expiration: time.Now().Add(10 * time.Minute),
+ },
isValid: true,
},
{
@@ -35,14 +40,19 @@ func TestNodeIsValid(t *testing.T) {
isValid: true,
},
{
- name: "Expired node",
- node: &node{Key: []byte("key3"), Value: []byte("value3"), Expiration: time.Now().Add(-1 * time.Minute)},
+ name: "Expired node",
+ node: &node{
+ Key: []byte("key3"), Value: []byte("value3"),
+ Expiration: time.Now().Add(-1 * time.Minute),
+ },
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
if got := tt.node.IsValid(); got != tt.isValid {
t.Errorf("IsValid() = %v, want %v", got, tt.isValid)
}
@@ -51,6 +61,8 @@ func TestNodeIsValid(t *testing.T) {
}
func TestNodeTTL(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
node *node
@@ -58,8 +70,11 @@ func TestNodeTTL(t *testing.T) {
}{
{
name: "Node with non-zero expiration",
- node: &node{Key: []byte("key1"), Value: []byte("value1"), Expiration: time.Now().Add(10 * time.Minute)},
- ttl: 10 * time.Minute,
+ node: &node{
+ Key: []byte("key1"), Value: []byte("value1"),
+ Expiration: time.Now().Add(10 * time.Minute),
+ },
+ ttl: 10 * time.Minute,
},
{
name: "Node with zero expiration",
@@ -68,13 +83,18 @@ func TestNodeTTL(t *testing.T) {
},
{
name: "Expired node",
- node: &node{Key: []byte("key3"), Value: []byte("value3"), Expiration: time.Now().Add(-1 * time.Minute)},
- ttl: -1 * time.Minute, // Should be negative or 0, depending on the implementation
+ node: &node{
+ Key: []byte("key3"), Value: []byte("value3"),
+ Expiration: time.Now().Add(-1 * time.Minute),
+ },
+ ttl: -1 * time.Minute, // Should be negative or 0, depending on the implementation
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
if got := tt.node.TTL().Round(time.Second); got != tt.ttl {
t.Errorf("TTL() = %v, want %v", got, tt.ttl)
}
@@ -83,6 +103,8 @@ func TestNodeTTL(t *testing.T) {
}
func TestNodeCost(t *testing.T) {
+ t.Parallel()
+
tests := []struct {
name string
node *node
@@ -112,6 +134,8 @@ func TestNodeCost(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
if got := tt.node.Cost(); got != tt.cost {
t.Errorf("Cost() = %v, want %v", got, tt.cost)
}
@@ -129,17 +153,19 @@ func TestStoreGetSet(t *testing.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) {
@@ -149,17 +175,19 @@ func TestStoreGetSet(t *testing.T) {
want := []byte("Value")
store.Set([]byte("Key"), want, 1*time.Hour)
+
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) != 1*time.Hour {
t.Errorf("ttl same: got %v expected %v", ttl.Round(time.Second), 1*time.Hour)
}
-
})
t.Run("Exists TTL", func(t *testing.T) {
@@ -169,6 +197,7 @@ func TestStoreGetSet(t *testing.T) {
want := []byte("Value")
store.Set([]byte("Key"), want, time.Nanosecond)
+
if _, _, ok := store.Get([]byte("Key")); ok {
t.Errorf("expected key to not exist")
}
@@ -192,14 +221,15 @@ 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)
}
-
})
t.Run("Resize", func(t *testing.T) {
@@ -207,7 +237,9 @@ func TestStoreGetSet(t *testing.T) {
store := setupTestStore(t)
- for i := range initialBucketSize {
+ size := uint64(float64(len(store.Bucket))*loadFactor + 1)
+
+ for i := range size + 1 {
key := binary.LittleEndian.AppendUint64(nil, i)
store.Set(key, key, 0)
}
@@ -220,7 +252,7 @@ func TestStoreGetSet(t *testing.T) {
}
if len(store.Bucket) != int(initialBucketSize)*2 {
- t.Errorf("expected bucket size to be %v, got %v", initialBucketSize*2, len(store.Bucket))
+ t.Errorf("expected bucket size to be %v, got %v", size*2, len(store.Bucket))
}
for i := range store.Length {
@@ -246,6 +278,7 @@ func TestStoreDelete(t *testing.T) {
if !store.Delete([]byte("Key")) {
t.Errorf("expected key to be deleted")
}
+
if _, _, ok := store.Get([]byte("Key")); ok {
t.Errorf("expected key to not exist")
}
@@ -270,6 +303,7 @@ func TestStoreClear(t *testing.T) {
want := []byte("Value")
store.Set([]byte("Key"), want, 0)
store.Clear()
+
if _, _, ok := store.Get([]byte("Key")); ok {
t.Errorf("expected key to not exist")
}
@@ -284,6 +318,7 @@ func TestStoreUpdateInPlace(t *testing.T) {
store := setupTestStore(t)
want := []byte("Value")
+
store.Set([]byte("Key"), []byte("Initial"), 1*time.Hour)
processFunc := func(v []byte) ([]byte, error) {
@@ -298,6 +333,7 @@ func TestStoreUpdateInPlace(t *testing.T) {
if !ok {
t.Fatalf("expected key to exist")
}
+
if !bytes.Equal(want, got) {
t.Errorf("got %v, want %v", got, want)
}
@@ -334,6 +370,7 @@ func TestStoreMemoize(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if !bytes.Equal(got, []byte("Value")) {
t.Fatalf("expected: %v, got: %v", "Value", got)
}
@@ -342,6 +379,7 @@ func TestStoreMemoize(t *testing.T) {
if !ok {
t.Fatalf("expected key to exist")
}
+
if !bytes.Equal(got, []byte("Value")) {
t.Fatalf("expected: %v, got: %v", "Value", got)
}
@@ -362,6 +400,7 @@ func TestStoreMemoize(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
+
if !bytes.Equal(got, []byte("Value")) {
t.Fatalf("expected: %v, got: %v", "Value", got)
}
@@ -444,6 +483,7 @@ func TestStoreEvict(t *testing.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)
@@ -469,6 +509,7 @@ func TestStoreEvict(t *testing.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)
@@ -494,6 +535,7 @@ func TestStoreEvict(t *testing.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)
@@ -521,7 +563,7 @@ func BenchmarkStoreGet(b *testing.B) {
}
for k, v := range policy {
b.Run(k, func(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
want := setupTestStore(b)
@@ -539,8 +581,6 @@ func BenchmarkStoreGet(b *testing.B) {
want.Set(key, []byte("Store"), 0)
b.ReportAllocs()
- b.ResetTimer()
-
for b.Loop() {
want.Get(key)
}
@@ -560,7 +600,7 @@ func BenchmarkStoreGetParallel(b *testing.B) {
}
for k, v := range policy {
b.Run(k, func(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
want := setupTestStore(b)
@@ -578,8 +618,6 @@ func BenchmarkStoreGetParallel(b *testing.B) {
want.Set(key, []byte("Store"), 0)
b.ReportAllocs()
- b.ResetTimer()
-
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
want.Get(key)
@@ -601,7 +639,7 @@ func BenchmarkStoreSet(b *testing.B) {
}
for k, v := range policy {
b.Run(k, func(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
want := setupTestStore(b)
@@ -619,7 +657,6 @@ func BenchmarkStoreSet(b *testing.B) {
store := []byte("Store")
b.ReportAllocs()
- b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@@ -642,7 +679,7 @@ func BenchmarkStoreSetParallel(b *testing.B) {
}
for k, v := range policy {
b.Run(k, func(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
want := setupTestStore(b)
@@ -660,7 +697,6 @@ func BenchmarkStoreSetParallel(b *testing.B) {
store := []byte("Store")
b.ReportAllocs()
- b.ResetTimer()
for b.Loop() {
want.Set(key, store, 0)
@@ -681,7 +717,7 @@ func BenchmarkStoreSetInsert(b *testing.B) {
}
for k, v := range policy {
b.Run(k, func(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
want := setupTestStore(b)
@@ -690,6 +726,7 @@ func BenchmarkStoreSetInsert(b *testing.B) {
}
list := make([][]byte, n)
+
for i := range n {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, uint64(i))
@@ -697,12 +734,13 @@ func BenchmarkStoreSetInsert(b *testing.B) {
}
b.ReportAllocs()
- b.ResetTimer()
for b.Loop() {
for _, k := range list {
want.Set(k, k, 0)
}
+
+ want.Clear()
}
})
}
@@ -711,7 +749,7 @@ func BenchmarkStoreSetInsert(b *testing.B) {
}
func BenchmarkStoreDelete(b *testing.B) {
- for n := 1; n <= 10000; n *= 10 {
+ for n := 1; n <= 100000; n *= 10 {
b.Run(strconv.Itoa(n), func(b *testing.B) {
want := setupTestStore(b)
@@ -725,7 +763,6 @@ func BenchmarkStoreDelete(b *testing.B) {
store := []byte("Store")
b.ReportAllocs()
- b.ResetTimer()
for b.Loop() {
want.Set(key, store, 0)