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) {
val, err, ok := c.get(key)
ok, val, err := c.get(key)
if ok {
return val, err
}

View File

@ -3,6 +3,7 @@ package kvmemcache
import (
"errors"
"fmt"
"sync"
"testing"
"time"
)
@ -48,15 +49,15 @@ func (c *Cache) assert(state State) error {
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{
MaxSize: 4,
TTL: 50 * time.Millisecond,
Src: func(key string) (interface{}, error) {
if key == "err" {
return nil, TestError
return nil, ErrTest
}
return key, nil
},
@ -66,6 +67,7 @@ func TestCache_Basic(t *testing.T) {
name string
sleep time.Duration
key string
evict bool
state State
}
@ -127,17 +129,43 @@ func TestCache_Basic(t *testing.T) {
Keys: []string{"b", "c", "d", "e"},
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 {
time.Sleep(tc.sleep)
val, err := c.Get(tc.key)
if tc.key == "err" && err != TestError {
t.Fatal(tc.name, val)
}
if tc.key != "err" && val.(string) != tc.key {
t.Fatal(tc.name, tc.key, val)
if !tc.evict {
val, err := c.Get(tc.key)
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)
}
} else {
c.Evict(tc.key)
}
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()
defer c.lock.Unlock()
li := c.cache[key]
if li == nil {
return nil, nil, false
return false, nil, nil
}
item := li.Value.(lruItem)
// Maybe evict.
if c.ttl != 0 && time.Since(item.createdAt) > c.ttl {
c.evict(key)
return nil, nil, false
return false, nil, nil
}
c.stats.Hits++
return item.value, item.err, true
return true, item.value, item.err
}
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)
// Check again in case we lost the update race.
val, err, ok := c.get(key)
ok, val, err := c.get(key)
if ok {
return val, err
}