// Copyright (c) 2014, Suryandaru Triandana // 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() } }