From d1952237edfbc3e2ab93a1109537c68144e9fd1e Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Sat, 15 Feb 2014 23:38:03 -0700 Subject: [PATCH] Improve test coverage. --- cursor.go | 4 +-- db.go | 17 +---------- db_test.go | 67 +++++++++++++++++++++++++++++++++++++------ functional_test.go | 5 +++- rwtransaction_test.go | 56 +++++++++++++++++++++++++++++------- transaction.go | 8 ++++-- transaction_test.go | 12 +++++++- 7 files changed, 128 insertions(+), 41 deletions(-) diff --git a/cursor.go b/cursor.go index a8a71b1..109cffb 100644 --- a/cursor.go +++ b/cursor.go @@ -154,9 +154,7 @@ func (c *Cursor) keyValue() ([]byte, []byte) { // node returns the node that the cursor is currently positioned on. func (c *Cursor) node(t *RWTransaction) *node { - if len(c.stack) == 0 { - return nil - } + _assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack") // Start from root and traverse down the hierarchy. n := t.node(c.stack[0].page.id, nil) diff --git a/db.go b/db.go index 399e39b..99cc9ca 100644 --- a/db.go +++ b/db.go @@ -240,7 +240,7 @@ func (db *DB) Close() { func (db *DB) close() { db.opened = false - + // TODO(benbjohnson): Undo everything in Open(). db.freelist = nil db.path = "" @@ -432,10 +432,6 @@ func (db *DB) Delete(name string, key []byte) error { // A reader transaction is maintained during the copy so it is safe to continue // using the database while a copy is in progress. func (db *DB) Copy(w io.Writer) error { - if !db.opened { - return DatabaseNotOpenError - } - // Maintain a reader transaction so pages don't get reclaimed. t, err := db.Transaction() if err != nil { @@ -514,14 +510,3 @@ func (db *DB) allocate(count int) (*page, error) { return p, nil } - -// sync flushes the file descriptor to disk. -func (db *DB) sync(force bool) error { - if db.opened { - return DatabaseNotOpenError - } - if err := syscall.Fsync(int(db.file.Fd())); err != nil { - return err - } - return nil -} diff --git a/db_test.go b/db_test.go index 666ba4a..89da00f 100644 --- a/db_test.go +++ b/db_test.go @@ -178,6 +178,14 @@ func TestDBDelete(t *testing.T) { }) } +// Ensure that a delete on a missing bucket returns an error. +func TestDBDeleteFromMissingBucket(t *testing.T) { + withOpenDB(func(db *DB, path string) { + err := db.Delete("widgets", []byte("foo")) + assert.Equal(t, err, BucketNotFoundError) + }) +} + // Ensure a database can provide a transactional block. func TestDBTransactionBlock(t *testing.T) { withOpenDB(func(db *DB, path string) { @@ -196,19 +204,62 @@ func TestDBTransactionBlock(t *testing.T) { }) } -// Ensure that the database can be copied to a writer. -func TestDBCopy(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) +// Ensure a closed database returns an error while running a transaction block +func TestDBTransactionBlockWhileClosed(t *testing.T) { + withDB(func(db *DB, path string) { + err := db.Do(func(txn *RWTransaction) error { + txn.CreateBucket("widgets") + return nil + }) + assert.Equal(t, err, DatabaseNotOpenError) + }) +} + +// Ensure a closed database returns an error when finding a bucket. +func TestDBBucketWhileClosed(t *testing.T) { + withDB(func(db *DB, path string) { + b, err := db.Bucket("widgets") + assert.Equal(t, err, DatabaseNotOpenError) + assert.Nil(t, b) + }) +} + +// Ensure a closed database returns an error when finding all buckets. +func TestDBBucketsWhileClosed(t *testing.T) { + withDB(func(db *DB, path string) { + b, err := db.Buckets() + assert.Equal(t, err, DatabaseNotOpenError) + assert.Nil(t, b) + }) +} + +// Ensure a closed database returns an error when getting a key. +func TestDBGetWhileClosed(t *testing.T) { + withDB(func(db *DB, path string) { + value, err := db.Get("widgets", []byte("foo")) + assert.Equal(t, err, DatabaseNotOpenError) + assert.Nil(t, value) + }) } // Ensure that the database can be copied to a file path. func TestDBCopyFile(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) -} + withOpenDB(func(db *DB, path string) { + db.CreateBucket("widgets") + db.Put("widgets", []byte("foo"), []byte("bar")) + db.Put("widgets", []byte("baz"), []byte("bat")) + assert.NoError(t, os.RemoveAll("/tmp/bolt.copyfile.db")) + assert.NoError(t, db.CopyFile("/tmp/bolt.copyfile.db", 0666)) -// Ensure that the database can sync to the file system. -func TestDBSync(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) + var db2 DB + assert.NoError(t, db2.Open("/tmp/bolt.copyfile.db", 0666)) + defer db2.Close() + + value, _ := db2.Get("widgets", []byte("foo")) + assert.Equal(t, value, []byte("bar")) + value, _ = db2.Get("widgets", []byte("baz")) + assert.Equal(t, value, []byte("bat")) + }) } // Ensure that an error is returned when a database write fails. diff --git a/functional_test.go b/functional_test.go index e155bc3..f0b18cf 100644 --- a/functional_test.go +++ b/functional_test.go @@ -47,7 +47,10 @@ func TestParallelTransactions(t *testing.T) { local := current txn, err := db.Transaction() mutex.RUnlock() - if !assert.NoError(t, err) { + if err == DatabaseNotOpenError { + wg.Done() + return + } else if !assert.NoError(t, err) { t.FailNow() } diff --git a/rwtransaction_test.go b/rwtransaction_test.go index fa254d8..14ba6b2 100644 --- a/rwtransaction_test.go +++ b/rwtransaction_test.go @@ -21,6 +21,15 @@ func TestRWTransaction(t *testing.T) { }) } +// Ensure that opening a RWTransaction while the DB is closed returns an error. +func TestRWTransactionOpenWithClosedDB(t *testing.T) { + withDB(func(db *DB, path string) { + txn, err := db.RWTransaction() + assert.Equal(t, err, DatabaseNotOpenError) + assert.Nil(t, txn) + }) +} + // Ensure that a bucket can be created and retrieved. func TestRWTransactionCreateBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { @@ -69,7 +78,23 @@ func TestRWTransactionCreateBucketWithLongName(t *testing.T) { // Ensure that a bucket can be deleted. func TestRWTransactionDeleteBucket(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) + withOpenDB(func(db *DB, path string) { + // Create a bucket and add a value. + db.CreateBucket("widgets") + db.Put("widgets", []byte("foo"), []byte("bar")) + + // Delete the bucket and make sure we can't get the value. + assert.NoError(t, db.DeleteBucket("widgets")) + value, err := db.Get("widgets", []byte("foo")) + assert.Equal(t, err, BucketNotFoundError) + assert.Nil(t, value) + + // Create the bucket again and make sure there's not a phantom value. + assert.NoError(t, db.CreateBucket("widgets")) + value, err = db.Get("widgets", []byte("foo")) + assert.NoError(t, err) + assert.Nil(t, value) + }) } // Ensure that a bucket can return an autoincrementing sequence. @@ -100,27 +125,38 @@ func TestRWTransactionNextSequence(t *testing.T) { // Ensure that an error is returned when inserting into a bucket that doesn't exist. func TestRWTransactionPutBucketNotFound(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) + withOpenDB(func(db *DB, path string) { + err := db.Put("widgets", []byte("foo"), []byte("bar")) + assert.Equal(t, err, BucketNotFoundError) + }) } // Ensure that an error is returned when inserting with an empty key. func TestRWTransactionPutEmptyKey(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) + withOpenDB(func(db *DB, path string) { + db.CreateBucket("widgets") + err := db.Put("widgets", []byte(""), []byte("bar")) + assert.Equal(t, err, KeyRequiredError) + err = db.Put("widgets", nil, []byte("bar")) + assert.Equal(t, err, KeyRequiredError) + }) } // Ensure that an error is returned when inserting with a key that's too large. func TestRWTransactionPutKeyTooLarge(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) -} - -// Ensure that an error is returned when inserting with data that's too large. -func TestRWTransactionPutDataTooLarge(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) + withOpenDB(func(db *DB, path string) { + db.CreateBucket("widgets") + err := db.Put("widgets", make([]byte, 32769), []byte("bar")) + assert.Equal(t, err, KeyTooLargeError) + }) } // Ensure that an error is returned when deleting from a bucket that doesn't exist. func TestRWTransactionDeleteBucketNotFound(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) + withOpenDB(func(db *DB, path string) { + err := db.DeleteBucket("widgets") + assert.Equal(t, err, BucketNotFoundError) + }) } // Ensure that a bucket can write random keys and values across multiple txns. diff --git a/transaction.go b/transaction.go index 46adefe..739d112 100644 --- a/transaction.go +++ b/transaction.go @@ -63,8 +63,12 @@ func (t *Transaction) Bucket(name string) *Bucket { // Buckets retrieves a list of all buckets. func (t *Transaction) Buckets() []*Bucket { - warn("[pending] Transaction.Buckets()") // TODO - return nil + buckets := make([]*Bucket, 0, len(t.buckets.items)) + for name, b := range t.buckets.items { + bucket := &Bucket{bucket: b, transaction: t, name: name} + buckets = append(buckets, bucket) + } + return buckets } // Cursor creates a cursor associated with a given bucket. diff --git a/transaction_test.go b/transaction_test.go index afe3b8e..5a7177f 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -12,7 +12,17 @@ import ( // Ensure that the database can retrieve a list of buckets. func TestTransactionBuckets(t *testing.T) { - t.Skip("pending") // TODO(benbjohnson) + withOpenDB(func(db *DB, path string) { + db.CreateBucket("foo") + db.CreateBucket("bar") + db.CreateBucket("baz") + buckets, err := db.Buckets() + if assert.NoError(t, err) && assert.Equal(t, len(buckets), 3) { + assert.Equal(t, buckets[0].Name(), "bar") + assert.Equal(t, buckets[1].Name(), "baz") + assert.Equal(t, buckets[2].Name(), "foo") + } + }) } // Ensure that a Transaction can retrieve a bucket.