mirror of
https://github.com/makayabou/asg-server.git
synced 2026-05-02 17:43:36 +02:00
392 lines
8.9 KiB
Go
392 lines
8.9 KiB
Go
package cache_test
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/android-sms-gateway/server/pkg/cache"
|
|
)
|
|
|
|
func TestMemoryCache_ZeroTTL(t *testing.T) {
|
|
// Test cache with zero TTL (no expiration)
|
|
cache := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
key := "zero-ttl-key"
|
|
value := "zero-ttl-value"
|
|
|
|
err := cache.Set(ctx, key, value)
|
|
if err != nil {
|
|
t.Fatalf("Set failed: %v", err)
|
|
}
|
|
|
|
// Wait some time to ensure no expiration
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
retrieved, err := cache.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("Get failed: %v", err)
|
|
}
|
|
|
|
if retrieved != value {
|
|
t.Errorf("Expected %s, got %s", value, retrieved)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_ImmediateExpiration(t *testing.T) {
|
|
// Test c with very short TTL
|
|
c := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
key := "expiring-key"
|
|
value := "expiring-value"
|
|
ttl := 1 * time.Millisecond
|
|
|
|
err := c.Set(ctx, key, value, cache.WithTTL(ttl))
|
|
if err != nil {
|
|
t.Fatalf("Set failed: %v", err)
|
|
}
|
|
|
|
// Wait for expiration
|
|
time.Sleep(2 * ttl)
|
|
|
|
_, err = c.Get(ctx, key)
|
|
if err != cache.ErrKeyExpired {
|
|
t.Errorf("Expected ErrKeyExpired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_NilContext(t *testing.T) {
|
|
// Test cache operations with nil context
|
|
cache := cache.NewMemory(0)
|
|
key := "nil-context-key"
|
|
value := "nil-context-value"
|
|
|
|
err := cache.Set(nil, key, value)
|
|
if err != nil {
|
|
t.Fatalf("Set with nil context failed: %v", err)
|
|
}
|
|
|
|
retrieved, err := cache.Get(nil, key)
|
|
if err != nil {
|
|
t.Fatalf("Get with nil context failed: %v", err)
|
|
}
|
|
|
|
if retrieved != value {
|
|
t.Errorf("Expected %s, got %s", value, retrieved)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_EmptyKey(t *testing.T) {
|
|
// Test cache operations with empty key
|
|
cache := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
key := ""
|
|
value := "empty-key-value"
|
|
|
|
err := cache.Set(ctx, key, value)
|
|
if err != nil {
|
|
t.Fatalf("Set with empty key failed: %v", err)
|
|
}
|
|
|
|
retrieved, err := cache.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("Get with empty key failed: %v", err)
|
|
}
|
|
|
|
if retrieved != value {
|
|
t.Errorf("Expected %s, got %s", value, retrieved)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_OverwriteWithDifferentTTL(t *testing.T) {
|
|
// Test overwriting a key with different TTL
|
|
c := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
key := "ttl-key"
|
|
value1 := "value1"
|
|
value2 := "value2"
|
|
|
|
// Set with short TTL
|
|
err := c.Set(ctx, key, value1, cache.WithTTL(100*time.Millisecond))
|
|
if err != nil {
|
|
t.Fatalf("Set failed: %v", err)
|
|
}
|
|
|
|
// Overwrite with longer TTL
|
|
err = c.Set(ctx, key, value2, cache.WithTTL(1*time.Second))
|
|
if err != nil {
|
|
t.Fatalf("Set overwrite failed: %v", err)
|
|
}
|
|
|
|
retrieved, err := c.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("Get failed: %v", err)
|
|
}
|
|
|
|
if retrieved != value2 {
|
|
t.Errorf("Expected %s, got %s", value2, retrieved)
|
|
}
|
|
|
|
// Wait for short TTL to expire but not long TTL
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
retrieved, err = c.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("Get after partial wait failed: %v", err)
|
|
}
|
|
|
|
if retrieved != value2 {
|
|
t.Errorf("Expected %s after partial wait, got %s", value2, retrieved)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_MixedTTLScenarios(t *testing.T) {
|
|
// Test various TTL scenarios
|
|
c := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
// Set multiple keys with different TTLs
|
|
keys := map[string]time.Duration{
|
|
"no-ttl": 0,
|
|
"short-ttl": 50 * time.Millisecond,
|
|
"medium-ttl": 200 * time.Millisecond,
|
|
"long-ttl": 500 * time.Millisecond,
|
|
}
|
|
|
|
for key, ttl := range keys {
|
|
value := "value-" + key
|
|
var err error
|
|
if ttl > 0 {
|
|
err = c.Set(ctx, key, value, cache.WithTTL(ttl))
|
|
} else {
|
|
err = c.Set(ctx, key, value)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Set %s failed: %v", key, err)
|
|
}
|
|
}
|
|
|
|
// Verify all keys are present initially
|
|
for key := range keys {
|
|
_, err := c.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("Get %s failed: %v", key, err)
|
|
}
|
|
}
|
|
|
|
// Wait for short TTL to expire
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Short TTL key should be expired, others should still be there
|
|
_, err := c.Get(ctx, "short-ttl")
|
|
if err != cache.ErrKeyExpired {
|
|
t.Errorf("Expected ErrKeyExpired for short-ttl, got %v", err)
|
|
}
|
|
|
|
for key := range keys {
|
|
if key == "short-ttl" {
|
|
continue
|
|
}
|
|
_, err := c.Get(ctx, key)
|
|
if err != nil {
|
|
t.Errorf("Get %s failed: %v", key, err)
|
|
}
|
|
}
|
|
|
|
// Wait for medium TTL to expire
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Medium TTL key should be expired, others should still be there
|
|
_, err = c.Get(ctx, "medium-ttl")
|
|
if err != cache.ErrKeyExpired {
|
|
t.Errorf("Expected ErrKeyExpired for medium-ttl, got %v", err)
|
|
}
|
|
|
|
for key := range keys {
|
|
if key == "short-ttl" || key == "medium-ttl" {
|
|
continue
|
|
}
|
|
_, err := c.Get(ctx, key)
|
|
if err != nil {
|
|
t.Errorf("Get %s failed: %v", key, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_RapidOperations(t *testing.T) {
|
|
// Test rapid c operations
|
|
c := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
const numOperations = 1000
|
|
const duration = 100 * time.Millisecond
|
|
|
|
start := time.Now()
|
|
opsCompleted := 0
|
|
|
|
for i := range numOperations {
|
|
// Alternate between set and get
|
|
if i%2 == 0 {
|
|
key := "rapid-key-" + strconv.Itoa(i)
|
|
value := "rapid-value-" + strconv.Itoa(i)
|
|
err := c.Set(ctx, key, value)
|
|
if err != nil {
|
|
t.Errorf("Set failed: %v", err)
|
|
}
|
|
} else {
|
|
key := "rapid-key-" + strconv.Itoa(i-1)
|
|
_, err := c.Get(ctx, key)
|
|
if err != nil && err != cache.ErrKeyNotFound {
|
|
t.Errorf("Get failed: %v", err)
|
|
}
|
|
}
|
|
opsCompleted++
|
|
}
|
|
|
|
durationTaken := time.Since(start)
|
|
t.Logf("Completed %d operations in %v (%.2f ops/ms)", opsCompleted, durationTaken, float64(opsCompleted)/float64(durationTaken.Milliseconds()))
|
|
|
|
// Verify operations completed within reasonable time
|
|
if durationTaken > 2*duration {
|
|
t.Errorf("Operations took too long: %v", durationTaken)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_CleanupOnEmptyCache(t *testing.T) {
|
|
// Test cleanup operation on empty cache
|
|
cache := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
err := cache.Cleanup(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Cleanup failed: %v", err)
|
|
}
|
|
|
|
// Should still work normally after cleanup
|
|
key := "post-cleanup-key"
|
|
value := "post-cleanup-value"
|
|
|
|
err = cache.Set(ctx, key, value)
|
|
if err != nil {
|
|
t.Fatalf("Set after cleanup failed: %v", err)
|
|
}
|
|
|
|
retrieved, err := cache.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("Get after cleanup failed: %v", err)
|
|
}
|
|
|
|
if retrieved != value {
|
|
t.Errorf("Expected %s, got %s", value, retrieved)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_DrainWithExpiredItems(t *testing.T) {
|
|
// Test drain operation with mix of expired and non-expired items
|
|
c := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
// Set non-expired item
|
|
err := c.Set(ctx, "valid-key", "valid-value")
|
|
if err != nil {
|
|
t.Fatalf("Set valid key failed: %v", err)
|
|
}
|
|
|
|
// Set expired item
|
|
err = c.Set(ctx, "expired-key", "expired-value", cache.WithTTL(1*time.Millisecond))
|
|
if err != nil {
|
|
t.Fatalf("Set expired key failed: %v", err)
|
|
}
|
|
|
|
// Wait for expiration
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Drain should only return non-expired items
|
|
items, err := c.Drain(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Drain failed: %v", err)
|
|
}
|
|
|
|
if len(items) != 1 {
|
|
t.Errorf("Expected 1 item in drain result, got %d", len(items))
|
|
}
|
|
|
|
if items["valid-key"] != "valid-value" {
|
|
t.Errorf("Expected valid-value, got %s", items["valid-key"])
|
|
}
|
|
|
|
// Verify expired item is gone (should be completely removed, not just expired)
|
|
_, err = c.Get(ctx, "expired-key")
|
|
if err != cache.ErrKeyNotFound {
|
|
t.Errorf("Expected ErrKeyNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_ExtremeKeyLength(t *testing.T) {
|
|
// Test with very long keys
|
|
cache := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
// Create a very long key (1KB)
|
|
longKey := strings.Repeat("a", 1024)
|
|
value := "extreme-key-value"
|
|
|
|
err := cache.Set(ctx, longKey, value)
|
|
if err != nil {
|
|
t.Fatalf("Set with long key failed: %v", err)
|
|
}
|
|
|
|
retrieved, err := cache.Get(ctx, longKey)
|
|
if err != nil {
|
|
t.Fatalf("Get with long key failed: %v", err)
|
|
}
|
|
|
|
if retrieved != value {
|
|
t.Errorf("Expected %s, got %s", value, retrieved)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCache_RaceConditionWithExpiration(t *testing.T) {
|
|
// Test race conditions between expiration and access
|
|
c := cache.NewMemory(0)
|
|
ctx := context.Background()
|
|
|
|
key := "race-expire-key"
|
|
value := "race-expire-value"
|
|
ttl := 10 * time.Millisecond
|
|
|
|
// Set item with short TTL
|
|
err := c.Set(ctx, key, value, cache.WithTTL(ttl))
|
|
if err != nil {
|
|
t.Fatalf("Set failed: %v", err)
|
|
}
|
|
|
|
const numGoroutines = 50
|
|
var wg sync.WaitGroup
|
|
|
|
// Launch goroutines that try to access the key while it's expiring
|
|
for i := range numGoroutines {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
|
|
// Wait for the item to be close to expiration with some jitter
|
|
jitter := time.Duration(id%3) * time.Millisecond
|
|
time.Sleep(ttl - 2*time.Millisecond + jitter)
|
|
|
|
// Try to get the item
|
|
_, err := c.Get(ctx, key)
|
|
if err != nil && err != cache.ErrKeyExpired && err != cache.ErrKeyNotFound {
|
|
t.Errorf("Get failed: %v", err)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|