492 lines
13 KiB
Go
492 lines
13 KiB
Go
package bitcask
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
c "git.tcp.direct/kayos/common/entropy"
|
|
)
|
|
|
|
func newTestDB(t *testing.T) *DB {
|
|
t.Helper()
|
|
tpath := t.TempDir()
|
|
tdb := OpenDB(tpath)
|
|
if tdb == nil {
|
|
t.Fatalf("failed to open testdb at %s, got nil", tpath)
|
|
}
|
|
return tdb
|
|
}
|
|
|
|
func seedRandKV(db *DB, store string) error {
|
|
return db.With(store).Put([]byte(c.RandStr(55)), []byte(c.RandStr(55)))
|
|
}
|
|
|
|
func seedRandStores(db *DB, t *testing.T) {
|
|
for n := 0; n != 5; n++ {
|
|
randstore := c.RandStr(5)
|
|
err := db.Init(randstore)
|
|
if err != nil {
|
|
t.Errorf("failed to initialize store for test SyncAndCloseAll: %e", err)
|
|
}
|
|
err = seedRandKV(db, randstore)
|
|
if err != nil {
|
|
t.Errorf("failed to initialize random values in store %s for test SyncAndCloseAll: %e", randstore, err)
|
|
}
|
|
}
|
|
t.Logf("seeded random stores with random values for test %s", t.Name())
|
|
}
|
|
|
|
func TestDB_Init(t *testing.T) { //nolint:funlen,gocognit,cyclop
|
|
var db = newTestDB(t)
|
|
type args struct{ storeName string }
|
|
type test struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
specErr error
|
|
}
|
|
tests := []test{
|
|
{
|
|
name: "simple",
|
|
args: args{"simple"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "storeExists",
|
|
args: args{"simple"},
|
|
wantErr: true,
|
|
specErr: ErrStoreExists,
|
|
},
|
|
{
|
|
name: "newStore",
|
|
args: args{"notsimple"},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := db.Init(tt.args.storeName)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("[FAIL] Init() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
if (err != nil) != tt.wantErr && tt.specErr != nil && !errors.Is(err, tt.specErr) {
|
|
t.Errorf("[FAIL] wanted error %e, got error %e", tt.specErr, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("withStoreTest", func(t *testing.T) {
|
|
key := []byte{51, 50}
|
|
value := []byte("string")
|
|
err := db.With("simple").Put(key, value)
|
|
t.Logf("Put Value %v at Key %v", string(value), key)
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] %e", err)
|
|
}
|
|
gvalue, gerr := db.With("simple").Get(key)
|
|
if gerr != nil {
|
|
t.Fatalf("[FAIL] %e", gerr)
|
|
}
|
|
if !bytes.Equal(gvalue, value) {
|
|
t.Errorf("[FAIL] wanted %v, got %v", string(value), string(gvalue))
|
|
}
|
|
t.Logf("Got Value %v at Key %v", string(gvalue), key)
|
|
})
|
|
t.Run("withNewStoreDoesExist", func(t *testing.T) {
|
|
nope := db.WithNew("bing")
|
|
if err := nope.Put([]byte("key"), []byte("value")); err != nil {
|
|
t.Fatalf("[FAIL] %e", err)
|
|
}
|
|
err := nope.Put([]byte("bing"), []byte("bong"))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] %e", err)
|
|
}
|
|
yup := db.WithNew("bing")
|
|
res, err := yup.Get([]byte("bing"))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] %e", err)
|
|
}
|
|
if !bytes.Equal(res, []byte("bong")) {
|
|
t.Errorf("[FAIL] wanted %v, got %v", string([]byte("bong")), string(res))
|
|
}
|
|
})
|
|
t.Run("withNewStoreDoesntExist", func(t *testing.T) {
|
|
if nope := db.WithNew("asdfqwerty"); nope.Backend() == nil {
|
|
t.Fatalf("[FAIL] got nil result for nonexistent store when it should have made itself: %T, %v", nope, nope)
|
|
} else {
|
|
t.Logf("[SUCCESS] got nil Value for store that doesn't exist")
|
|
}
|
|
})
|
|
t.Run("withStoreDoesntExist", func(t *testing.T) {
|
|
nope := db.With(c.RandStr(10))
|
|
if nope != nil {
|
|
t.Fatalf("[FAIL] got non nil result for nonexistent store: %T, %v", nope.Backend(), nope.Backend())
|
|
} else {
|
|
t.Logf("[SUCCESS] got nil Value for store that doesn't exist")
|
|
}
|
|
})
|
|
t.Run("syncAllShouldFail", func(t *testing.T) {
|
|
db.store["wtf"] = Store{}
|
|
t.Cleanup(func() {
|
|
t.Logf("deleting bogus store map entry")
|
|
delete(db.store, "wtf")
|
|
})
|
|
err := db.SyncAll()
|
|
if err == nil {
|
|
t.Fatalf("[FAIL] we should have gotten an error from bogus store map entry")
|
|
}
|
|
t.Logf("[SUCCESS] got compound error: %e", err)
|
|
})
|
|
|
|
// TODO: make sure sync is ACTUALLY sycing instead of only checking for nil err... ( ._. )
|
|
|
|
t.Run("syncAll", func(t *testing.T) {
|
|
err := db.SyncAll()
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] got compound error: %e", err)
|
|
}
|
|
})
|
|
t.Run("closeAll", func(t *testing.T) {
|
|
t.Cleanup(func() {
|
|
err := os.RemoveAll("./testdata")
|
|
if err != nil {
|
|
t.Fatalf("[CLEANUP FAIL] %e", err)
|
|
}
|
|
t.Logf("[CLEANUP] cleaned up ./testdata")
|
|
})
|
|
err := db.CloseAll()
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] got compound error: %e", err)
|
|
}
|
|
db = nil
|
|
})
|
|
t.Run("SyncAndCloseAll", func(t *testing.T) {
|
|
db = newTestDB(t)
|
|
seedRandStores(db, t)
|
|
err := db.SyncAndCloseAll()
|
|
if err != nil {
|
|
t.Errorf("[FAIL] failed to SyncAndCloseAll: %e", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_Sync(t *testing.T) {
|
|
// TODO: make sure sync is ACTUALLY sycing instead of only checking for nil err...
|
|
var db = newTestDB(t)
|
|
seedRandStores(db, t)
|
|
t.Run("Sync", func(t *testing.T) {
|
|
for d := range db.store {
|
|
err := db.With(d).Sync()
|
|
if err != nil {
|
|
t.Errorf("[FAIL] failed to sync %s: %e", d, err)
|
|
} else {
|
|
t.Logf("[+] Sync() successful for %s", d)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_Close(t *testing.T) {
|
|
var db = newTestDB(t)
|
|
defer func() {
|
|
db = nil
|
|
}()
|
|
seedRandStores(db, t)
|
|
var oldstores []string
|
|
t.Run("Close", func(t *testing.T) {
|
|
for d := range db.store {
|
|
oldstores = append(oldstores, d)
|
|
err := db.Close(d)
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to close %s: %e", d, err)
|
|
} else {
|
|
t.Logf("[+] Close() successful for %s", d)
|
|
}
|
|
}
|
|
})
|
|
t.Run("AssureClosed", func(t *testing.T) {
|
|
for _, d := range oldstores {
|
|
if st := db.With(d); st != nil {
|
|
t.Fatalf("[FAIL] store %s should have been deleted", d)
|
|
}
|
|
}
|
|
t.Logf("[SUCCESS] Confirmed that all stores have been closed")
|
|
})
|
|
|
|
t.Run("CantCloseBogusStore", func(t *testing.T) {
|
|
err := db.Close(c.RandStr(55))
|
|
if !errors.Is(err, ErrBogusStore) {
|
|
t.Errorf("[FAIL] got err %e, wanted err %e", err, ErrBogusStore)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_withAll(t *testing.T) {
|
|
var db = newTestDB(t)
|
|
defer db.CloseAll()
|
|
t.Run("withAllNoStores", func(t *testing.T) {
|
|
err := db.withAll(121)
|
|
if !errors.Is(err, ErrNoStores) {
|
|
t.Errorf("[FAIL] got err %e, wanted err %e", err, ErrNoStores)
|
|
}
|
|
})
|
|
t.Run("withAllNilMap", func(t *testing.T) {
|
|
nilDb := newTestDB(t)
|
|
nilDb.store = nil
|
|
err := nilDb.withAll(dclose)
|
|
if err == nil {
|
|
t.Errorf("[FAIL] got nil err from trying to work on nil map, wanted err")
|
|
}
|
|
})
|
|
t.Run("withAllBogusAction", func(t *testing.T) {
|
|
err := db.Init("asdf")
|
|
if err != nil {
|
|
t.Errorf("[FAIL] unexpected error: %e", err)
|
|
}
|
|
wAllErr := db.withAll(121)
|
|
if !errors.Is(wAllErr, ErrUnknownAction) {
|
|
t.Errorf("[FAIL] wanted error %e, got error %e", ErrUnknownAction, err)
|
|
}
|
|
})
|
|
t.Run("ListAll", func(t *testing.T) {
|
|
allStores := db.AllStores()
|
|
if len(allStores) == 0 {
|
|
t.Errorf("[FAIL] no stores found")
|
|
}
|
|
for n, s := range allStores {
|
|
if n == "" {
|
|
t.Errorf("[FAIL] store name is empty")
|
|
}
|
|
if s == nil {
|
|
t.Errorf("[FAIL] store is nil")
|
|
}
|
|
t.Logf("[+] found store named %s: %v", n, s)
|
|
}
|
|
if len(allStores) != len(db.store) {
|
|
t.Errorf("[FAIL] found %d stores, expected %d", len(allStores), len(db.store))
|
|
}
|
|
})
|
|
t.Run("ListAllAndInteract", func(t *testing.T) {
|
|
err := db.Init("asdf2")
|
|
if err != nil {
|
|
t.Errorf("[FAIL] unexpected error: %e", err)
|
|
}
|
|
err = db.With("asdf").Put([]byte("asdf"), []byte("asdf"))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] unexpected error: %e", err)
|
|
}
|
|
err = db.With("asdf2").Put([]byte("asdf2"), []byte("asdf2"))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] unexpected error: %e", err)
|
|
}
|
|
allStores := db.AllStores()
|
|
if len(allStores) == 0 {
|
|
t.Errorf("[FAIL] no stores found")
|
|
}
|
|
for n, s := range allStores {
|
|
if n == "" {
|
|
t.Errorf("[FAIL] store name is empty")
|
|
}
|
|
if s == nil {
|
|
t.Errorf("[FAIL] store is nil")
|
|
}
|
|
if len(db.store) != 2 {
|
|
t.Errorf("[SANITY FAIL] found %d stores, expected %d", len(allStores), len(db.store))
|
|
}
|
|
t.Logf("[+] found store named %s: %v", n, s)
|
|
if len(allStores) != len(db.store) {
|
|
t.Errorf("[FAIL] found %d stores, expected %d", len(allStores), len(db.store))
|
|
}
|
|
var res []byte
|
|
res, err = db.With(n).Get([]byte(n))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] unexpected error: %e", err)
|
|
}
|
|
if !bytes.Equal(res, []byte(n)) {
|
|
t.Errorf("[FAIL] expected %s, got %s", n, res)
|
|
} else {
|
|
t.Logf("[+] found %s in store %s", res, n)
|
|
}
|
|
}
|
|
})
|
|
t.Run("WithAllIncludingBadStore", func(t *testing.T) {
|
|
db.store["yeeterson"] = Store{}
|
|
err := db.withAll(dclose)
|
|
if err != nil {
|
|
t.Logf(err.Error())
|
|
}
|
|
if err == nil {
|
|
t.Errorf("[FAIL] got nil err, wanted any error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_WithOptions(t *testing.T) { //nolint:funlen,gocognit,cyclop
|
|
tpath := t.TempDir()
|
|
tdb := OpenDB(tpath)
|
|
if tdb == nil {
|
|
t.Fatalf("failed to open testdb at %s, got nil", tpath)
|
|
}
|
|
defer func() {
|
|
err := tdb.CloseAll()
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to close testdb: %e", err)
|
|
}
|
|
}()
|
|
t.Run("WithMaxKeySize", func(t *testing.T) {
|
|
err := tdb.Init(t.Name(), WithMaxKeySize(10))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to init testdb for %s: %e", t.Name(), err)
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte(c.RandStr(10)), []byte("asdf"))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte(c.RandStr(11)), []byte("asdf"))
|
|
if err == nil {
|
|
t.Errorf("[FAIL] expected error while using a key larger than the max key value option, got nil")
|
|
}
|
|
})
|
|
t.Run("WithMaxValueSize", func(t *testing.T) {
|
|
err := tdb.Init(t.Name(), WithMaxValueSize(10))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to init testdb for %s: %e", t.Name(), err)
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(10)))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(11)))
|
|
if err == nil {
|
|
t.Errorf("[FAIL] expected error while using a value larger than the max key value option, got nil")
|
|
}
|
|
})
|
|
t.Run("WithMaxDataFileSize", func(t *testing.T) {
|
|
err := tdb.Init(t.Name(), WithMaxDatafileSize(10))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to init testdb for %s: %e", t.Name(), err)
|
|
}
|
|
checkDir := func() int {
|
|
targetDir := tpath + "/" + t.Name()
|
|
var files []os.DirEntry
|
|
files, err = os.ReadDir(targetDir)
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to read directory %s: %e", targetDir, err)
|
|
}
|
|
datafilecount := 0
|
|
for _, file := range files {
|
|
if strings.Contains(file.Name(), ".data") {
|
|
datafilecount++
|
|
}
|
|
}
|
|
return datafilecount
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(8)))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
if checkDir() != 1 {
|
|
t.Errorf("[FAIL] expected 1 datafile, got %d", checkDir())
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(10)))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
if checkDir() != 2 {
|
|
t.Errorf("[FAIL] expected 2 datafile, got %d", checkDir())
|
|
}
|
|
})
|
|
t.Run("SetDefaultBitcaskOptions", func(t *testing.T) {
|
|
SetDefaultBitcaskOptions(
|
|
WithMaxKeySize(20),
|
|
WithMaxValueSize(20),
|
|
WithMaxDatafileSize(20),
|
|
)
|
|
err := tdb.Init(t.Name())
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to init testdb for %s: %e", t.Name(), err)
|
|
}
|
|
checkDir := func() int {
|
|
targetDir := tpath + "/" + t.Name()
|
|
var files []os.DirEntry
|
|
files, err = os.ReadDir(targetDir)
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to read directory %s: %e", targetDir, err)
|
|
}
|
|
datafilecount := 0
|
|
for _, file := range files {
|
|
if strings.Contains(file.Name(), ".data") {
|
|
datafilecount++
|
|
}
|
|
}
|
|
return datafilecount
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte(c.RandStr(20)), []byte("asdf"))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte(c.RandStr(21)), []byte("asdf"))
|
|
if err == nil {
|
|
t.Errorf("[FAIL] expected error while using a key larger than the max key value option, got nil")
|
|
}
|
|
//
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(9)))
|
|
if err != nil {
|
|
t.Errorf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(21)))
|
|
if err == nil {
|
|
t.Errorf("[FAIL] expected error while using a value larger than the max key value option, got nil")
|
|
}
|
|
//
|
|
if checkDir() != 2 {
|
|
t.Fatalf("[FAIL] expected 2 datafiles, got %d", checkDir())
|
|
}
|
|
//
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(11)))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
if checkDir() != 3 {
|
|
t.Fatalf("[FAIL] expected 3 datafiles, got %d", checkDir())
|
|
}
|
|
err = tdb.With(t.Name()).Put([]byte("asdf"), []byte(c.RandStr(10)))
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to put key: %e", err)
|
|
}
|
|
if checkDir() != 4 {
|
|
t.Errorf("[FAIL] expected 4 datafile, got %d", checkDir())
|
|
}
|
|
})
|
|
t.Run("InitWithBogusOption", func(t *testing.T) {
|
|
db := newTestDB(t)
|
|
err := db.Init("bogus", "yeet")
|
|
if err == nil {
|
|
t.Errorf("[FAIL] Init should have failed with bogus option")
|
|
}
|
|
})
|
|
}
|
|
func Test_PhonyInit(t *testing.T) {
|
|
newtmp := t.TempDir()
|
|
err := os.MkdirAll(newtmp+"/"+t.Name(), 0755)
|
|
if err != nil {
|
|
t.Fatalf("[FAIL] failed to create test directory: %e", err)
|
|
}
|
|
err = os.Symlink("/dev/null", newtmp+"/"+t.Name()+"/config.json")
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
tdb := OpenDB(newtmp)
|
|
defer func() {
|
|
_ = tdb.CloseAll()
|
|
}()
|
|
err = tdb.Init(t.Name())
|
|
if err == nil {
|
|
t.Error("[FAIL] expected error while trying to open a store where a config file exists, got nil")
|
|
}
|
|
}
|