Files
viettranx 3b4004946e chore: apply go fix syntax upgrades (min/max, switch patterns)
Apply Go 1.21+ min/max builtins and improved switch pattern matching
across test files and utilities. No functional changes.
2026-04-15 11:24:57 +07:00

197 lines
4.7 KiB
Go

package cache
import (
"context"
"testing"
"time"
)
func TestInMemoryCache_GetSet(t *testing.T) {
c := NewInMemoryCache[string]()
ctx := context.Background()
c.Set(ctx, "hello", "world", 0)
val, ok := c.Get(ctx, "hello")
if !ok {
t.Fatal("expected key to exist")
}
if val != "world" {
t.Fatalf("expected 'world', got %q", val)
}
}
func TestInMemoryCache_TTLExpiry(t *testing.T) {
c := NewInMemoryCache[int]()
ctx := context.Background()
c.Set(ctx, "key", 42, 20*time.Millisecond)
// Should be present immediately
val, ok := c.Get(ctx, "key")
if !ok || val != 42 {
t.Fatal("expected key to be present before TTL expiry")
}
time.Sleep(40 * time.Millisecond)
_, ok = c.Get(ctx, "key")
if ok {
t.Fatal("expected key to be expired after TTL")
}
}
func TestInMemoryCache_Delete(t *testing.T) {
c := NewInMemoryCache[string]()
ctx := context.Background()
c.Set(ctx, "k", "v", 0)
c.Delete(ctx, "k")
_, ok := c.Get(ctx, "k")
if ok {
t.Fatal("expected key to be deleted")
}
}
func TestInMemoryCache_DeleteByPrefix(t *testing.T) {
c := NewInMemoryCache[string]()
ctx := context.Background()
c.Set(ctx, "agent:1:files", "a", 0)
c.Set(ctx, "agent:1:meta", "b", 0)
c.Set(ctx, "agent:2:files", "c", 0)
c.DeleteByPrefix(ctx, "agent:1:")
if _, ok := c.Get(ctx, "agent:1:files"); ok {
t.Error("agent:1:files should be deleted")
}
if _, ok := c.Get(ctx, "agent:1:meta"); ok {
t.Error("agent:1:meta should be deleted")
}
if _, ok := c.Get(ctx, "agent:2:files"); !ok {
t.Error("agent:2:files should still exist")
}
}
func TestInMemoryCache_Clear(t *testing.T) {
c := NewInMemoryCache[int]()
ctx := context.Background()
c.Set(ctx, "a", 1, 0)
c.Set(ctx, "b", 2, 0)
c.Set(ctx, "c", 3, 0)
c.Clear(ctx)
for _, k := range []string{"a", "b", "c"} {
if _, ok := c.Get(ctx, k); ok {
t.Errorf("key %q should be cleared", k)
}
}
}
// TestInMemoryCache_PeriodicSweep verifies expired entries are removed by
// the background sweep goroutine (not just lazy on Get).
func TestInMemoryCache_PeriodicSweep(t *testing.T) {
c := NewInMemoryCache[string](
WithSweepInterval[string](20 * time.Millisecond),
)
defer c.Close()
ctx := context.Background()
c.Set(ctx, "short", "v1", 10*time.Millisecond)
c.Set(ctx, "long", "v2", 0) // no expiry
// Wait for sweep to run at least once after short entry expires
time.Sleep(60 * time.Millisecond)
// Short entry should be removed by sweep even without Get
if count := c.sizeLocked(); count != 1 {
t.Errorf("expected 1 entry after sweep, got %d", count)
}
if _, ok := c.Get(ctx, "long"); !ok {
t.Error("long-lived entry should still exist")
}
}
// TestInMemoryCache_MaxSizeEviction verifies oldest entries are evicted when
// max size cap is reached.
func TestInMemoryCache_MaxSizeEviction(t *testing.T) {
c := NewInMemoryCache[int](
WithMaxSize[int](5),
WithSweepInterval[int](10*time.Millisecond),
)
defer c.Close()
ctx := context.Background()
// Insert 10 entries with distinct creation times to ensure oldest-first ordering
for i := range 10 {
c.Set(ctx, string(rune('a'+i)), i, 0)
time.Sleep(2 * time.Millisecond)
}
// Trigger sweep by waiting for interval
time.Sleep(30 * time.Millisecond)
if count := c.sizeLocked(); count > 5 {
t.Errorf("expected size ≤ 5 after max-size eviction, got %d", count)
}
}
// TestInMemoryCache_Close verifies Close stops the sweep goroutine (no leak).
func TestInMemoryCache_Close(t *testing.T) {
c := NewInMemoryCache[string](
WithSweepInterval[string](10 * time.Millisecond),
)
ctx := context.Background()
c.Set(ctx, "k", "v", 0)
c.Close()
// Close should be idempotent
c.Close()
// After Close, cache is still readable for lazy access but sweep is stopped
if _, ok := c.Get(ctx, "k"); !ok {
t.Error("Get should still work after Close")
}
}
// TestInMemoryCache_ConcurrentSweepAndSet verifies no race between sweep and Set.
func TestInMemoryCache_ConcurrentSweepAndSet(t *testing.T) {
c := NewInMemoryCache[int](
WithSweepInterval[int](1*time.Millisecond),
WithMaxSize[int](100),
)
defer c.Close()
ctx := context.Background()
done := make(chan bool)
go func() {
for i := range 500 {
c.Set(ctx, string(rune('a'+(i%26))), i, 5*time.Millisecond)
}
done <- true
}()
go func() {
for i := range 500 {
_, _ = c.Get(ctx, string(rune('a'+(i%26))))
}
done <- true
}()
<-done
<-done
}
// TestInMemoryCache_BackwardCompatZeroArg verifies existing call sites with
// zero-arg constructor still work (variadic options).
func TestInMemoryCache_BackwardCompatZeroArg(t *testing.T) {
c := NewInMemoryCache[string]()
defer c.Close()
ctx := context.Background()
c.Set(ctx, "k", "v", 0)
if v, ok := c.Get(ctx, "k"); !ok || v != "v" {
t.Errorf("backward compat broken: got %v, %v", v, ok)
}
}