Cache test for Bonsai

This commit is contained in:
Ethan Frey 2017-07-26 17:44:48 -04:00
parent 199ee81a97
commit 84e2fa64f1
4 changed files with 126 additions and 117 deletions

View File

@ -1,19 +1,12 @@
package state
import (
"container/list"
"fmt"
cmn "github.com/tendermint/tmlibs/common"
)
import "container/list"
// KVCache is a cache that enforces deterministic sync order.
type KVCache struct {
store KVStore
cache map[string]kvCacheValue
keys *list.List
logging bool
logLines []string
store KVStore
cache map[string]kvCacheValue
keys *list.List
}
type kvCacheValue struct {
@ -31,18 +24,6 @@ func NewKVCache(store KVStore) *KVCache {
}).Reset()
}
func (kvc *KVCache) SetLogging() {
kvc.logging = true
}
func (kvc *KVCache) GetLogLines() []string {
return kvc.logLines
}
func (kvc *KVCache) ClearLogLines() {
kvc.logLines = nil
}
func (kvc *KVCache) Reset() *KVCache {
kvc.cache = make(map[string]kvCacheValue)
kvc.keys = list.New()
@ -50,10 +31,6 @@ func (kvc *KVCache) Reset() *KVCache {
}
func (kvc *KVCache) Set(key []byte, value []byte) {
if kvc.logging {
line := fmt.Sprintf("Set %v = %v", LegibleBytes(key), LegibleBytes(value))
kvc.logLines = append(kvc.logLines, line)
}
cacheValue, ok := kvc.cache[string(key)]
if ok {
kvc.keys.MoveToBack(cacheValue.e)
@ -67,10 +44,6 @@ func (kvc *KVCache) Set(key []byte, value []byte) {
func (kvc *KVCache) Get(key []byte) (value []byte) {
cacheValue, ok := kvc.cache[string(key)]
if ok {
if kvc.logging {
line := fmt.Sprintf("Get (hit) %v = %v", LegibleBytes(key), LegibleBytes(cacheValue.v))
kvc.logLines = append(kvc.logLines, line)
}
return cacheValue.v
} else {
value := kvc.store.Get(key)
@ -78,10 +51,6 @@ func (kvc *KVCache) Get(key []byte) (value []byte) {
v: value,
e: kvc.keys.PushBack(key),
}
if kvc.logging {
line := fmt.Sprintf("Get (miss) %v = %v", LegibleBytes(key), LegibleBytes(value))
kvc.logLines = append(kvc.logLines, line)
}
return value
}
}
@ -95,17 +64,3 @@ func (kvc *KVCache) Sync() {
}
kvc.Reset()
}
//----------------------------------------
func LegibleBytes(data []byte) string {
s := ""
for _, b := range data {
if 0x21 <= b && b < 0x7F {
s += cmn.Green(string(b))
} else {
s += cmn.Blue(cmn.Fmt("%02X", b))
}
}
return s
}

View File

@ -1,70 +1,124 @@
package state
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKVCache(t *testing.T) {
func TestCache(t *testing.T) {
assert := assert.New(t)
//stores to be tested
ms := NewMemKVStore()
store := NewMemKVStore()
kvc := NewKVCache(store)
cases := []struct {
init []Model
toGet []Model
toList []listQuery
//key value pairs to be tested within the system
var keyvalue = []struct {
key string
value string
setCache []Model
removeCache []Model
getCache []Model
listCache []listQuery
}{
{"foo", "snake"},
{"bar", "mouse"},
// simple add
{
init: []Model{m("a", "1"), m("c", "2")},
toGet: []Model{m("a", "1"), m("c", "2"), m("d", "")},
toList: []listQuery{{
"a", "e", 0,
[]Model{m("a", "1"), m("c", "2")},
m("c", "2"),
}},
setCache: []Model{m("d", "3")},
removeCache: []Model{m("a", "1")},
getCache: []Model{m("a", ""), m("c", "2"), m("d", "3")},
listCache: []listQuery{{
"a", "e", 0,
[]Model{m("c", "2"), m("d", "3")},
m("d", "3"),
}},
},
}
//set the kvc to have all the key value pairs
setRecords := func(kv KVStore) {
for _, n := range keyvalue {
kv.Set([]byte(n.key), []byte(n.value))
checkGet := func(db SimpleDB, m Model, msg string) {
val := db.Get(m.Key)
assert.EqualValues(m.Value, val, msg)
has := db.Has(m.Key)
assert.Equal(len(m.Value) != 0, has, msg)
}
checkList := func(db SimpleDB, lq listQuery, msg string) {
start, end := []byte(lq.start), []byte(lq.end)
list := db.List(start, end, lq.limit)
if assert.EqualValues(lq.expected, list, msg) {
var first Model
if len(lq.expected) > 0 {
first = lq.expected[0]
}
f := db.First(start, end)
assert.EqualValues(first, f, msg)
l := db.Last(start, end)
assert.EqualValues(lq.last, l, msg)
}
}
//store has all the key value pairs
storeHasAll := func(kv KVStore) bool {
for _, n := range keyvalue {
if !bytes.Equal(kv.Get([]byte(n.key)), []byte(n.value)) {
return false
for i, tc := range cases {
for j, db := range GetDBs() {
for _, s := range tc.init {
db.Set(s.Key, s.Value)
}
for k, g := range tc.toGet {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkGet(db, g, msg)
}
for k, lq := range tc.toList {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkList(db, lq, msg)
}
// make cache
cache := db.Checkpoint()
for _, s := range tc.setCache {
cache.Set(s.Key, s.Value)
}
for k, r := range tc.removeCache {
val := cache.Remove(r.Key)
assert.EqualValues(r.Value, val, "%d/%d/%d", i, j, k)
}
// make sure data is in cache
for k, g := range tc.getCache {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkGet(cache, g, msg)
}
for k, lq := range tc.listCache {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkList(cache, lq, msg)
}
// data not in basic store
for k, g := range tc.toGet {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkGet(db, g, msg)
}
for k, lq := range tc.toList {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkList(db, lq, msg)
}
// commit
db.Commit(cache)
// make sure data is in cache
for k, g := range tc.getCache {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkGet(db, g, msg)
}
for k, lq := range tc.listCache {
msg := fmt.Sprintf("%d/%d/%d", i, j, k)
checkList(db, lq, msg)
}
}
return true
}
//test read/write for MemKVStore
setRecords(ms)
assert.True(storeHasAll(ms), "MemKVStore doesn't retrieve after Set")
//test read/write for KVCache
setRecords(kvc)
assert.True(storeHasAll(kvc), "KVCache doesn't retrieve after Set")
//test reset
kvc.Reset()
assert.False(storeHasAll(kvc), "KVCache retrieving after reset")
//test sync
setRecords(kvc)
assert.False(storeHasAll(store), "store retrieving before synced")
kvc.Sync()
assert.True(storeHasAll(store), "store isn't retrieving after synced")
//test logging
assert.Zero(len(kvc.GetLogLines()), "logging events existed before using SetLogging")
kvc.SetLogging()
setRecords(kvc)
assert.Equal(len(kvc.GetLogLines()), 2, "incorrect number of logging events recorded")
kvc.ClearLogLines()
assert.Zero(len(kvc.GetLogLines()), "logging events still exists after ClearLogLines")
}

View File

@ -34,14 +34,14 @@ type SimpleDB interface {
First(start, end []byte) Model
Last(start, end []byte) Model
// // Checkpoint returns the same state, but where writes
// // are buffered and don't affect the parent
// Checkpoint() SimpleDB
// Checkpoint returns the same state, but where writes
// are buffered and don't affect the parent
Checkpoint() SimpleDB
// // Commit will take all changes from the checkpoint and write
// // them to the parent.
// // Returns an error if this is not a child of this one
// Commit(SimpleDB) error
// Commit will take all changes from the checkpoint and write
// them to the parent.
// Returns an error if this is not a child of this one
Commit(SimpleDB) error
// Discard will remove reference to this
Discard()
@ -56,7 +56,7 @@ type MemKVStore struct {
m map[string][]byte
}
var _ SimpleDB = NewMemKVStore()
// var _ SimpleDB = NewMemKVStore()
// NewMemKVStore initializes a MemKVStore
func NewMemKVStore() *MemKVStore {

View File

@ -9,7 +9,7 @@ import (
func GetDBs() []SimpleDB {
return []SimpleDB{
NewMemKVStore(),
// NewMemKVStore(),
NewBonsai(iavl.NewIAVLTree(0, nil)),
}
}
@ -28,21 +28,21 @@ func m(k, v string) Model {
}
}
type listQuery struct {
// this is the list query
start, end string
limit int
// expected result from List, first element also expected for First
expected []Model
// expected result from Last
last Model
}
// TestKVStore makes sure that get/set/remove operations work,
// as well as list
func TestKVStore(t *testing.T) {
assert := assert.New(t)
type listQuery struct {
// this is the list query
start, end string
limit int
// expected result from List, first element also expected for First
expected []Model
// expected result from Last
last Model
}
cases := []struct {
toSet []Model
toRemove []Model