Files
oci-portal/internal/cache/ttlcache_test.go
T

96 lines
2.2 KiB
Go

package cache
import (
"errors"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestGetSetExpiry(t *testing.T) {
c := New(10)
if _, ok := c.Get("k"); ok {
t.Fatal("空缓存不应命中")
}
c.Set("k", "v", 50*time.Millisecond)
if v, ok := c.Get("k"); !ok || v.(string) != "v" {
t.Fatalf("Get = %v %v, want v true", v, ok)
}
// jitter ≤ TTL/10,60ms 覆盖 50+5ms 上限
time.Sleep(60 * time.Millisecond)
if _, ok := c.Get("k"); ok {
t.Error("过期后不应命中")
}
}
func TestDeletePrefix(t *testing.T) {
c := New(10)
c.Set("t1|a", 1, time.Minute)
c.Set("t1|b", 2, time.Minute)
c.Set("t2|a", 3, time.Minute)
c.DeletePrefix("t1|")
if _, ok := c.Get("t1|a"); ok {
t.Error("t1|a 应被前缀删除")
}
if _, ok := c.Get("t2|a"); !ok {
t.Error("t2|a 不应被删除")
}
}
func TestEvictAtMax(t *testing.T) {
c := New(2)
c.Set("a", 1, time.Minute)
c.Set("b", 2, time.Minute)
c.Set("c", 3, time.Minute)
if n := len(c.items); n > 2 {
t.Errorf("键数 = %d, 应不超过上限 2", n)
}
if _, ok := c.Get("c"); !ok {
t.Error("最新写入的键应存在")
}
}
func TestDoSingleflight(t *testing.T) {
c := New(10)
var calls atomic.Int32
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
v, err := Do(c, "k", time.Minute, func() (int, error) {
calls.Add(1)
time.Sleep(30 * time.Millisecond)
return 42, nil
})
if err != nil || v != 42 {
t.Errorf("Do = %d, %v", v, err)
}
}()
}
wg.Wait()
if n := calls.Load(); n != 1 {
t.Errorf("并发 miss 回源 %d 次, want 1", n)
}
// 命中缓存不再回源
if _, err := Do(c, "k", time.Minute, func() (int, error) { calls.Add(1); return 0, nil }); err != nil {
t.Fatal(err)
}
if n := calls.Load(); n != 1 {
t.Errorf("命中后回源 %d 次, want 1", n)
}
}
func TestDoErrorNotCached(t *testing.T) {
c := New(10)
boom := errors.New("boom")
if _, err := Do(c, "k", time.Minute, func() (int, error) { return 0, boom }); !errors.Is(err, boom) {
t.Fatalf("err = %v, want boom", err)
}
v, err := Do(c, "k", time.Minute, func() (int, error) { return 7, nil })
if err != nil || v != 7 {
t.Errorf("错误不应入缓存,重试 = %d, %v", v, err)
}
}