// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
|
|
// All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package testutil
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/syndtr/goleveldb/leveldb/errors"
|
|
"github.com/syndtr/goleveldb/leveldb/iterator"
|
|
"github.com/syndtr/goleveldb/leveldb/util"
|
|
)
|
|
|
|
type DB interface{}
|
|
|
|
type Put interface {
|
|
TestPut(key []byte, value []byte) error
|
|
}
|
|
|
|
type Delete interface {
|
|
TestDelete(key []byte) error
|
|
}
|
|
|
|
type Find interface {
|
|
TestFind(key []byte) (rkey, rvalue []byte, err error)
|
|
}
|
|
|
|
type Get interface {
|
|
TestGet(key []byte) (value []byte, err error)
|
|
}
|
|
|
|
type Has interface {
|
|
TestHas(key []byte) (ret bool, err error)
|
|
}
|
|
|
|
type NewIterator interface {
|
|
TestNewIterator(slice *util.Range) iterator.Iterator
|
|
}
|
|
|
|
type DBAct int
|
|
|
|
func (a DBAct) String() string {
|
|
switch a {
|
|
case DBNone:
|
|
return "none"
|
|
case DBPut:
|
|
return "put"
|
|
case DBOverwrite:
|
|
return "overwrite"
|
|
case DBDelete:
|
|
return "delete"
|
|
case DBDeleteNA:
|
|
return "delete_na"
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
const (
|
|
DBNone DBAct = iota
|
|
DBPut
|
|
DBOverwrite
|
|
DBDelete
|
|
DBDeleteNA
|
|
)
|
|
|
|
type DBTesting struct {
|
|
Rand *rand.Rand
|
|
DB interface {
|
|
Get
|
|
Put
|
|
Delete
|
|
}
|
|
PostFn func(t *DBTesting)
|
|
Deleted, Present KeyValue
|
|
Act, LastAct DBAct
|
|
ActKey, LastActKey []byte
|
|
}
|
|
|
|
func (t *DBTesting) post() {
|
|
if t.PostFn != nil {
|
|
t.PostFn(t)
|
|
}
|
|
}
|
|
|
|
func (t *DBTesting) setAct(act DBAct, key []byte) {
|
|
t.LastAct, t.Act = t.Act, act
|
|
t.LastActKey, t.ActKey = t.ActKey, key
|
|
}
|
|
|
|
func (t *DBTesting) text() string {
|
|
return fmt.Sprintf("last action was <%v> %q, <%v> %q", t.LastAct, t.LastActKey, t.Act, t.ActKey)
|
|
}
|
|
|
|
func (t *DBTesting) Text() string {
|
|
return "DBTesting " + t.text()
|
|
}
|
|
|
|
func (t *DBTesting) TestPresentKV(key, value []byte) {
|
|
rvalue, err := t.DB.TestGet(key)
|
|
Expect(err).ShouldNot(HaveOccurred(), "Get on key %q, %s", key, t.text())
|
|
Expect(rvalue).Should(Equal(value), "Value for key %q, %s", key, t.text())
|
|
}
|
|
|
|
func (t *DBTesting) TestAllPresent() {
|
|
t.Present.IterateShuffled(t.Rand, func(i int, key, value []byte) {
|
|
t.TestPresentKV(key, value)
|
|
})
|
|
}
|
|
|
|
func (t *DBTesting) TestDeletedKey(key []byte) {
|
|
_, err := t.DB.TestGet(key)
|
|
Expect(err).Should(Equal(errors.ErrNotFound), "Get on deleted key %q, %s", key, t.text())
|
|
}
|
|
|
|
func (t *DBTesting) TestAllDeleted() {
|
|
t.Deleted.IterateShuffled(t.Rand, func(i int, key, value []byte) {
|
|
t.TestDeletedKey(key)
|
|
})
|
|
}
|
|
|
|
func (t *DBTesting) TestAll() {
|
|
dn := t.Deleted.Len()
|
|
pn := t.Present.Len()
|
|
ShuffledIndex(t.Rand, dn+pn, 1, func(i int) {
|
|
if i >= dn {
|
|
key, value := t.Present.Index(i - dn)
|
|
t.TestPresentKV(key, value)
|
|
} else {
|
|
t.TestDeletedKey(t.Deleted.KeyAt(i))
|
|
}
|
|
})
|
|
}
|
|
|
|
func (t *DBTesting) Put(key, value []byte) {
|
|
if new := t.Present.PutU(key, value); new {
|
|
t.setAct(DBPut, key)
|
|
} else {
|
|
t.setAct(DBOverwrite, key)
|
|
}
|
|
t.Deleted.Delete(key)
|
|
err := t.DB.TestPut(key, value)
|
|
Expect(err).ShouldNot(HaveOccurred(), t.Text())
|
|
t.TestPresentKV(key, value)
|
|
t.post()
|
|
}
|
|
|
|
func (t *DBTesting) PutRandom() bool {
|
|
if t.Deleted.Len() > 0 {
|
|
i := t.Rand.Intn(t.Deleted.Len())
|
|
key, value := t.Deleted.Index(i)
|
|
t.Put(key, value)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (t *DBTesting) Delete(key []byte) {
|
|
if exist, value := t.Present.Delete(key); exist {
|
|
t.setAct(DBDelete, key)
|
|
t.Deleted.PutU(key, value)
|
|
} else {
|
|
t.setAct(DBDeleteNA, key)
|
|
}
|
|
err := t.DB.TestDelete(key)
|
|
Expect(err).ShouldNot(HaveOccurred(), t.Text())
|
|
t.TestDeletedKey(key)
|
|
t.post()
|
|
}
|
|
|
|
func (t *DBTesting) DeleteRandom() bool {
|
|
if t.Present.Len() > 0 {
|
|
i := t.Rand.Intn(t.Present.Len())
|
|
t.Delete(t.Present.KeyAt(i))
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (t *DBTesting) RandomAct(round int) {
|
|
for i := 0; i < round; i++ {
|
|
if t.Rand.Int()%2 == 0 {
|
|
t.PutRandom()
|
|
} else {
|
|
t.DeleteRandom()
|
|
}
|
|
}
|
|
}
|
|
|
|
func DoDBTesting(t *DBTesting) {
|
|
if t.Rand == nil {
|
|
t.Rand = NewRand()
|
|
}
|
|
|
|
t.DeleteRandom()
|
|
t.PutRandom()
|
|
t.DeleteRandom()
|
|
t.DeleteRandom()
|
|
for i := t.Deleted.Len() / 2; i >= 0; i-- {
|
|
t.PutRandom()
|
|
}
|
|
t.RandomAct((t.Deleted.Len() + t.Present.Len()) * 10)
|
|
|
|
// Additional iterator testing
|
|
if db, ok := t.DB.(NewIterator); ok {
|
|
iter := db.TestNewIterator(nil)
|
|
Expect(iter.Error()).NotTo(HaveOccurred())
|
|
|
|
it := IteratorTesting{
|
|
KeyValue: t.Present,
|
|
Iter: iter,
|
|
}
|
|
|
|
DoIteratorTesting(&it)
|
|
iter.Release()
|
|
}
|
|
}
|