296 lines
6.8 KiB
Go
296 lines
6.8 KiB
Go
// Copyright 2017 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package kvcache
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/pingcap/tidb/pkg/util/memory"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type mockCacheKey struct {
|
|
hash []byte
|
|
key int64
|
|
}
|
|
|
|
func (mk *mockCacheKey) Hash() []byte {
|
|
if mk.hash != nil {
|
|
return mk.hash
|
|
}
|
|
mk.hash = make([]byte, 8)
|
|
for i := uint(0); i < 8; i++ {
|
|
mk.hash[i] = byte((mk.key >> ((i - 1) * 8)) & 0xff)
|
|
}
|
|
return mk.hash
|
|
}
|
|
|
|
func newMockHashKey(key int64) *mockCacheKey {
|
|
return &mockCacheKey{
|
|
key: key,
|
|
}
|
|
}
|
|
|
|
func TestPut(t *testing.T) {
|
|
maxMem, err := memory.MemTotal()
|
|
require.NoError(t, err)
|
|
|
|
lruMaxMem := NewSimpleLRUCache(3, 0, maxMem)
|
|
lruZeroQuota := NewSimpleLRUCache(3, 0, 0)
|
|
require.Equal(t, uint(3), lruMaxMem.capacity)
|
|
require.Equal(t, uint(3), lruMaxMem.capacity)
|
|
|
|
keys := make([]*mockCacheKey, 5)
|
|
vals := make([]int64, 5)
|
|
maxMemDroppedKv := make(map[Key]Value)
|
|
zeroQuotaDroppedKv := make(map[Key]Value)
|
|
|
|
// test onEvict function
|
|
lruMaxMem.SetOnEvict(func(key Key, value Value) {
|
|
maxMemDroppedKv[key] = value
|
|
})
|
|
// test onEvict function on 0 value of quota
|
|
lruZeroQuota.SetOnEvict(func(key Key, value Value) {
|
|
zeroQuotaDroppedKv[key] = value
|
|
})
|
|
for i := 0; i < 5; i++ {
|
|
keys[i] = newMockHashKey(int64(i))
|
|
vals[i] = int64(i)
|
|
lruMaxMem.Put(keys[i], vals[i])
|
|
lruZeroQuota.Put(keys[i], vals[i])
|
|
}
|
|
require.Equal(t, lruMaxMem.size, lruMaxMem.capacity)
|
|
require.Equal(t, lruZeroQuota.size, lruZeroQuota.capacity)
|
|
require.Equal(t, uint(3), lruMaxMem.size)
|
|
require.Equal(t, lruZeroQuota.size, lruMaxMem.size)
|
|
|
|
// test for non-existent elements
|
|
require.Len(t, maxMemDroppedKv, 2)
|
|
for i := 0; i < 2; i++ {
|
|
element, exists := lruMaxMem.elements[string(keys[i].Hash())]
|
|
require.False(t, exists)
|
|
require.Nil(t, element)
|
|
require.Equal(t, vals[i], maxMemDroppedKv[keys[i]])
|
|
require.Equal(t, vals[i], zeroQuotaDroppedKv[keys[i]])
|
|
}
|
|
|
|
// test for existent elements
|
|
root := lruMaxMem.cache.Front()
|
|
require.NotNil(t, root)
|
|
for i := 4; i >= 2; i-- {
|
|
entry, ok := root.Value.(*cacheEntry)
|
|
require.True(t, ok)
|
|
require.NotNil(t, entry)
|
|
|
|
// test key
|
|
key := entry.key
|
|
require.NotNil(t, key)
|
|
require.Equal(t, keys[i], key)
|
|
|
|
element, exists := lruMaxMem.elements[string(keys[i].Hash())]
|
|
require.True(t, exists)
|
|
require.NotNil(t, element)
|
|
require.Equal(t, root, element)
|
|
|
|
// test value
|
|
value, ok := entry.value.(int64)
|
|
require.True(t, ok)
|
|
require.Equal(t, vals[i], value)
|
|
|
|
root = root.Next()
|
|
}
|
|
|
|
// test for end of double-linked list
|
|
require.Nil(t, root)
|
|
}
|
|
|
|
func TestZeroQuota(t *testing.T) {
|
|
lru := NewSimpleLRUCache(100, 0, 0)
|
|
require.Equal(t, uint(100), lru.capacity)
|
|
|
|
keys := make([]*mockCacheKey, 100)
|
|
vals := make([]int64, 100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
keys[i] = newMockHashKey(int64(i))
|
|
vals[i] = int64(i)
|
|
lru.Put(keys[i], vals[i])
|
|
}
|
|
require.Equal(t, lru.size, lru.capacity)
|
|
require.Equal(t, uint(100), lru.size)
|
|
}
|
|
|
|
func TestOOMGuard(t *testing.T) {
|
|
maxMem, err := memory.MemTotal()
|
|
require.NoError(t, err)
|
|
|
|
lru := NewSimpleLRUCache(3, 1.0, maxMem)
|
|
require.Equal(t, uint(3), lru.capacity)
|
|
|
|
keys := make([]*mockCacheKey, 5)
|
|
vals := make([]int64, 5)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
keys[i] = newMockHashKey(int64(i))
|
|
vals[i] = int64(i)
|
|
lru.Put(keys[i], vals[i])
|
|
}
|
|
require.Equal(t, uint(0), lru.size)
|
|
|
|
// test for non-existent elements
|
|
for i := 0; i < 5; i++ {
|
|
element, exists := lru.elements[string(keys[i].Hash())]
|
|
require.False(t, exists)
|
|
require.Nil(t, element)
|
|
}
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
maxMem, err := memory.MemTotal()
|
|
require.NoError(t, err)
|
|
|
|
lru := NewSimpleLRUCache(3, 0, maxMem)
|
|
|
|
keys := make([]*mockCacheKey, 5)
|
|
vals := make([]int64, 5)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
keys[i] = newMockHashKey(int64(i))
|
|
vals[i] = int64(i)
|
|
lru.Put(keys[i], vals[i])
|
|
}
|
|
|
|
// test for non-existent elements
|
|
for i := 0; i < 2; i++ {
|
|
value, exists := lru.Get(keys[i])
|
|
require.False(t, exists)
|
|
require.Nil(t, value)
|
|
}
|
|
|
|
for i := 2; i < 5; i++ {
|
|
value, exists := lru.Get(keys[i])
|
|
require.True(t, exists)
|
|
require.NotNil(t, value)
|
|
require.Equal(t, vals[i], value)
|
|
require.Equal(t, uint(3), lru.size)
|
|
require.Equal(t, uint(3), lru.capacity)
|
|
|
|
root := lru.cache.Front()
|
|
require.NotNil(t, root)
|
|
|
|
entry, ok := root.Value.(*cacheEntry)
|
|
require.True(t, ok)
|
|
require.Equal(t, keys[i], entry.key)
|
|
|
|
value, ok = entry.value.(int64)
|
|
require.True(t, ok)
|
|
require.Equal(t, vals[i], value)
|
|
}
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
maxMem, err := memory.MemTotal()
|
|
require.NoError(t, err)
|
|
|
|
lru := NewSimpleLRUCache(3, 0, maxMem)
|
|
|
|
keys := make([]*mockCacheKey, 3)
|
|
vals := make([]int64, 3)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
keys[i] = newMockHashKey(int64(i))
|
|
vals[i] = int64(i)
|
|
lru.Put(keys[i], vals[i])
|
|
}
|
|
require.Equal(t, 3, int(lru.size))
|
|
|
|
lru.Delete(keys[1])
|
|
value, exists := lru.Get(keys[1])
|
|
require.False(t, exists)
|
|
require.Nil(t, value)
|
|
require.Equal(t, 2, int(lru.size))
|
|
|
|
_, exists = lru.Get(keys[0])
|
|
require.True(t, exists)
|
|
|
|
_, exists = lru.Get(keys[2])
|
|
require.True(t, exists)
|
|
}
|
|
|
|
func TestDeleteAll(t *testing.T) {
|
|
maxMem, err := memory.MemTotal()
|
|
require.NoError(t, err)
|
|
|
|
lru := NewSimpleLRUCache(3, 0, maxMem)
|
|
|
|
keys := make([]*mockCacheKey, 3)
|
|
vals := make([]int64, 3)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
keys[i] = newMockHashKey(int64(i))
|
|
vals[i] = int64(i)
|
|
lru.Put(keys[i], vals[i])
|
|
}
|
|
require.Equal(t, 3, int(lru.size))
|
|
|
|
lru.DeleteAll()
|
|
|
|
for i := 0; i < 3; i++ {
|
|
value, exists := lru.Get(keys[i])
|
|
require.False(t, exists)
|
|
require.Nil(t, value)
|
|
require.Equal(t, 0, int(lru.size))
|
|
}
|
|
}
|
|
|
|
func TestValues(t *testing.T) {
|
|
maxMem, err := memory.MemTotal()
|
|
require.NoError(t, err)
|
|
|
|
lru := NewSimpleLRUCache(5, 0, maxMem)
|
|
|
|
keys := make([]*mockCacheKey, 5)
|
|
vals := make([]int64, 5)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
keys[i] = newMockHashKey(int64(i))
|
|
vals[i] = int64(i)
|
|
lru.Put(keys[i], vals[i])
|
|
}
|
|
|
|
values := lru.Values()
|
|
require.Equal(t, 5, len(values))
|
|
for i := 0; i < 5; i++ {
|
|
require.Equal(t, int64(4-i), values[i])
|
|
}
|
|
}
|
|
|
|
func TestPutProfileName(t *testing.T) {
|
|
lru := NewSimpleLRUCache(3, 0, 10)
|
|
require.Equal(t, uint(3), lru.capacity)
|
|
tem := reflect.TypeOf(*lru)
|
|
pt := reflect.TypeOf(lru)
|
|
functionName := ""
|
|
for i := 0; i < pt.NumMethod(); i++ {
|
|
if pt.Method(i).Name == "Put" {
|
|
functionName = "Put"
|
|
}
|
|
}
|
|
pName := fmt.Sprintf("%s.(*%s).%s", tem.PkgPath(), tem.Name(), functionName)
|
|
require.Equal(t, ProfileName, pName)
|
|
}
|