diff --git a/state/kvcache.go b/state/kvcache.go index 10acf8f7c..4231c621a 100644 --- a/state/kvcache.go +++ b/state/kvcache.go @@ -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 -} diff --git a/state/kvcache_test.go b/state/kvcache_test.go index e8ab4ab35..5af26b919 100644 --- a/state/kvcache_test.go +++ b/state/kvcache_test.go @@ -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") - } diff --git a/state/kvstore.go b/state/kvstore.go index 4471c8fd2..7e4907e14 100644 --- a/state/kvstore.go +++ b/state/kvstore.go @@ -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 { diff --git a/state/store_test.go b/state/store_test.go index 3d94e52fe..300cec58c 100644 --- a/state/store_test.go +++ b/state/store_test.go @@ -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