119 lines
3.3 KiB
Go
119 lines
3.3 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestMemoryOwnershipCapacityExpiryAndClose(t *testing.T) {
|
|
m, err := NewMemory(MemoryConfig{MaxEntries: 2, MaxBytes: 10})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Now()
|
|
m.now = func() time.Time { return now }
|
|
ctx := context.Background()
|
|
value := []byte("aa")
|
|
if err := m.Set(ctx, "a", value, time.Second); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
value[0] = 'x'
|
|
got, _ := m.Get(ctx, "a")
|
|
if string(got) != "aa" {
|
|
t.Fatal("retained caller memory")
|
|
}
|
|
got[0] = 'z'
|
|
got, _ = m.Get(ctx, "a")
|
|
if string(got) != "aa" {
|
|
t.Fatal("returned mutable cache memory")
|
|
}
|
|
m.Set(ctx, "b", []byte("bb"), 0)
|
|
m.Get(ctx, "a")
|
|
m.Set(ctx, "c", []byte("cc"), 0)
|
|
if _, err := m.Get(ctx, "b"); !errors.Is(err, ErrMiss) {
|
|
t.Fatal("LRU eviction failed")
|
|
}
|
|
now = now.Add(time.Second)
|
|
if _, err := m.Get(ctx, "a"); !errors.Is(err, ErrMiss) {
|
|
t.Fatal("expiration boundary ignored")
|
|
}
|
|
if err := m.Set(ctx, "large", []byte("123456"), 0); !errors.Is(err, ErrTooLarge) {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.Get(ctx, "c"); err != nil {
|
|
t.Fatal("oversized write evicted valid data")
|
|
}
|
|
canceled, cancel := context.WithCancel(ctx)
|
|
cancel()
|
|
if err := m.Delete(canceled, "c"); !errors.Is(err, context.Canceled) {
|
|
t.Fatal(err)
|
|
}
|
|
m.Close()
|
|
m.Close()
|
|
if _, err := m.Get(ctx, "c"); !errors.Is(err, ErrClosed) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestLoaderCoalescesAndSeparatesCallerCancellation(t *testing.T) {
|
|
m, _ := NewMemory(MemoryConfig{})
|
|
loader, _ := NewLoader(m, LoaderConfig{MaxLoads: 1, Timeout: time.Second})
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
started, release := make(chan struct{}), make(chan struct{})
|
|
var calls atomic.Int32
|
|
load := func(ctx context.Context) ([]byte, error) {
|
|
calls.Add(1)
|
|
close(started)
|
|
select {
|
|
case <-release:
|
|
return []byte("value"), nil
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
}
|
|
first := make(chan error, 1)
|
|
go func() { _, err := loader.GetOrLoad(ctx, "key", time.Minute, load); first <- err }()
|
|
<-started
|
|
second := make(chan error, 1)
|
|
go func() {
|
|
v, err := loader.GetOrLoad(context.Background(), "key", time.Minute, load)
|
|
if err == nil && string(v) != "value" {
|
|
err = errors.New("wrong value")
|
|
}
|
|
second <- err
|
|
}()
|
|
if _, err := loader.GetOrLoad(context.Background(), "other", 0, load); !errors.Is(err, ErrBusy) {
|
|
t.Fatal(err)
|
|
}
|
|
cancel()
|
|
if err := <-first; !errors.Is(err, context.Canceled) {
|
|
t.Fatal(err)
|
|
}
|
|
close(release)
|
|
if err := <-second; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls.Load() != 1 {
|
|
t.Fatalf("loads=%d", calls.Load())
|
|
}
|
|
}
|
|
|
|
func TestLoaderPanicDeadlineAndStoreFailure(t *testing.T) {
|
|
m, _ := NewMemory(MemoryConfig{})
|
|
l, _ := NewLoader(m, LoaderConfig{Timeout: 20 * time.Millisecond})
|
|
if _, err := l.GetOrLoad(context.Background(), "panic", 0, func(context.Context) ([]byte, error) { panic("failure") }); err == nil {
|
|
t.Fatal("panic escaped")
|
|
}
|
|
if _, err := l.GetOrLoad(context.Background(), "panic", 0, func(ctx context.Context) ([]byte, error) { <-ctx.Done(); return nil, ctx.Err() }); !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatal(err)
|
|
}
|
|
m.Close()
|
|
if _, err := l.GetOrLoad(context.Background(), "closed", 0, func(context.Context) ([]byte, error) { t.Fatal("store outage triggered load"); return nil, nil }); !errors.Is(err, ErrClosed) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|