tests: cache
This commit is contained in:
parent
92732f9682
commit
597feb7b54
8 changed files with 822 additions and 32 deletions
285
cache/memory_test.go
vendored
Normal file
285
cache/memory_test.go
vendored
Normal file
|
@ -0,0 +1,285 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.nakama.town/fmartingr/gotoolkit/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewMemoryCache(t *testing.T) {
|
||||
cache := NewMemoryCache()
|
||||
require.NotNil(t, cache, "NewMemoryCache returned nil")
|
||||
require.NotNil(t, cache.data, "Cache data map was not initialized")
|
||||
}
|
||||
|
||||
func TestMemoryCache_Set(t *testing.T) {
|
||||
cache := NewMemoryCache()
|
||||
|
||||
// Test basic set
|
||||
err := cache.Set("key1", "value1")
|
||||
require.NoError(t, err, "Set returned unexpected error")
|
||||
|
||||
// Verify the item was stored correctly
|
||||
cache.dataMu.RLock()
|
||||
item, exists := cache.data["key1"]
|
||||
cache.dataMu.RUnlock()
|
||||
require.True(t, exists, "Set did not store the item in the cache")
|
||||
assert.Equal(t, "key1", item.Key, "Wrong key stored")
|
||||
assert.Equal(t, "value1", item.Value, "Wrong value stored")
|
||||
assert.Nil(t, item.TTL, "TTL should be nil")
|
||||
|
||||
// Test set with TTL
|
||||
withTTL := func(item *model.CacheItem) {
|
||||
expiry := time.Now().Add(10 * time.Minute)
|
||||
item.TTL = &expiry
|
||||
}
|
||||
|
||||
err = cache.Set("key2", "value2", withTTL)
|
||||
require.NoError(t, err, "Set with TTL returned unexpected error")
|
||||
|
||||
// Verify the item with TTL was stored correctly
|
||||
cache.dataMu.RLock()
|
||||
itemWithTTL, exists := cache.data["key2"]
|
||||
cache.dataMu.RUnlock()
|
||||
require.True(t, exists, "Set did not store the item with TTL in the cache")
|
||||
assert.NotNil(t, itemWithTTL.TTL, "TTL option was not applied")
|
||||
}
|
||||
|
||||
func TestMemoryCache_SetTableDriven(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
value interface{}
|
||||
ttl *time.Duration
|
||||
}{
|
||||
{
|
||||
name: "string value without TTL",
|
||||
key: "string-key",
|
||||
value: "string-value",
|
||||
ttl: nil,
|
||||
},
|
||||
{
|
||||
name: "int value without TTL",
|
||||
key: "int-key",
|
||||
value: 42,
|
||||
ttl: nil,
|
||||
},
|
||||
{
|
||||
name: "struct value without TTL",
|
||||
key: "struct-key",
|
||||
value: struct{ Name string }{"test"},
|
||||
ttl: nil,
|
||||
},
|
||||
{
|
||||
name: "string value with TTL",
|
||||
key: "string-key-ttl",
|
||||
value: "string-value-ttl",
|
||||
ttl: ptrDuration(5 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cache := NewMemoryCache()
|
||||
|
||||
var opts []model.CacheOption
|
||||
if tt.ttl != nil {
|
||||
opts = append(opts, func(item *model.CacheItem) {
|
||||
expiry := time.Now().Add(*tt.ttl)
|
||||
item.TTL = &expiry
|
||||
})
|
||||
}
|
||||
|
||||
err := cache.Set(tt.key, tt.value, opts...)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cache.dataMu.RLock()
|
||||
item, exists := cache.data[tt.key]
|
||||
cache.dataMu.RUnlock()
|
||||
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, tt.key, item.Key)
|
||||
assert.Equal(t, tt.value, item.Value)
|
||||
|
||||
if tt.ttl != nil {
|
||||
assert.NotNil(t, item.TTL)
|
||||
} else {
|
||||
assert.Nil(t, item.TTL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryCache_Get(t *testing.T) {
|
||||
cache := NewMemoryCache()
|
||||
|
||||
// Test getting non-existent key
|
||||
_, err := cache.Get("nonexistent")
|
||||
assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Wrong error returned for non-existent key")
|
||||
|
||||
// Test getting existing key
|
||||
require.NoError(t, cache.Set("key1", "value1"))
|
||||
value, err := cache.Get("key1")
|
||||
require.NoError(t, err, "Get returned unexpected error")
|
||||
assert.Equal(t, "value1", value, "Wrong value returned")
|
||||
|
||||
// Test getting expired key
|
||||
expiredTTL := func(item *model.CacheItem) {
|
||||
expiry := time.Now().Add(-1 * time.Minute) // Past time
|
||||
item.TTL = &expiry
|
||||
}
|
||||
require.NoError(t, cache.Set("expired", "value", expiredTTL))
|
||||
|
||||
_, err = cache.Get("expired")
|
||||
assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Wrong error returned for expired item")
|
||||
|
||||
// Verify the expired key was deleted
|
||||
cache.dataMu.RLock()
|
||||
_, exists := cache.data["expired"]
|
||||
cache.dataMu.RUnlock()
|
||||
assert.False(t, exists, "Expired item was not deleted during Get")
|
||||
}
|
||||
|
||||
func TestMemoryCache_GetTableDriven(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupKey string
|
||||
setupValue any
|
||||
setupTTL *time.Duration
|
||||
getKey string
|
||||
expectValue any
|
||||
expectError error
|
||||
}{
|
||||
{
|
||||
name: "get existing key",
|
||||
setupKey: "test-key",
|
||||
setupValue: "test-value",
|
||||
getKey: "test-key",
|
||||
expectValue: "test-value",
|
||||
expectError: nil,
|
||||
},
|
||||
{
|
||||
name: "get non-existent key",
|
||||
setupKey: "test-key",
|
||||
setupValue: "test-value",
|
||||
getKey: "different-key",
|
||||
expectValue: nil,
|
||||
expectError: model.ErrCacheKeyDontExist,
|
||||
},
|
||||
{
|
||||
name: "get expired key",
|
||||
setupKey: "expired-key",
|
||||
setupValue: "expired-value",
|
||||
setupTTL: ptrDuration(-1 * time.Minute), // Negative to create an expired key
|
||||
getKey: "expired-key",
|
||||
expectValue: nil,
|
||||
expectError: model.ErrCacheKeyDontExist,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cache := NewMemoryCache()
|
||||
|
||||
// Setup
|
||||
var opts []model.CacheOption
|
||||
if tt.setupTTL != nil {
|
||||
opts = append(opts, func(item *model.CacheItem) {
|
||||
expiry := time.Now().Add(*tt.setupTTL)
|
||||
item.TTL = &expiry
|
||||
})
|
||||
}
|
||||
|
||||
if tt.setupKey != "" {
|
||||
err := cache.Set(tt.setupKey, tt.setupValue, opts...)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Test
|
||||
value, err := cache.Get(tt.getKey)
|
||||
|
||||
if tt.expectError != nil {
|
||||
assert.ErrorIs(t, err, tt.expectError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectValue, value)
|
||||
|
||||
// If it was an expired key, ensure it's deleted
|
||||
if tt.setupTTL != nil && *tt.setupTTL < 0 {
|
||||
cache.dataMu.RLock()
|
||||
_, exists := cache.data[tt.setupKey]
|
||||
cache.dataMu.RUnlock()
|
||||
assert.False(t, exists, "Expired item was not deleted during Get")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryCache_Delete(t *testing.T) {
|
||||
cache := NewMemoryCache()
|
||||
|
||||
// Setup test data
|
||||
require.NoError(t, cache.Set("key1", "value1"))
|
||||
|
||||
// Test deleting existing key
|
||||
err := cache.Delete("key1")
|
||||
require.NoError(t, err, "Delete returned unexpected error")
|
||||
|
||||
// Verify the key was deleted
|
||||
cache.dataMu.RLock()
|
||||
_, exists := cache.data["key1"]
|
||||
cache.dataMu.RUnlock()
|
||||
assert.False(t, exists, "Item was not deleted from cache")
|
||||
|
||||
// Test deleting non-existent key (should not error)
|
||||
err = cache.Delete("nonexistent")
|
||||
assert.NoError(t, err, "Delete on non-existent key returned error")
|
||||
}
|
||||
|
||||
func TestMemoryCache_Concurrency(t *testing.T) {
|
||||
cache := NewMemoryCache()
|
||||
const goroutines = 10
|
||||
const operations = 100
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(goroutines * 2)
|
||||
|
||||
// Writers
|
||||
for i := range goroutines {
|
||||
go func(id int) {
|
||||
for j := range operations {
|
||||
key := "key" + string(rune('A'+id)) + string(rune('0'+j%10))
|
||||
err := cache.Set(key, j)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Readers
|
||||
for i := range goroutines {
|
||||
go func(id int) {
|
||||
for j := range operations {
|
||||
key := "key" + string(rune('A'+id)) + string(rune('0'+j%10))
|
||||
_, _ = cache.Get(key) // We ignore errors here as keys might not exist yet
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to complete
|
||||
wg.Wait()
|
||||
|
||||
// If we reached here without deadlock or panic, the test passes
|
||||
}
|
||||
|
||||
// Helper function to create duration pointers
|
||||
func ptrDuration(d time.Duration) *time.Duration {
|
||||
return &d
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue