Files
remark42/app/rest/cache/cache_test.go
T
2018-05-31 15:44:00 -05:00

270 lines
7.4 KiB
Go

package cache
import (
"fmt"
"net/http"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/umputun/remark/app/rest"
"github.com/umputun/remark/app/store"
)
func TestLoadingCache_Get(t *testing.T) {
var postFnCall, coldCalls int32
lc := NewLoadingCache(PostFlushFn(func() { atomic.AddInt32(&postFnCall, 1) }))
res, err := lc.Get("key", time.Minute, func() ([]byte, error) {
atomic.AddInt32(&coldCalls, 1)
return []byte("result"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result", string(res))
assert.Equal(t, int32(1), atomic.LoadInt32(&coldCalls))
assert.Equal(t, int32(0), atomic.LoadInt32(&postFnCall))
res, err = lc.Get("key", time.Minute, func() ([]byte, error) {
atomic.AddInt32(&coldCalls, 1)
return []byte("result"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result", string(res))
assert.Equal(t, int32(1), atomic.LoadInt32(&coldCalls))
assert.Equal(t, int32(0), atomic.LoadInt32(&postFnCall))
lc.Flush()
time.Sleep(100 * time.Millisecond) // let postFn to do its thing
assert.Equal(t, int32(1), atomic.LoadInt32(&postFnCall))
}
func TestLoadingCache_MaxKeys(t *testing.T) {
var postFnCall, coldCalls int32
lc := NewLoadingCache(PostFlushFn(func() { atomic.AddInt32(&postFnCall, 1) }),
MaxKeys(5), MaxValSize(10))
// put 5 keys to cache
for i := 0; i < 5; i++ {
res, err := lc.Get(fmt.Sprintf("key-%d", i), 500*time.Millisecond, func() ([]byte, error) {
atomic.AddInt32(&coldCalls, 1)
return []byte(fmt.Sprintf("result-%d", i)), nil
})
assert.Nil(t, err)
assert.Equal(t, fmt.Sprintf("result-%d", i), string(res))
assert.Equal(t, int32(i+1), atomic.LoadInt32(&coldCalls))
assert.Equal(t, int32(0), atomic.LoadInt32(&postFnCall))
}
// check if really cached
res, err := lc.Get("key-3", time.Minute, func() ([]byte, error) {
return []byte("result-blah"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result-3", string(res), "should be cached")
// try to cache after maxKeys reached
res, err = lc.Get("key-X", time.Minute, func() ([]byte, error) {
return []byte("result-X"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result-X", string(res))
assert.Equal(t, 5, lc.(*loadingCache).bytesCache.Len())
// put to cache and make sure it cached
res, err = lc.Get("key-Z", time.Minute, func() ([]byte, error) {
return []byte("result-Z"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result-Z", string(res))
res, err = lc.Get("key-Z", time.Minute, func() ([]byte, error) {
return []byte("result-Zzzz"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result-Z", string(res), "got cached value")
assert.Equal(t, 5, lc.(*loadingCache).bytesCache.Len())
}
func TestLoadingCache_MaxSize(t *testing.T) {
lc := NewLoadingCache(MaxKeys(5), MaxValSize(10))
// put good size value to cache and make sure it cached
res, err := lc.Get("key-Z", time.Minute, func() ([]byte, error) {
return []byte("result-Z"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result-Z", string(res))
res, err = lc.Get("key-Z", time.Minute, func() ([]byte, error) {
return []byte("result-Zzzz"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result-Z", string(res), "got cached value")
// put too big value to cache and make sure it is not cached
res, err = lc.Get("key-Big", time.Minute, func() ([]byte, error) {
return []byte("1234567890"), nil
})
assert.Nil(t, err)
assert.Equal(t, "1234567890", string(res))
res, err = lc.Get("key-Big", time.Minute, func() ([]byte, error) {
return []byte("result-big"), nil
})
assert.Nil(t, err)
assert.Equal(t, "result-big", string(res), "got not cached value")
}
func TestLoadingCache_URLKey(t *testing.T) {
r, err := http.NewRequest("GET", "http://blah/123", nil)
assert.Nil(t, err)
key := URLKey(r)
assert.Equal(t, "http://blah/123", key)
r, err = http.NewRequest("GET", "http://blah/123?key=v&k2=v2", nil)
assert.Nil(t, err)
key = URLKey(r)
assert.Equal(t, "http://blah/123?key=v&k2=v2", key)
user := store.User{Admin: true}
r = rest.SetUserInfo(r, user)
key = URLKey(r)
assert.Equal(t, "admin!!http://blah/123?key=v&k2=v2", key)
}
func TestLoadingCache_Parallel(t *testing.T) {
var coldCalls int32
lc := NewLoadingCache()
res, err := lc.Get("key", time.Minute, func() ([]byte, error) {
return []byte("value"), nil
})
assert.Nil(t, err)
assert.Equal(t, "value", string(res))
wg := sync.WaitGroup{}
for i := 0; i < 1000; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
res, err := lc.Get("key", time.Minute, func() ([]byte, error) {
atomic.AddInt32(&coldCalls, 1)
return []byte(fmt.Sprintf("result-%d", i)), nil
})
require.Nil(t, err)
require.Equal(t, "value", string(res))
}()
}
wg.Wait()
assert.Equal(t, int32(0), atomic.LoadInt32(&coldCalls))
}
func TestLoadingCache_Scopes(t *testing.T) {
lc := NewLoadingCache()
res, err := lc.Get(Key("key", "s1", "s2"), time.Minute, func() ([]byte, error) {
return []byte("value"), nil
})
assert.Nil(t, err)
assert.Equal(t, "value", string(res))
res, err = lc.Get(Key("key2", "s2"), time.Minute, func() ([]byte, error) {
return []byte("value2"), nil
})
assert.Nil(t, err)
assert.Equal(t, "value2", string(res))
assert.Equal(t, 2, lc.(*loadingCache).bytesCache.Len())
lc.Flush("s1")
assert.Equal(t, 1, lc.(*loadingCache).bytesCache.Len())
lc.Get(Key("key2", "s2"), time.Minute, func() ([]byte, error) {
assert.Fail(t, "should stay")
return nil, nil
})
res, err = lc.Get(Key("key", "s1", "s2"), time.Minute, func() ([]byte, error) {
return []byte("value-upd"), nil
})
assert.Equal(t, "value-upd", string(res), "was deleted, update")
}
func TestLoadingCache_Flush(t *testing.T) {
lc := NewLoadingCache()
addToCache := func(key string, scopes ...string) {
res, err := lc.Get(key, time.Minute, func() ([]byte, error) {
return []byte("value" + key), nil
})
require.Nil(t, err)
require.Equal(t, "value"+key, string(res))
}
init := func() {
lc.Flush()
addToCache(Key("key1", "s1", "s2"))
addToCache(Key("key2", "s1", "s2", "s3"))
addToCache(Key("key3", "s1", "s2", "s3"))
addToCache(Key("key4", "s2", "s3"))
addToCache(Key("key5", "s2"))
addToCache(Key("key6"))
addToCache(Key("key7", "s4", "s3"))
require.Equal(t, 7, lc.(*loadingCache).bytesCache.Len(), "cache init")
}
tbl := []struct {
scopes []string
left int
msg string
}{
{[]string{}, 0, "full flush, no scopes"},
{[]string{"s0"}, 7, "flush wrong scope"},
{[]string{"s1"}, 4, "flush s1 scope"},
{[]string{"s2", "s1"}, 2, "flush s2+s1 scope"},
{[]string{"s1", "s2"}, 2, "flush s1+s2 scope"},
{[]string{"s1", "s2", "s4"}, 1, "flush s1+s2+s4 scope"},
{[]string{"s1", "s2", "s3"}, 1, "flush s1+s2+s3 scope"},
{[]string{"s1", "s2", "ss"}, 2, "flush s1+s2+wrong scope"},
}
for i, tt := range tbl {
init()
lc.Flush(tt.scopes...)
assert.Equal(t, tt.left, lc.(*loadingCache).bytesCache.Len(), "keys size, %s #%d", tt.msg, i)
}
}
func TestLoadingCache_Keys(t *testing.T) {
tbl := []struct {
key string
scopes []string
full string
}{
{"key1", []string{"s1"}, "s1@@key1"},
{"key2", []string{"s11", "s2"}, "s11$$s2@@key2"},
{"key3", []string{}, "@@key3"},
}
for n, tt := range tbl {
full := Key(tt.key, tt.scopes...)
assert.Equal(t, tt.full, full, "making key, #%d", n)
k, s, e := parseKey(full)
assert.Nil(t, e)
assert.Equal(t, tt.scopes, s)
assert.Equal(t, tt.key, k)
}
_, _, err := parseKey("abc")
assert.Error(t, err)
_, _, err = parseKey("")
assert.Error(t, err)
}