kvmemcache: tests complete

master
J. David Lee 2021-04-06 17:51:16 +02:00
parent a6290db03b
commit d53a869235
3 changed files with 74 additions and 16 deletions

View File

@ -47,7 +47,7 @@ func New(conf Config) *Cache {
} }
func (c *Cache) Get(key string) (interface{}, error) { func (c *Cache) Get(key string) (interface{}, error) {
val, err, ok := c.get(key) ok, val, err := c.get(key)
if ok { if ok {
return val, err return val, err
} }

View File

@ -3,6 +3,7 @@ package kvmemcache
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync"
"testing" "testing"
"time" "time"
) )
@ -48,15 +49,15 @@ func (c *Cache) assert(state State) error {
return nil return nil
} }
var TestError = errors.New("Hello") var ErrTest = errors.New("Hello")
func TestCache_Basic(t *testing.T) { func TestCache_basic(t *testing.T) {
c := New(Config{ c := New(Config{
MaxSize: 4, MaxSize: 4,
TTL: 50 * time.Millisecond, TTL: 50 * time.Millisecond,
Src: func(key string) (interface{}, error) { Src: func(key string) (interface{}, error) {
if key == "err" { if key == "err" {
return nil, TestError return nil, ErrTest
} }
return key, nil return key, nil
}, },
@ -66,6 +67,7 @@ func TestCache_Basic(t *testing.T) {
name string name string
sleep time.Duration sleep time.Duration
key string key string
evict bool
state State state State
} }
@ -127,17 +129,43 @@ func TestCache_Basic(t *testing.T) {
Keys: []string{"b", "c", "d", "e"}, Keys: []string{"b", "c", "d", "e"},
Stats: Stats{Hits: 2, Misses: 6}, Stats: Stats{Hits: 2, Misses: 6},
}, },
}, {
name: "get err",
key: "err",
state: State{
Keys: []string{"c", "d", "e", "err"},
Stats: Stats{Hits: 2, Misses: 7},
},
}, {
name: "get err again",
key: "err",
state: State{
Keys: []string{"c", "d", "e", "err"},
Stats: Stats{Hits: 3, Misses: 7},
},
}, {
name: "evict c",
key: "c",
evict: true,
state: State{
Keys: []string{"d", "e", "err"},
Stats: Stats{Hits: 3, Misses: 7},
},
}, },
} }
for _, tc := range cases { for _, tc := range cases {
time.Sleep(tc.sleep) time.Sleep(tc.sleep)
val, err := c.Get(tc.key) if !tc.evict {
if tc.key == "err" && err != TestError { val, err := c.Get(tc.key)
t.Fatal(tc.name, val) if tc.key == "err" && err != ErrTest {
} t.Fatal(tc.name, val)
if tc.key != "err" && val.(string) != tc.key { }
t.Fatal(tc.name, tc.key, val) if tc.key != "err" && val.(string) != tc.key {
t.Fatal(tc.name, tc.key, val)
}
} else {
c.Evict(tc.key)
} }
if err := c.assert(tc.state); err != nil { if err := c.assert(tc.state); err != nil {
@ -146,4 +174,34 @@ func TestCache_Basic(t *testing.T) {
} }
} }
// TODO: Test thundering herd mitigation. func TestCache_thunderingHerd(t *testing.T) {
c := New(Config{
MaxSize: 4,
Src: func(key string) (interface{}, error) {
time.Sleep(time.Second)
return key, nil
},
})
wg := sync.WaitGroup{}
for i := 0; i < 1024; i++ {
wg.Add(1)
go func() {
defer wg.Done()
val, err := c.Get("a")
if err != nil {
panic(err)
}
if val != "a" {
panic(err)
}
}()
}
wg.Wait()
stats := c.Stats()
if stats.Hits != 1023 || stats.Misses != 1 {
t.Fatal(stats)
}
}

View File

@ -30,24 +30,24 @@ func (c *Cache) evict(key string) {
} }
} }
func (c *Cache) get(key string) (val interface{}, err error, ok bool) { func (c *Cache) get(key string) (ok bool, val interface{}, err error) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
li := c.cache[key] li := c.cache[key]
if li == nil { if li == nil {
return nil, nil, false return false, nil, nil
} }
item := li.Value.(lruItem) item := li.Value.(lruItem)
// Maybe evict. // Maybe evict.
if c.ttl != 0 && time.Since(item.createdAt) > c.ttl { if c.ttl != 0 && time.Since(item.createdAt) > c.ttl {
c.evict(key) c.evict(key)
return nil, nil, false return false, nil, nil
} }
c.stats.Hits++ c.stats.Hits++
return item.value, item.err, true return true, item.value, item.err
} }
func (c *Cache) load(key string) (interface{}, error) { func (c *Cache) load(key string) (interface{}, error) {
@ -55,7 +55,7 @@ func (c *Cache) load(key string) (interface{}, error) {
defer c.updateLock.Unlock(key) defer c.updateLock.Unlock(key)
// Check again in case we lost the update race. // Check again in case we lost the update race.
val, err, ok := c.get(key) ok, val, err := c.get(key)
if ok { if ok {
return val, err return val, err
} }