aboutsummaryrefslogtreecommitdiff
path: root/db_test.go
diff options
context:
space:
mode:
authorBen Johnson <benbjohnson@yahoo.com>2015-12-27 15:27:39 -0700
committerBen Johnson <benbjohnson@yahoo.com>2016-01-02 21:30:31 -0700
commit8b08bd4a8065cb7a240761c3683e8f837b06cc3c (patch)
tree26a01c0d5a3da53f45c7f54062c3af11479c4a2f /db_test.go
parentMerge pull request #474 from elithrar/patch-1 (diff)
downloaddedo-8b08bd4a8065cb7a240761c3683e8f837b06cc3c.tar.gz
dedo-8b08bd4a8065cb7a240761c3683e8f837b06cc3c.tar.xz
test suite refactoring
This commit refactors the test suite to make it cleaner and to use the standard testing library better. The `assert()`, `equals()`, and `ok()` functions have been removed and some test names have been changed for clarity. No functionality has been changed.
Diffstat (limited to 'db_test.go')
-rw-r--r--db_test.go1290
1 files changed, 973 insertions, 317 deletions
diff --git a/db_test.go b/db_test.go
index 197071b..c1346b8 100644
--- a/db_test.go
+++ b/db_test.go
@@ -1,17 +1,21 @@
package bolt_test
import (
+ "bytes"
"encoding/binary"
"errors"
"flag"
"fmt"
+ "hash/fnv"
"io/ioutil"
+ "log"
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
+ "sync"
"testing"
"time"
"unsafe"
@@ -50,24 +54,34 @@ type meta struct {
func TestOpen(t *testing.T) {
path := tempfile()
db, err := bolt.Open(path, 0666, nil)
- assert(t, db != nil, "")
- ok(t, err)
- equals(t, db.Path(), path)
- ok(t, db.Close())
+ if err != nil {
+ t.Fatal(err)
+ } else if db == nil {
+ t.Fatal("expected db")
+ }
+
+ if s := db.Path(); s != path {
+ t.Fatalf("unexpected path: %s", s)
+ }
+
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Ensure that opening a database with a blank path returns an error.
+func TestOpen_ErrPathRequired(t *testing.T) {
+ _, err := bolt.Open("", 0666, nil)
+ if err == nil {
+ t.Fatalf("expected error")
+ }
}
// Ensure that opening a database with a bad path returns an error.
-func TestOpen_BadPath(t *testing.T) {
- for _, path := range []string{
- "",
- filepath.Join(tempfile(), "youre-not-my-real-parent"),
- } {
- t.Logf("path = %q", path)
- db, err := bolt.Open(path, 0666, nil)
- assert(t, err != nil, "err: %s", err)
- equals(t, path, err.(*os.PathError).Path)
- equals(t, "open", err.(*os.PathError).Op)
- equals(t, (*bolt.DB)(nil), db)
+func TestOpen_ErrNotExists(t *testing.T) {
+ _, err := bolt.Open(filepath.Join(tempfile(), "bad-path"), 0666, nil)
+ if err == nil {
+ t.Fatal("expected error")
}
}
@@ -81,13 +95,20 @@ func TestOpen_ErrChecksum(t *testing.T) {
path := tempfile()
f, err := os.Create(path)
- equals(t, nil, err)
- f.WriteAt(buf, pageHeaderSize)
- f.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := f.WriteAt(buf, pageHeaderSize); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
defer os.Remove(path)
- _, err = bolt.Open(path, 0666, nil)
- equals(t, bolt.ErrChecksum, err)
+ if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrChecksum {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
// Ensure that opening a file that is not a Bolt database returns ErrInvalid.
@@ -95,13 +116,20 @@ func TestOpen_ErrInvalid(t *testing.T) {
path := tempfile()
f, err := os.Create(path)
- equals(t, nil, err)
- fmt.Fprintln(f, "this is not a bolt database")
- f.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := fmt.Fprintln(f, "this is not a bolt database"); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
defer os.Remove(path)
- _, err = bolt.Open(path, 0666, nil)
- equals(t, bolt.ErrInvalid, err)
+ if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrInvalid {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
// Ensure that opening a file created with a different version of Bolt returns
@@ -114,13 +142,20 @@ func TestOpen_ErrVersionMismatch(t *testing.T) {
path := tempfile()
f, err := os.Create(path)
- equals(t, nil, err)
- f.WriteAt(buf, pageHeaderSize)
- f.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := f.WriteAt(buf, pageHeaderSize); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
defer os.Remove(path)
- _, err = bolt.Open(path, 0666, nil)
- equals(t, bolt.ErrVersionMismatch, err)
+ if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrVersionMismatch {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
// Ensure that opening an already open database file will timeout.
@@ -133,17 +168,26 @@ func TestOpen_Timeout(t *testing.T) {
// Open a data file.
db0, err := bolt.Open(path, 0666, nil)
- assert(t, db0 != nil, "")
- ok(t, err)
+ if err != nil {
+ t.Fatal(err)
+ } else if db0 == nil {
+ t.Fatal("expected database")
+ }
// Attempt to open the database again.
start := time.Now()
db1, err := bolt.Open(path, 0666, &bolt.Options{Timeout: 100 * time.Millisecond})
- assert(t, db1 == nil, "")
- equals(t, bolt.ErrTimeout, err)
- assert(t, time.Since(start) > 100*time.Millisecond, "")
+ if err != bolt.ErrTimeout {
+ t.Fatalf("unexpected timeout: %s", err)
+ } else if db1 != nil {
+ t.Fatal("unexpected database")
+ } else if time.Since(start) <= 100*time.Millisecond {
+ t.Fatal("expected to wait at least timeout duration")
+ }
- db0.Close()
+ if err := db0.Close(); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure that opening an already open database file will wait until its closed.
@@ -156,39 +200,52 @@ func TestOpen_Wait(t *testing.T) {
// Open a data file.
db0, err := bolt.Open(path, 0666, nil)
- assert(t, db0 != nil, "")
- ok(t, err)
+ if err != nil {
+ t.Fatal(err)
+ }
// Close it in just a bit.
- time.AfterFunc(100*time.Millisecond, func() { db0.Close() })
+ time.AfterFunc(100*time.Millisecond, func() { _ = db0.Close() })
// Attempt to open the database again.
start := time.Now()
db1, err := bolt.Open(path, 0666, &bolt.Options{Timeout: 200 * time.Millisecond})
- assert(t, db1 != nil, "")
- ok(t, err)
- assert(t, time.Since(start) > 100*time.Millisecond, "")
+ if err != nil {
+ t.Fatal(err)
+ } else if time.Since(start) <= 100*time.Millisecond {
+ t.Fatal("expected to wait at least timeout duration")
+ }
+
+ if err := db1.Close(); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure that opening a database does not increase its size.
// https://github.com/boltdb/bolt/issues/291
func TestOpen_Size(t *testing.T) {
// Open a data file.
- db := NewTestDB()
+ db := MustOpenDB()
path := db.Path()
- defer db.Close()
+ defer db.MustClose()
// Insert until we get above the minimum 4MB size.
- ok(t, db.Update(func(tx *bolt.Tx) error {
+ if err := db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucketIfNotExists([]byte("data"))
for i := 0; i < 10000; i++ {
- ok(t, b.Put([]byte(fmt.Sprintf("%04d", i)), make([]byte, 1000)))
+ if err := b.Put([]byte(fmt.Sprintf("%04d", i)), make([]byte, 1000)); err != nil {
+ t.Fatal(err)
+ }
}
return nil
- }))
+ }); err != nil {
+ t.Fatal(err)
+ }
// Close database and grab the size.
- db.DB.Close()
+ if err := db.DB.Close(); err != nil {
+ t.Fatal(err)
+ }
sz := fileSize(path)
if sz == 0 {
t.Fatalf("unexpected new file size: %d", sz)
@@ -196,9 +253,20 @@ func TestOpen_Size(t *testing.T) {
// Reopen database, update, and check size again.
db0, err := bolt.Open(path, 0666, nil)
- ok(t, err)
- ok(t, db0.Update(func(tx *bolt.Tx) error { return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}) }))
- ok(t, db0.Close())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := db0.Update(func(tx *bolt.Tx) error {
+ if err := tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}); err != nil {
+ t.Fatal(err)
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+ if err := db0.Close(); err != nil {
+ t.Fatal(err)
+ }
newSz := fileSize(path)
if newSz == 0 {
t.Fatalf("unexpected new file size: %d", newSz)
@@ -218,25 +286,31 @@ func TestOpen_Size_Large(t *testing.T) {
}
// Open a data file.
- db := NewTestDB()
+ db := MustOpenDB()
path := db.Path()
- defer db.Close()
+ defer db.MustClose()
// Insert until we get above the minimum 4MB size.
var index uint64
for i := 0; i < 10000; i++ {
- ok(t, db.Update(func(tx *bolt.Tx) error {
+ if err := db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucketIfNotExists([]byte("data"))
for j := 0; j < 1000; j++ {
- ok(t, b.Put(u64tob(index), make([]byte, 50)))
+ if err := b.Put(u64tob(index), make([]byte, 50)); err != nil {
+ t.Fatal(err)
+ }
index++
}
return nil
- }))
+ }); err != nil {
+ t.Fatal(err)
+ }
}
// Close database and grab the size.
- db.DB.Close()
+ if err := db.DB.Close(); err != nil {
+ t.Fatal(err)
+ }
sz := fileSize(path)
if sz == 0 {
t.Fatalf("unexpected new file size: %d", sz)
@@ -246,9 +320,18 @@ func TestOpen_Size_Large(t *testing.T) {
// Reopen database, update, and check size again.
db0, err := bolt.Open(path, 0666, nil)
- ok(t, err)
- ok(t, db0.Update(func(tx *bolt.Tx) error { return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}) }))
- ok(t, db0.Close())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := db0.Update(func(tx *bolt.Tx) error {
+ return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0})
+ }); err != nil {
+ t.Fatal(err)
+ }
+ if err := db0.Close(); err != nil {
+ t.Fatal(err)
+ }
+
newSz := fileSize(path)
if newSz == 0 {
t.Fatalf("unexpected new file size: %d", newSz)
@@ -265,14 +348,26 @@ func TestOpen_Check(t *testing.T) {
path := tempfile()
db, err := bolt.Open(path, 0666, nil)
- ok(t, err)
- ok(t, db.View(func(tx *bolt.Tx) error { return <-tx.Check() }))
- db.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
+ t.Fatal(err)
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
db, err = bolt.Open(path, 0666, nil)
- ok(t, err)
- ok(t, db.View(func(tx *bolt.Tx) error { return <-tx.Check() }))
- db.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
+ t.Fatal(err)
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure that write errors to the meta file handler during initialization are returned.
@@ -285,14 +380,22 @@ func TestOpen_FileTooSmall(t *testing.T) {
path := tempfile()
db, err := bolt.Open(path, 0666, nil)
- ok(t, err)
- db.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
// corrupt the database
- ok(t, os.Truncate(path, int64(os.Getpagesize())))
+ if err := os.Truncate(path, int64(os.Getpagesize())); err != nil {
+ t.Fatal(err)
+ }
db, err = bolt.Open(path, 0666, nil)
- equals(t, errors.New("file size too small"), err)
+ if err == nil || err.Error() != "file size too small" {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
// Ensure that a database can be opened in read-only mode by multiple processes
@@ -309,50 +412,65 @@ func TestOpen_ReadOnly(t *testing.T) {
// Open in read-write mode.
db, err := bolt.Open(path, 0666, nil)
- ok(t, db.Update(func(tx *bolt.Tx) error {
+ if err != nil {
+ t.Fatal(err)
+ } else if db.IsReadOnly() {
+ t.Fatal("db should not be in read only mode")
+ }
+ if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket(bucket)
if err != nil {
return err
}
- return b.Put(key, value)
- }))
- assert(t, db != nil, "")
- assert(t, !db.IsReadOnly(), "")
- ok(t, err)
- ok(t, db.Close())
+ if err := b.Put(key, value); err != nil {
+ t.Fatal(err)
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
// Open in read-only mode.
db0, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
- ok(t, err)
- defer db0.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
// Opening in read-write mode should return an error.
- _, err = bolt.Open(path, 0666, &bolt.Options{Timeout: time.Millisecond * 100})
- assert(t, err != nil, "")
+ if _, err = bolt.Open(path, 0666, &bolt.Options{Timeout: time.Millisecond * 100}); err == nil {
+ t.Fatal("expected error")
+ }
// And again (in read-only mode).
db1, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
- ok(t, err)
- defer db1.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
// Verify both read-only databases are accessible.
for _, db := range []*bolt.DB{db0, db1} {
// Verify is is in read only mode indeed.
- assert(t, db.IsReadOnly(), "")
+ if !db.IsReadOnly() {
+ t.Fatal("expected read only mode")
+ }
// Read-only databases should not allow updates.
- assert(t,
- bolt.ErrDatabaseReadOnly == db.Update(func(*bolt.Tx) error {
- panic(`should never get here`)
- }),
- "")
+ if err := db.Update(func(*bolt.Tx) error {
+ panic(`should never get here`)
+ }); err != bolt.ErrDatabaseReadOnly {
+ t.Fatalf("unexpected error: %s", err)
+ }
// Read-only databases should not allow beginning writable txns.
- _, err = db.Begin(true)
- assert(t, bolt.ErrDatabaseReadOnly == err, "")
+ if _, err := db.Begin(true); err != bolt.ErrDatabaseReadOnly {
+ t.Fatalf("unexpected error: %s", err)
+ }
// Verify the data.
- ok(t, db.View(func(tx *bolt.Tx) error {
+ if err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucket)
if b == nil {
return fmt.Errorf("expected bucket `%s`", string(bucket))
@@ -364,7 +482,16 @@ func TestOpen_ReadOnly(t *testing.T) {
return fmt.Errorf("expected `%s`, got `%s`", expected, got)
}
return nil
- }))
+ }); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if err := db0.Close(); err != nil {
+ t.Fatal(err)
+ }
+ if err := db1.Close(); err != nil {
+ t.Fatal(err)
}
}
@@ -380,29 +507,40 @@ func TestDB_Open_InitialMmapSize(t *testing.T) {
testWriteSize := 1 << 27 // 134MB
db, err := bolt.Open(path, 0666, &bolt.Options{InitialMmapSize: initMmapSize})
- assert(t, err == nil, "")
+ if err != nil {
+ t.Fatal(err)
+ }
// create a long-running read transaction
// that never gets closed while writing
rtx, err := db.Begin(false)
- assert(t, err == nil, "")
- defer rtx.Rollback()
+ if err != nil {
+ t.Fatal(err)
+ }
// create a write transaction
wtx, err := db.Begin(true)
- assert(t, err == nil, "")
+ if err != nil {
+ t.Fatal(err)
+ }
b, err := wtx.CreateBucket([]byte("test"))
- assert(t, err == nil, "")
+ if err != nil {
+ t.Fatal(err)
+ }
// and commit a large write
err = b.Put([]byte("foo"), make([]byte, testWriteSize))
- assert(t, err == nil, "")
+ if err != nil {
+ t.Fatal(err)
+ }
done := make(chan struct{})
go func() {
- wtx.Commit()
+ if err := wtx.Commit(); err != nil {
+ t.Fatal(err)
+ }
done <- struct{}{}
}()
@@ -411,36 +549,49 @@ func TestDB_Open_InitialMmapSize(t *testing.T) {
t.Errorf("unexpected that the reader blocks writer")
case <-done:
}
-}
-// TODO(benbjohnson): Test corruption at every byte of the first two pages.
+ if err := rtx.Rollback(); err != nil {
+ t.Fatal(err)
+ }
+}
// Ensure that a database cannot open a transaction when it's not open.
-func TestDB_Begin_DatabaseNotOpen(t *testing.T) {
+func TestDB_Begin_ErrDatabaseNotOpen(t *testing.T) {
var db bolt.DB
- tx, err := db.Begin(false)
- assert(t, tx == nil, "")
- equals(t, err, bolt.ErrDatabaseNotOpen)
+ if _, err := db.Begin(false); err != bolt.ErrDatabaseNotOpen {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
// Ensure that a read-write transaction can be retrieved.
func TestDB_BeginRW(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
+ db := MustOpenDB()
+ defer db.MustClose()
+
tx, err := db.Begin(true)
- assert(t, tx != nil, "")
- ok(t, err)
- assert(t, tx.DB() == db.DB, "")
- equals(t, tx.Writable(), true)
- ok(t, tx.Commit())
+ if err != nil {
+ t.Fatal(err)
+ } else if tx == nil {
+ t.Fatal("expected tx")
+ }
+
+ if tx.DB() != db.DB {
+ t.Fatal("unexpected tx database")
+ } else if !tx.Writable() {
+ t.Fatal("expected writable tx")
+ }
+
+ if err := tx.Commit(); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure that opening a transaction while the DB is closed returns an error.
func TestDB_BeginRW_Closed(t *testing.T) {
var db bolt.DB
- tx, err := db.Begin(true)
- equals(t, err, bolt.ErrDatabaseNotOpen)
- assert(t, tx == nil, "")
+ if _, err := db.Begin(true); err != bolt.ErrDatabaseNotOpen {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
func TestDB_Close_PendingTx_RW(t *testing.T) { testDB_Close_PendingTx(t, true) }
@@ -448,8 +599,8 @@ func TestDB_Close_PendingTx_RO(t *testing.T) { testDB_Close_PendingTx(t, false)
// Ensure that a database cannot close while transactions are open.
func testDB_Close_PendingTx(t *testing.T, writable bool) {
- db := NewTestDB()
- defer db.Close()
+ db := MustOpenDB()
+ defer db.MustClose()
// Start transaction.
tx, err := db.Begin(true)
@@ -460,7 +611,9 @@ func testDB_Close_PendingTx(t *testing.T, writable bool) {
// Open update in separate goroutine.
done := make(chan struct{})
go func() {
- db.Close()
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
close(done)
}()
@@ -488,247 +641,343 @@ func testDB_Close_PendingTx(t *testing.T, writable bool) {
// Ensure a database can provide a transactional block.
func TestDB_Update(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
- err := db.Update(func(tx *bolt.Tx) error {
- tx.CreateBucket([]byte("widgets"))
- b := tx.Bucket([]byte("widgets"))
- b.Put([]byte("foo"), []byte("bar"))
- b.Put([]byte("baz"), []byte("bat"))
- b.Delete([]byte("foo"))
+ db := MustOpenDB()
+ defer db.MustClose()
+ if err := db.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucket([]byte("widgets"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
+ t.Fatal(err)
+ }
+ if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
+ t.Fatal(err)
+ }
+ if err := b.Delete([]byte("foo")); err != nil {
+ t.Fatal(err)
+ }
return nil
- })
- ok(t, err)
- err = db.View(func(tx *bolt.Tx) error {
- assert(t, tx.Bucket([]byte("widgets")).Get([]byte("foo")) == nil, "")
- equals(t, []byte("bat"), tx.Bucket([]byte("widgets")).Get([]byte("baz")))
+ }); err != nil {
+ t.Fatal(err)
+ }
+ if err := db.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("widgets"))
+ if v := b.Get([]byte("foo")); v != nil {
+ t.Fatalf("expected nil value, got: %v", v)
+ }
+ if v := b.Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) {
+ t.Fatalf("unexpected value: %v", v)
+ }
return nil
- })
- ok(t, err)
+ }); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure a closed database returns an error while running a transaction block
func TestDB_Update_Closed(t *testing.T) {
var db bolt.DB
- err := db.Update(func(tx *bolt.Tx) error {
- tx.CreateBucket([]byte("widgets"))
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
+ t.Fatal(err)
+ }
return nil
- })
- equals(t, err, bolt.ErrDatabaseNotOpen)
+ }); err != bolt.ErrDatabaseNotOpen {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
// Ensure a panic occurs while trying to commit a managed transaction.
func TestDB_Update_ManualCommit(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
+ db := MustOpenDB()
+ defer db.MustClose()
- var ok bool
- db.Update(func(tx *bolt.Tx) error {
+ var panicked bool
+ if err := db.Update(func(tx *bolt.Tx) error {
func() {
defer func() {
if r := recover(); r != nil {
- ok = true
+ panicked = true
}
}()
- tx.Commit()
+
+ if err := tx.Commit(); err != nil {
+ t.Fatal(err)
+ }
}()
return nil
- })
- assert(t, ok, "expected panic")
+ }); err != nil {
+ t.Fatal(err)
+ } else if !panicked {
+ t.Fatal("expected panic")
+ }
}
// Ensure a panic occurs while trying to rollback a managed transaction.
func TestDB_Update_ManualRollback(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
+ db := MustOpenDB()
+ defer db.MustClose()
- var ok bool
- db.Update(func(tx *bolt.Tx) error {
+ var panicked bool
+ if err := db.Update(func(tx *bolt.Tx) error {
func() {
defer func() {
if r := recover(); r != nil {
- ok = true
+ panicked = true
}
}()
- tx.Rollback()
+
+ if err := tx.Rollback(); err != nil {
+ t.Fatal(err)
+ }
}()
return nil
- })
- assert(t, ok, "expected panic")
+ }); err != nil {
+ t.Fatal(err)
+ } else if !panicked {
+ t.Fatal("expected panic")
+ }
}
// Ensure a panic occurs while trying to commit a managed transaction.
func TestDB_View_ManualCommit(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
+ db := MustOpenDB()
+ defer db.MustClose()
- var ok bool
- db.Update(func(tx *bolt.Tx) error {
+ var panicked bool
+ if err := db.View(func(tx *bolt.Tx) error {
func() {
defer func() {
if r := recover(); r != nil {
- ok = true
+ panicked = true
}
}()
- tx.Commit()
+
+ if err := tx.Commit(); err != nil {
+ t.Fatal(err)
+ }
}()
return nil
- })
- assert(t, ok, "expected panic")
+ }); err != nil {
+ t.Fatal(err)
+ } else if !panicked {
+ t.Fatal("expected panic")
+ }
}
// Ensure a panic occurs while trying to rollback a managed transaction.
func TestDB_View_ManualRollback(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
+ db := MustOpenDB()
+ defer db.MustClose()
- var ok bool
- db.Update(func(tx *bolt.Tx) error {
+ var panicked bool
+ if err := db.View(func(tx *bolt.Tx) error {
func() {
defer func() {
if r := recover(); r != nil {
- ok = true
+ panicked = true
}
}()
- tx.Rollback()
+
+ if err := tx.Rollback(); err != nil {
+ t.Fatal(err)
+ }
}()
return nil
- })
- assert(t, ok, "expected panic")
+ }); err != nil {
+ t.Fatal(err)
+ } else if !panicked {
+ t.Fatal("expected panic")
+ }
}
// Ensure a write transaction that panics does not hold open locks.
func TestDB_Update_Panic(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
+ db := MustOpenDB()
+ defer db.MustClose()
+ // Panic during update but recover.
func() {
defer func() {
if r := recover(); r != nil {
t.Log("recover: update", r)
}
}()
- db.Update(func(tx *bolt.Tx) error {
- tx.CreateBucket([]byte("widgets"))
+
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
+ t.Fatal(err)
+ }
panic("omg")
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
}()
// Verify we can update again.
- err := db.Update(func(tx *bolt.Tx) error {
- _, err := tx.CreateBucket([]byte("widgets"))
- return err
- })
- ok(t, err)
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
+ t.Fatal(err)
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
// Verify that our change persisted.
- err = db.Update(func(tx *bolt.Tx) error {
- assert(t, tx.Bucket([]byte("widgets")) != nil, "")
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if tx.Bucket([]byte("widgets")) == nil {
+ t.Fatal("expected bucket")
+ }
return nil
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure a database can return an error through a read-only transactional block.
func TestDB_View_Error(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
- err := db.View(func(tx *bolt.Tx) error {
+ db := MustOpenDB()
+ defer db.MustClose()
+
+ if err := db.View(func(tx *bolt.Tx) error {
return errors.New("xxx")
- })
- equals(t, errors.New("xxx"), err)
+ }); err == nil || err.Error() != "xxx" {
+ t.Fatalf("unexpected error: %s", err)
+ }
}
// Ensure a read transaction that panics does not hold open locks.
func TestDB_View_Panic(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
- db.Update(func(tx *bolt.Tx) error {
- tx.CreateBucket([]byte("widgets"))
+ db := MustOpenDB()
+ defer db.MustClose()
+
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
+ t.Fatal(err)
+ }
return nil
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
+ // Panic during view transaction but recover.
func() {
defer func() {
if r := recover(); r != nil {
t.Log("recover: view", r)
}
}()
- db.View(func(tx *bolt.Tx) error {
- assert(t, tx.Bucket([]byte("widgets")) != nil, "")
+
+ if err := db.View(func(tx *bolt.Tx) error {
+ if tx.Bucket([]byte("widgets")) == nil {
+ t.Fatal("expected bucket")
+ }
panic("omg")
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
}()
// Verify that we can still use read transactions.
- db.View(func(tx *bolt.Tx) error {
- assert(t, tx.Bucket([]byte("widgets")) != nil, "")
+ if err := db.View(func(tx *bolt.Tx) error {
+ if tx.Bucket([]byte("widgets")) == nil {
+ t.Fatal("expected bucket")
+ }
return nil
- })
-}
-
-// Ensure that an error is returned when a database write fails.
-func TestDB_Commit_WriteFail(t *testing.T) {
- t.Skip("pending") // TODO(benbjohnson)
+ }); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure that DB stats can be returned.
func TestDB_Stats(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
- db.Update(func(tx *bolt.Tx) error {
+ db := MustOpenDB()
+ defer db.MustClose()
+ if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
+
stats := db.Stats()
- equals(t, 2, stats.TxStats.PageCount)
- equals(t, 0, stats.FreePageN)
- equals(t, 2, stats.PendingPageN)
+ if stats.TxStats.PageCount != 2 {
+ t.Fatalf("unexpected TxStats.PageCount", stats.TxStats.PageCount)
+ } else if stats.FreePageN != 0 {
+ t.Fatalf("unexpected FreePageN != 0", stats.FreePageN)
+ } else if stats.PendingPageN != 2 {
+ t.Fatalf("unexpected PendingPageN != 2", stats.PendingPageN)
+ }
}
// Ensure that database pages are in expected order and type.
func TestDB_Consistency(t *testing.T) {
- db := NewTestDB()
- defer db.Close()
- db.Update(func(tx *bolt.Tx) error {
+ db := MustOpenDB()
+ defer db.MustClose()
+ if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
for i := 0; i < 10; i++ {
- db.Update(func(tx *bolt.Tx) error {
- ok(t, tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")))
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if err := tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")); err != nil {
+ t.Fatal(err)
+ }
return nil
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
}
- db.Update(func(tx *bolt.Tx) error {
- p, _ := tx.Page(0)
- assert(t, p != nil, "")
- equals(t, "meta", p.Type)
- p, _ = tx.Page(1)
- assert(t, p != nil, "")
- equals(t, "meta", p.Type)
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if p, _ := tx.Page(0); p == nil {
+ t.Fatal("expected page")
+ } else if p.Type != "meta" {
+ t.Fatalf("unexpected page type: %s", p.Type)
+ }
- p, _ = tx.Page(2)
- assert(t, p != nil, "")
- equals(t, "free", p.Type)
+ if p, _ := tx.Page(1); p == nil {
+ t.Fatal("expected page")
+ } else if p.Type != "meta" {
+ t.Fatalf("unexpected page type: %s", p.Type)
+ }
- p, _ = tx.Page(3)
- assert(t, p != nil, "")
- equals(t, "free", p.Type)
+ if p, _ := tx.Page(2); p == nil {
+ t.Fatal("expected page")
+ } else if p.Type != "free" {
+ t.Fatalf("unexpected page type: %s", p.Type)
+ }
- p, _ = tx.Page(4)
- assert(t, p != nil, "")
- equals(t, "leaf", p.Type)
+ if p, _ := tx.Page(3); p == nil {
+ t.Fatal("expected page")
+ } else if p.Type != "free" {
+ t.Fatalf("unexpected page type: %s", p.Type)
+ }
- p, _ = tx.Page(5)
- assert(t, p != nil, "")
- equals(t, "freelist", p.Type)
+ if p, _ := tx.Page(4); p == nil {
+ t.Fatal("expected page")
+ } else if p.Type != "leaf" {
+ t.Fatalf("unexpected page type: %s", p.Type)
+ }
+
+ if p, _ := tx.Page(5); p == nil {
+ t.Fatal("expected page")
+ } else if p.Type != "freelist" {
+ t.Fatalf("unexpected page type: %s", p.Type)
+ }
- p, _ = tx.Page(6)
- assert(t, p == nil, "")
+ if p, _ := tx.Page(6); p != nil {
+ t.Fatal("unexpected page")
+ }
return nil
- })
+ }); err != nil {
+ t.Fatal(err)
+ }
}
// Ensure that DB stats can be subtracted from one another.
@@ -739,19 +988,209 @@ func TestDBStats_Sub(t *testing.T) {
b.TxStats.PageCount = 10
b.FreePageN = 14
diff := b.Sub(&a)
- equals(t, 7, diff.TxStats.PageCount)
+ if diff.TxStats.PageCount != 7 {
+ t.Fatalf("unexpected TxStats.PageCount: %d", diff.TxStats.PageCount)
+ }
+
// free page stats are copied from the receiver and not subtracted
- equals(t, 14, diff.FreePageN)
+ if diff.FreePageN != 14 {
+ t.Fatalf("unexpected FreePageN: %d", diff.FreePageN)
+ }
+}
+
+// Ensure two functions can perform updates in a single batch.
+func TestDB_Batch(t *testing.T) {
+ db := MustOpenDB()
+ defer db.MustClose()
+
+ if err := db.Update(func(tx *bolt.Tx) error {
+ if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
+ t.Fatal(err)
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ // Iterate over multiple updates in separate goroutines.
+ n := 2
+ ch := make(chan error)
+ for i := 0; i < n; i++ {
+ go func(i int) {
+ ch <- db.Batch(func(tx *bolt.Tx) error {
+ return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
+ })
+ }(i)
+ }
+
+ // Check all responses to make sure there's no error.
+ for i := 0; i < n; i++ {
+ if err := <-ch; err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Ensure data is correct.
+ if err := db.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("widgets"))
+ for i := 0; i < n; i++ {
+ if v := b.Get(u64tob(uint64(i))); v == nil {
+ t.Errorf("key not found: %d", i)
+ }
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDB_Batch_Panic(t *testing.T) {
+ db := MustOpenDB()
+ defer db.MustClose()
+
+ var sentinel int
+ var bork = &sentinel
+ var problem interface{}
+ var err error
+
+ // Execute a function inside a batch that panics.
+ func() {
+ defer func() {
+ if p := recover(); p != nil {
+ problem = p
+ }
+ }()
+ err = db.Batch(func(tx *bolt.Tx) error {
+ panic(bork)
+ })
+ }()
+
+ // Verify there is no error.
+ if g, e := err, error(nil); g != e {
+ t.Fatalf("wrong error: %v != %v", g, e)
+ }
+ // Verify the panic was captured.
+ if g, e := problem, bork; g != e {
+ t.Fatalf("wrong error: %v != %v", g, e)
+ }
+}
+
+func TestDB_BatchFull(t *testing.T) {
+ db := MustOpenDB()
+ defer db.MustClose()
+ if err := db.Update(func(tx *bolt.Tx) error {
+ _, err := tx.CreateBucket([]byte("widgets"))
+ return err
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ const size = 3
+ // buffered so we never leak goroutines
+ ch := make(chan error, size)
+ put := func(i int) {
+ ch <- db.Batch(func(tx *bolt.Tx) error {
+ return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
+ })
+ }
+
+ db.MaxBatchSize = size
+ // high enough to never trigger here
+ db.MaxBatchDelay = 1 * time.Hour
+
+ go put(1)
+ go put(2)
+
+ // Give the batch a chance to exhibit bugs.
+ time.Sleep(10 * time.Millisecond)
+
+ // not triggered yet
+ select {
+ case <-ch:
+ t.Fatalf("batch triggered too early")
+ default:
+ }
+
+ go put(3)
+
+ // Check all responses to make sure there's no error.
+ for i := 0; i < size; i++ {
+ if err := <-ch; err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Ensure data is correct.
+ if err := db.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("widgets"))
+ for i := 1; i <= size; i++ {
+ if v := b.Get(u64tob(uint64(i))); v == nil {
+ t.Errorf("key not found: %d", i)
+ }
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDB_BatchTime(t *testing.T) {
+ db := MustOpenDB()
+ defer db.MustClose()
+ if err := db.Update(func(tx *bolt.Tx) error {
+ _, err := tx.CreateBucket([]byte("widgets"))
+ return err
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ const size = 1
+ // buffered so we never leak goroutines
+ ch := make(chan error, size)
+ put := func(i int) {
+ ch <- db.Batch(func(tx *bolt.Tx) error {
+ return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
+ })
+ }
+
+ db.MaxBatchSize = 1000
+ db.MaxBatchDelay = 0
+
+ go put(1)
+
+ // Batch must trigger by time alone.
+
+ // Check all responses to make sure there's no error.
+ for i := 0; i < size; i++ {
+ if err := <-ch; err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Ensure data is correct.
+ if err := db.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("widgets"))
+ for i := 1; i <= size; i++ {
+ if v := b.Get(u64tob(uint64(i))); v == nil {
+ t.Errorf("key not found: %d", i)
+ }
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
}
func ExampleDB_Update() {
// Open the database.
- db, _ := bolt.Open(tempfile(), 0666, nil)
+ db, err := bolt.Open(tempfile(), 0666, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
defer os.Remove(db.Path())
- defer db.Close()
- // Execute several commands within a write transaction.
- err := db.Update(func(tx *bolt.Tx) error {
+ // Execute several commands within a read-write transaction.
+ if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
return err
@@ -760,15 +1199,22 @@ func ExampleDB_Update() {
return err
}
return nil
- })
+ }); err != nil {
+ log.Fatal(err)
+ }
+
+ // Read the value back from a separate read-only transaction.
+ if err := db.View(func(tx *bolt.Tx) error {
+ value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
+ fmt.Printf("The value of 'foo' is: %s\n", value)
+ return nil
+ }); err != nil {
+ log.Fatal(err)
+ }
- // If our transactional block didn't return an error then our data is saved.
- if err == nil {
- db.View(func(tx *bolt.Tx) error {
- value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
- fmt.Printf("The value of 'foo' is: %s\n", value)
- return nil
- })
+ // Close database to release the file lock.
+ if err := db.Close(); err != nil {
+ log.Fatal(err)
}
// Output:
@@ -777,25 +1223,42 @@ func ExampleDB_Update() {
func ExampleDB_View() {
// Open the database.
- db, _ := bolt.Open(tempfile(), 0666, nil)
+ db, err := bolt.Open(tempfile(), 0666, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
defer os.Remove(db.Path())
- defer db.Close()
// Insert data into a bucket.
- db.Update(func(tx *bolt.Tx) error {
- tx.CreateBucket([]byte("people"))
- b := tx.Bucket([]byte("people"))
- b.Put([]byte("john"), []byte("doe"))
- b.Put([]byte("susy"), []byte("que"))
+ if err := db.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucket([]byte("people"))
+ if err != nil {
+ return err
+ }
+ if err := b.Put([]byte("john"), []byte("doe")); err != nil {
+ return err
+ }
+ if err := b.Put([]byte("susy"), []byte("que")); err != nil {
+ return err
+ }
return nil
- })
+ }); err != nil {
+ log.Fatal(err)
+ }
// Access data from within a read-only transactional block.
- db.View(func(tx *bolt.Tx) error {
+ if err := db.View(func(tx *bolt.Tx) error {
v := tx.Bucket([]byte("people")).Get([]byte("john"))
fmt.Printf("John's last name is %s.\n", v)
return nil
- })
+ }); err != nil {
+ log.Fatal(err)
+ }
+
+ // Close database to release the file lock.
+ if err := db.Close(); err != nil {
+ log.Fatal(err)
+ }
// Output:
// John's last name is doe.
@@ -803,31 +1266,56 @@ func ExampleDB_View() {
func ExampleDB_Begin_ReadOnly() {
// Open the database.
- db, _ := bolt.Open(tempfile(), 0666, nil)
+ db, err := bolt.Open(tempfile(), 0666, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
defer os.Remove(db.Path())
- defer db.Close()
- // Create a bucket.
- db.Update(func(tx *bolt.Tx) error {
+ // Create a bucket using a read-write transaction.
+ if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
- })
+ }); err != nil {
+ log.Fatal(err)
+ }
// Create several keys in a transaction.
- tx, _ := db.Begin(true)
+ tx, err := db.Begin(true)
+ if err != nil {
+ log.Fatal(err)
+ }
b := tx.Bucket([]byte("widgets"))
- b.Put([]byte("john"), []byte("blue"))
- b.Put([]byte("abby"), []byte("red"))
- b.Put([]byte("zephyr"), []byte("purple"))
- tx.Commit()
+ if err := b.Put([]byte("john"), []byte("blue")); err != nil {
+ log.Fatal(err)
+ }
+ if err := b.Put([]byte("abby"), []byte("red")); err != nil {
+ log.Fatal(err)
+ }
+ if err := b.Put([]byte("zephyr"), []byte("purple")); err != nil {
+ log.Fatal(err)
+ }
+ if err := tx.Commit(); err != nil {
+ log.Fatal(err)
+ }
// Iterate over the values in sorted key order.
- tx, _ = db.Begin(false)
+ tx, err = db.Begin(false)
+ if err != nil {
+ log.Fatal(err)
+ }
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
fmt.Printf("%s likes %s\n", k, v)
}
- tx.Rollback()
+
+ if err := tx.Rollback(); err != nil {
+ log.Fatal(err)
+ }
+
+ if err := db.Close(); err != nil {
+ log.Fatal(err)
+ }
// Output:
// abby likes red
@@ -835,51 +1323,195 @@ func ExampleDB_Begin_ReadOnly() {
// zephyr likes purple
}
-// TestDB represents a wrapper around a Bolt DB to handle temporary file
-// creation and automatic cleanup on close.
-type TestDB struct {
- *bolt.DB
-}
+func BenchmarkDBBatchAutomatic(b *testing.B) {
+ db := MustOpenDB()
+ defer db.MustClose()
+ if err := db.Update(func(tx *bolt.Tx) error {
+ _, err := tx.CreateBucket([]byte("bench"))
+ return err
+ }); err != nil {
+ b.Fatal(err)
+ }
-// NewTestDB returns a new instance of TestDB.
-func NewTestDB() *TestDB {
- db, err := bolt.Open(tempfile(), 0666, nil)
- if err != nil {
- panic("cannot open db: " + err.Error())
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ start := make(chan struct{})
+ var wg sync.WaitGroup
+
+ for round := 0; round < 1000; round++ {
+ wg.Add(1)
+
+ go func(id uint32) {
+ defer wg.Done()
+ <-start
+
+ h := fnv.New32a()
+ buf := make([]byte, 4)
+ binary.LittleEndian.PutUint32(buf, id)
+ _, _ = h.Write(buf[:])
+ k := h.Sum(nil)
+ insert := func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("bench"))
+ return b.Put(k, []byte("filler"))
+ }
+ if err := db.Batch(insert); err != nil {
+ b.Error(err)
+ return
+ }
+ }(uint32(round))
+ }
+ close(start)
+ wg.Wait()
}
- return &TestDB{db}
+
+ b.StopTimer()
+ validateBatchBench(b, db)
}
-// MustView executes a read-only function. Panic on error.
-func (db *TestDB) MustView(fn func(tx *bolt.Tx) error) {
- if err := db.DB.View(func(tx *bolt.Tx) error {
- return fn(tx)
+func BenchmarkDBBatchSingle(b *testing.B) {
+ db := MustOpenDB()
+ defer db.MustClose()
+ if err := db.Update(func(tx *bolt.Tx) error {
+ _, err := tx.CreateBucket([]byte("bench"))
+ return err
}); err != nil {
- panic(err.Error())
+ b.Fatal(err)
}
-}
-// MustUpdate executes a read-write function. Panic on error.
-func (db *TestDB) MustUpdate(fn func(tx *bolt.Tx) error) {
- if err := db.DB.View(func(tx *bolt.Tx) error {
- return fn(tx)
- }); err != nil {
- panic(err.Error())
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ start := make(chan struct{})
+ var wg sync.WaitGroup
+
+ for round := 0; round < 1000; round++ {
+ wg.Add(1)
+ go func(id uint32) {
+ defer wg.Done()
+ <-start
+
+ h := fnv.New32a()
+ buf := make([]byte, 4)
+ binary.LittleEndian.PutUint32(buf, id)
+ _, _ = h.Write(buf[:])
+ k := h.Sum(nil)
+ insert := func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("bench"))
+ return b.Put(k, []byte("filler"))
+ }
+ if err := db.Update(insert); err != nil {
+ b.Error(err)
+ return
+ }
+ }(uint32(round))
+ }
+ close(start)
+ wg.Wait()
}
+
+ b.StopTimer()
+ validateBatchBench(b, db)
}
-// MustCreateBucket creates a new bucket. Panic on error.
-func (db *TestDB) MustCreateBucket(name []byte) {
+func BenchmarkDBBatchManual10x100(b *testing.B) {
+ db := MustOpenDB()
+ defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
- _, err := tx.CreateBucket([]byte(name))
+ _, err := tx.CreateBucket([]byte("bench"))
return err
}); err != nil {
- panic(err.Error())
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ start := make(chan struct{})
+ var wg sync.WaitGroup
+
+ for major := 0; major < 10; major++ {
+ wg.Add(1)
+ go func(id uint32) {
+ defer wg.Done()
+ <-start
+
+ insert100 := func(tx *bolt.Tx) error {
+ h := fnv.New32a()
+ buf := make([]byte, 4)
+ for minor := uint32(0); minor < 100; minor++ {
+ binary.LittleEndian.PutUint32(buf, uint32(id*100+minor))
+ h.Reset()
+ _, _ = h.Write(buf[:])
+ k := h.Sum(nil)
+ b := tx.Bucket([]byte("bench"))
+ if err := b.Put(k, []byte("filler")); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if err := db.Update(insert100); err != nil {
+ b.Fatal(err)
+ }
+ }(uint32(major))
+ }
+ close(start)
+ wg.Wait()
+ }
+
+ b.StopTimer()
+ validateBatchBench(b, db)
+}
+
+func validateBatchBench(b *testing.B, db *DB) {
+ var rollback = errors.New("sentinel error to cause rollback")
+ validate := func(tx *bolt.Tx) error {
+ bucket := tx.Bucket([]byte("bench"))
+ h := fnv.New32a()
+ buf := make([]byte, 4)
+ for id := uint32(0); id < 1000; id++ {
+ binary.LittleEndian.PutUint32(buf, id)
+ h.Reset()
+ _, _ = h.Write(buf[:])
+ k := h.Sum(nil)
+ v := bucket.Get(k)
+ if v == nil {
+ b.Errorf("not found id=%d key=%x", id, k)
+ continue
+ }
+ if g, e := v, []byte("filler"); !bytes.Equal(g, e) {
+ b.Errorf("bad value for id=%d key=%x: %s != %q", id, k, g, e)
+ }
+ if err := bucket.Delete(k); err != nil {
+ return err
+ }
+ }
+ // should be empty now
+ c := bucket.Cursor()
+ for k, v := c.First(); k != nil; k, v = c.Next() {
+ b.Errorf("unexpected key: %x = %q", k, v)
+ }
+ return rollback
+ }
+ if err := db.Update(validate); err != nil && err != rollback {
+ b.Error(err)
+ }
+}
+
+// DB is a test wrapper for bolt.DB.
+type DB struct {
+ *bolt.DB
+}
+
+// MustOpenDB returns a new, open DB at a temporary location.
+func MustOpenDB() *DB {
+ db, err := bolt.Open(tempfile(), 0666, nil)
+ if err != nil {
+ panic(err)
}
+ return &DB{db}
}
// Close closes the database and deletes the underlying file.
-func (db *TestDB) Close() {
+func (db *DB) Close() error {
// Log statistics.
if *statsFlag {
db.PrintStats()
@@ -890,11 +1522,18 @@ func (db *TestDB) Close() {
// Close database and remove file.
defer os.Remove(db.Path())
- db.DB.Close()
+ return db.DB.Close()
+}
+
+// MustClose closes the database and deletes the underlying file. Panic on error.
+func (db *DB) MustClose() {
+ if err := db.Close(); err != nil {
+ panic(err)
+ }
}
// PrintStats prints the database stats
-func (db *TestDB) PrintStats() {
+func (db *DB) PrintStats() {
var stats = db.Stats()
fmt.Printf("[db] %-20s %-20s %-20s\n",
fmt.Sprintf("pg(%d/%d)", stats.TxStats.PageCount, stats.TxStats.PageAlloc),
@@ -909,8 +1548,8 @@ func (db *TestDB) PrintStats() {
}
// MustCheck runs a consistency check on the database and panics if any errors are found.
-func (db *TestDB) MustCheck() {
- db.Update(func(tx *bolt.Tx) error {
+func (db *DB) MustCheck() {
+ if err := db.Update(func(tx *bolt.Tx) error {
// Collect all the errors.
var errors []error
for err := range tx.Check() {
@@ -923,7 +1562,9 @@ func (db *TestDB) MustCheck() {
// If errors occurred, copy the DB and print the errors.
if len(errors) > 0 {
var path = tempfile()
- tx.CopyFile(path, 0600)
+ if err := tx.CopyFile(path, 0600); err != nil {
+ panic(err)
+ }
// Print errors.
fmt.Print("\n\n")
@@ -939,31 +1580,46 @@ func (db *TestDB) MustCheck() {
}
return nil
- })
+ }); err != nil && err != bolt.ErrDatabaseNotOpen {
+ panic(err)
+ }
}
// CopyTempFile copies a database to a temporary file.
-func (db *TestDB) CopyTempFile() {
+func (db *DB) CopyTempFile() {
path := tempfile()
- db.View(func(tx *bolt.Tx) error { return tx.CopyFile(path, 0600) })
+ if err := db.View(func(tx *bolt.Tx) error {
+ return tx.CopyFile(path, 0600)
+ }); err != nil {
+ panic(err)
+ }
fmt.Println("db copied to: ", path)
}
// tempfile returns a temporary file path.
func tempfile() string {
- f, _ := ioutil.TempFile("", "bolt-")
- f.Close()
- os.Remove(f.Name())
+ f, err := ioutil.TempFile("", "bolt-")
+ if err != nil {
+ panic(err)
+ }
+ if err := f.Close(); err != nil {
+ panic(err)
+ }
+ if err := os.Remove(f.Name()); err != nil {
+ panic(err)
+ }
return f.Name()
}
// mustContainKeys checks that a bucket contains a given set of keys.
func mustContainKeys(b *bolt.Bucket, m map[string]string) {
found := make(map[string]string)
- b.ForEach(func(k, _ []byte) error {
+ if err := b.ForEach(func(k, _ []byte) error {
found[string(k)] = ""
return nil
- })
+ }); err != nil {
+ panic(err)
+ }
// Check for keys found in bucket that shouldn't be there.
var keys []string