mirror of
https://github.com/timshannon/bolthold.git
synced 2025-12-24 13:37:58 +08:00
560 lines
14 KiB
Go
560 lines
14 KiB
Go
// Copyright 2016 Tim Shannon. All rights reserved.
|
|
// Use of this source code is governed by the MIT license
|
|
// that can be found in the LICENSE file.
|
|
|
|
package bolthold_test
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/timshannon/bolthold"
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
func testWrapWithBucket(t *testing.T, tests func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T)) {
|
|
filename := tempfile()
|
|
store, err := bolthold.Open(filename, 0666, nil)
|
|
if err != nil {
|
|
t.Fatalf("Error opening %s: %s", filename, err)
|
|
}
|
|
|
|
if store == nil {
|
|
t.Fatalf("store is null!")
|
|
}
|
|
|
|
defer store.Close()
|
|
defer os.Remove(filename)
|
|
|
|
var bucket *bolt.Bucket
|
|
err = store.Bolt().Update(func(tx *bolt.Tx) error {
|
|
bucket, err = tx.CreateBucketIfNotExists([]byte("test bucket parent"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tests(store, bucket, t)
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error creating bucket %s: %s", filename, err)
|
|
}
|
|
}
|
|
|
|
func testWrapWithReadOnlyBucket(t *testing.T, tests func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T)) {
|
|
filename := tempfile()
|
|
store, err := bolthold.Open(filename, 0666, nil)
|
|
if err != nil {
|
|
t.Fatalf("Error opening %s: %s", filename, err)
|
|
}
|
|
|
|
if store == nil {
|
|
t.Fatalf("store is null!")
|
|
}
|
|
|
|
defer store.Close()
|
|
defer os.Remove(filename)
|
|
|
|
var bucket *bolt.Bucket
|
|
err = store.Bolt().Update(func(tx *bolt.Tx) error {
|
|
bucket, err = tx.CreateBucketIfNotExists([]byte("test bucket parent"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error creating bucket %s: %s", filename, err)
|
|
}
|
|
err = store.Bolt().View(func(tx *bolt.Tx) error {
|
|
bucket = tx.Bucket([]byte("test bucket parent"))
|
|
|
|
tests(store, bucket, t)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGetFromBucket(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Created: time.Now(),
|
|
}
|
|
err := store.InsertIntoBucket(bucket, key, data)
|
|
if err != nil {
|
|
t.Fatalf("Error creating data for get test: %s", err)
|
|
}
|
|
|
|
result := &ItemTest{}
|
|
|
|
err = store.Get(key, result)
|
|
if err != bolthold.ErrNotFound {
|
|
t.Fatalf("Expected to not find record")
|
|
}
|
|
|
|
err = store.GetFromBucket(bucket, key, result)
|
|
if err != nil {
|
|
t.Fatalf("Error getting data from bolthold bucket: %s", err)
|
|
}
|
|
|
|
if !data.equal(result) {
|
|
t.Fatalf("Got %v wanted %v.", result, data)
|
|
}
|
|
})
|
|
}
|
|
|
|
func insertBucketTestData(t *testing.T, store *bolthold.Store, bucket *bolt.Bucket) {
|
|
for i := range testData {
|
|
err := store.InsertIntoBucket(bucket, testData[i].Key, testData[i])
|
|
if err != nil {
|
|
t.Fatalf("Error inserting test data for find test: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindInBucket(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
insertBucketTestData(t, store, bucket)
|
|
for _, tst := range testResults {
|
|
t.Run(tst.name, func(t *testing.T) {
|
|
var result []ItemTest
|
|
err := store.FindInBucket(bucket, &result, tst.query)
|
|
if err != nil {
|
|
t.Fatalf("Error finding data from bolthold: %s", err)
|
|
}
|
|
if len(result) != len(tst.result) {
|
|
if testing.Verbose() {
|
|
t.Fatalf("Find result count is %d wanted %d. Results: %v", len(result),
|
|
len(tst.result), result)
|
|
}
|
|
t.Fatalf("Find result count is %d wanted %d.", len(result), len(tst.result))
|
|
}
|
|
|
|
for i := range result {
|
|
found := false
|
|
for k := range tst.result {
|
|
if result[i].equal(&testData[tst.result[k]]) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
if testing.Verbose() {
|
|
t.Fatalf("%v should not be in the result set! Full results: %v",
|
|
result[i], result)
|
|
}
|
|
t.Fatalf("%v should not be in the result set!", result[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBucketCount(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
insertBucketTestData(t, store, bucket)
|
|
for _, tst := range testResults {
|
|
t.Run(tst.name, func(t *testing.T) {
|
|
count, err := store.CountInBucket(bucket, ItemTest{}, tst.query)
|
|
if err != nil {
|
|
t.Fatalf("Error counting data from bolthold: %s", err)
|
|
}
|
|
|
|
if count != len(tst.result) {
|
|
t.Fatalf("Count result is %d wanted %d.", count, len(tst.result))
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFindOneInBucket(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
insertBucketTestData(t, store, bucket)
|
|
for _, tst := range testResults {
|
|
t.Run(tst.name, func(t *testing.T) {
|
|
result := &ItemTest{}
|
|
err := store.FindOneInBucket(bucket, result, tst.query)
|
|
if len(tst.result) == 0 && err == bolthold.ErrNotFound {
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error finding one data from bolthold: %s", err)
|
|
}
|
|
|
|
if !result.equal(&testData[tst.result[0]]) {
|
|
t.Fatalf("Result doesnt match the first record in the testing result set. "+
|
|
"Expected key of %d got %d", &testData[tst.result[0]].Key, result.Key)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInsertBucket(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Category: "Test Category",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.InsertIntoBucket(bucket, key, data)
|
|
if err != nil {
|
|
t.Fatalf("Error inserting data for test: %s", err)
|
|
}
|
|
|
|
result := &ItemTest{}
|
|
|
|
err = store.GetFromBucket(bucket, key, result)
|
|
if err != nil {
|
|
t.Fatalf("Error getting data from bolthold: %s", err)
|
|
}
|
|
|
|
if !data.equal(result) {
|
|
t.Fatalf("Got %v wanted %v.", result, data)
|
|
}
|
|
|
|
// test duplicate insert
|
|
err = store.InsertIntoBucket(bucket, key, &ItemTest{
|
|
Name: "Test Name",
|
|
Created: time.Now(),
|
|
})
|
|
|
|
if err != bolthold.ErrKeyExists {
|
|
t.Fatalf("Insert didn't fail! Expected %s got %s", bolthold.ErrKeyExists, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInsertBucketReadTxn(t *testing.T) {
|
|
testWrapWithReadOnlyBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Category: "Test Category",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.Bolt().View(func(tx *bolt.Tx) error {
|
|
return store.InsertIntoBucket(bucket, key, data)
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Inserting into a read only transaction didn't fail!")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpdateBucket(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Category: "Test Category",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.UpdateBucket(bucket, key, data)
|
|
if err != bolthold.ErrNotFound {
|
|
t.Fatalf("Update without insert didn't fail! Expected %s got %s", bolthold.ErrNotFound, err)
|
|
}
|
|
|
|
err = store.InsertIntoBucket(bucket, key, data)
|
|
if err != nil {
|
|
t.Fatalf("Error creating data for update test: %s", err)
|
|
}
|
|
|
|
result := &ItemTest{}
|
|
|
|
err = store.GetFromBucket(bucket, key, result)
|
|
if err != nil {
|
|
t.Fatalf("Error getting data from bolthold: %s", err)
|
|
}
|
|
|
|
if !data.equal(result) {
|
|
t.Fatalf("Got %v wanted %v.", result, data)
|
|
}
|
|
|
|
update := &ItemTest{
|
|
Name: "Test Name Updated",
|
|
Category: "Test Category Updated",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
// test duplicate insert
|
|
err = store.UpdateBucket(bucket, key, update)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error updating data: %s", err)
|
|
}
|
|
|
|
err = store.GetFromBucket(bucket, key, result)
|
|
if err != nil {
|
|
t.Fatalf("Error getting data from bolthold: %s", err)
|
|
}
|
|
|
|
if !result.equal(update) {
|
|
t.Fatalf("Update didn't complete. Expected %v, got %v", update, result)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpdateBucketReadTxn(t *testing.T) {
|
|
testWrapWithReadOnlyBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Category: "Test Category",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.Bolt().View(func(tx *bolt.Tx) error {
|
|
return store.UpdateBucket(bucket, key, data)
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Updating into a read only transaction didn't fail!")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpsertBucket(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Category: "Test Category",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.UpsertBucket(bucket, key, data)
|
|
if err != nil {
|
|
t.Fatalf("Error upserting data: %s", err)
|
|
}
|
|
|
|
result := &ItemTest{}
|
|
|
|
err = store.GetFromBucket(bucket, key, result)
|
|
if err != nil {
|
|
t.Fatalf("Error getting data from bolthold: %s", err)
|
|
}
|
|
|
|
if !data.equal(result) {
|
|
t.Fatalf("Got %v wanted %v.", result, data)
|
|
}
|
|
|
|
update := &ItemTest{
|
|
Name: "Test Name Updated",
|
|
Category: "Test Category Updated",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
// test duplicate insert
|
|
err = store.UpsertBucket(bucket, key, update)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error updating data: %s", err)
|
|
}
|
|
|
|
err = store.GetFromBucket(bucket, key, result)
|
|
if err != nil {
|
|
t.Fatalf("Error getting data from bolthold: %s", err)
|
|
}
|
|
|
|
if !result.equal(update) {
|
|
t.Fatalf("Upsert didn't complete. Expected %v, got %v", update, result)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpsertBucketReadTxn(t *testing.T) {
|
|
testWrapWithReadOnlyBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Category: "Test Category",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.Bolt().View(func(tx *bolt.Tx) error {
|
|
return store.UpsertBucket(bucket, key, data)
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Updating into a read only transaction didn't fail!")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpdateMatchingBucket(t *testing.T) {
|
|
for _, tst := range testResults {
|
|
t.Run(tst.name, func(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
insertBucketTestData(t, store, bucket)
|
|
|
|
err := store.UpdateMatchingInBucket(bucket, &ItemTest{}, tst.query,
|
|
func(record interface{}) error {
|
|
update, ok := record.(*ItemTest)
|
|
if !ok {
|
|
return fmt.Errorf("Record isn't the correct type! "+
|
|
"Wanted Itemtest, got %T", record)
|
|
}
|
|
|
|
update.UpdateField = "updated"
|
|
update.UpdateIndex = "updated index"
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error updating data from bolthold: %s", err)
|
|
}
|
|
|
|
var result []ItemTest
|
|
err = store.FindInBucket(bucket, &result, bolthold.Where("UpdateIndex").
|
|
Eq("updated index").And("UpdateField").Eq("updated"))
|
|
if err != nil {
|
|
t.Fatalf("Error finding result after update from bolthold: %s", err)
|
|
}
|
|
|
|
if len(result) != len(tst.result) {
|
|
if testing.Verbose() {
|
|
t.Fatalf("Find result count after update is %d wanted %d. Results: %v",
|
|
len(result), len(tst.result), result)
|
|
}
|
|
t.Fatalf("Find result count after update is %d wanted %d.", len(result),
|
|
len(tst.result))
|
|
}
|
|
|
|
for i := range result {
|
|
found := false
|
|
for k := range tst.result {
|
|
if result[i].Key == testData[tst.result[k]].Key &&
|
|
result[i].UpdateField == "updated" &&
|
|
result[i].UpdateIndex == "updated index" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
if testing.Verbose() {
|
|
t.Fatalf("Could not find %v in the update result set! "+
|
|
"Full results: %v", result[i], result)
|
|
}
|
|
t.Fatalf("Could not find %v in the updated result set!", result[i])
|
|
}
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeleteBucket(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.InsertIntoBucket(bucket, key, data)
|
|
if err != nil {
|
|
t.Fatalf("Error inserting data for delete test: %s", err)
|
|
}
|
|
|
|
result := &ItemTest{}
|
|
|
|
err = store.DeleteFromBucket(bucket, key, result)
|
|
if err != nil {
|
|
t.Fatalf("Error deleting data from bolthold: %s", err)
|
|
}
|
|
|
|
err = store.GetFromBucket(bucket, key, result)
|
|
if err != bolthold.ErrNotFound {
|
|
t.Fatalf("Data was not deleted from bolthold")
|
|
}
|
|
|
|
})
|
|
}
|
|
|
|
func TestDeleteBucketReadTxn(t *testing.T) {
|
|
testWrapWithReadOnlyBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
key := "testKey"
|
|
data := &ItemTest{
|
|
Name: "Test Name",
|
|
Created: time.Now(),
|
|
}
|
|
|
|
err := store.Bolt().View(func(tx *bolt.Tx) error {
|
|
return store.DeleteFromBucket(bucket, key, data)
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatalf("Deleting from a read only transaction didn't fail!")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDeleteMatchingBucket(t *testing.T) {
|
|
for _, tst := range testResults {
|
|
t.Run(tst.name, func(t *testing.T) {
|
|
testWrapWithBucket(t, func(store *bolthold.Store, bucket *bolt.Bucket, t *testing.T) {
|
|
|
|
insertBucketTestData(t, store, bucket)
|
|
|
|
err := store.DeleteMatchingFromBucket(bucket, &ItemTest{}, tst.query)
|
|
if err != nil {
|
|
t.Fatalf("Error deleting data from bolthold: %s", err)
|
|
}
|
|
|
|
var result []ItemTest
|
|
err = store.FindInBucket(bucket, &result, nil)
|
|
if err != nil {
|
|
t.Fatalf("Error finding result after delete from bolthold: %s", err)
|
|
}
|
|
|
|
if len(result) != (len(testData) - len(tst.result)) {
|
|
if testing.Verbose() {
|
|
t.Fatalf("Delete result count is %d wanted %d. Results: %v", len(result),
|
|
(len(testData) - len(tst.result)), result)
|
|
}
|
|
t.Fatalf("Delete result count is %d wanted %d.", len(result),
|
|
(len(testData) - len(tst.result)))
|
|
|
|
}
|
|
|
|
for i := range result {
|
|
found := false
|
|
for k := range tst.result {
|
|
if result[i].equal(&testData[tst.result[k]]) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if found {
|
|
if testing.Verbose() {
|
|
t.Fatalf("Found %v in the result set when it should've "+
|
|
"been deleted! Full results: %v", result[i], result)
|
|
}
|
|
t.Fatalf("Found %v in the result set when it should've been deleted!",
|
|
result[i])
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|