johnsonjh/jleveldb

View on GitHub
leveldb/testutil/db.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright © 2014, Suryandaru Triandana <syndtr@gmail.com>
// Copyright © 2021, Jeffrey H. Johnson <trnsz@pobox.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/johnsonjh/jleveldb/leveldb/errors"
    "github.com/johnsonjh/jleveldb/leveldb/iterator"
    "github.com/johnsonjh/jleveldb/leveldb/util"
)

type DB interface{}

type Put interface {
    TestPut(key, 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()
    }
}