kvmemcache: tests complete
This commit is contained in:
parent
a6290db03b
commit
d53a869235
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user