diff options
Diffstat (limited to 'tx_test.go')
-rw-r--r-- | tx_test.go | 413 |
1 files changed, 87 insertions, 326 deletions
@@ -5,27 +5,25 @@ import ( "fmt" "math/rand" "os" - "sort" "strconv" "strings" "testing" - "testing/quick" "github.com/stretchr/testify/assert" ) // Ensure that committing a closed transaction returns an error. -func TestTxCommitClosed(t *testing.T) { +func TestTx_Commit_Closed(t *testing.T) { withOpenDB(func(db *DB, path string) { tx, _ := db.Begin(true) - tx.CreateBucket("foo") + tx.CreateBucket([]byte("foo")) assert.NoError(t, tx.Commit()) assert.Equal(t, tx.Commit(), ErrTxClosed) }) } // Ensure that rolling back a closed transaction returns an error. -func TestTxRollbackClosed(t *testing.T) { +func TestTx_Rollback_Closed(t *testing.T) { withOpenDB(func(db *DB, path string) { tx, _ := db.Begin(true) assert.NoError(t, tx.Rollback()) @@ -34,114 +32,69 @@ func TestTxRollbackClosed(t *testing.T) { } // Ensure that committing a read-only transaction returns an error. -func TestTxCommitReadOnly(t *testing.T) { +func TestTx_Commit_ReadOnly(t *testing.T) { withOpenDB(func(db *DB, path string) { tx, _ := db.Begin(false) assert.Equal(t, tx.Commit(), ErrTxNotWritable) }) } -// Ensure that the database can retrieve a list of buckets. -func TestTxBuckets(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - tx.CreateBucket("foo") - tx.CreateBucket("bar") - tx.CreateBucket("baz") - buckets := tx.Buckets() - if 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") - } - return nil - }) - }) -} - // Ensure that creating a bucket with a read-only transaction returns an error. -func TestTxCreateBucketReadOnly(t *testing.T) { +func TestTx_CreateBucket_ReadOnly(t *testing.T) { withOpenDB(func(db *DB, path string) { db.View(func(tx *Tx) error { - assert.Equal(t, tx.CreateBucket("foo"), ErrTxNotWritable) + assert.Equal(t, tx.CreateBucket([]byte("foo")), ErrTxNotWritable) return nil }) }) } // Ensure that creating a bucket on a closed transaction returns an error. -func TestTxCreateBucketClosed(t *testing.T) { +func TestTx_CreateBucket_Closed(t *testing.T) { withOpenDB(func(db *DB, path string) { tx, _ := db.Begin(true) tx.Commit() - assert.Equal(t, tx.CreateBucket("foo"), ErrTxClosed) + assert.Equal(t, tx.CreateBucket([]byte("foo")), ErrTxClosed) }) } // Ensure that a Tx can retrieve a bucket. -func TestTxBucket(t *testing.T) { +func TestTx_Bucket(t *testing.T) { withOpenDB(func(db *DB, path string) { db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - b := tx.Bucket("widgets") - if assert.NotNil(t, b) { - assert.Equal(t, "widgets", b.Name()) - } + tx.CreateBucket([]byte("widgets")) + b := tx.Bucket([]byte("widgets")) + assert.NotNil(t, b) return nil }) }) } // Ensure that a Tx retrieving a non-existent key returns nil. -func TestTxGetMissing(t *testing.T) { +func TestTx_Get_Missing(t *testing.T) { withOpenDB(func(db *DB, path string) { db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) - value := tx.Bucket("widgets").Get([]byte("no_such_key")) + tx.CreateBucket([]byte("widgets")) + tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) + value := tx.Bucket([]byte("widgets")).Get([]byte("no_such_key")) assert.Nil(t, value) return nil }) }) } -// Ensure that retrieving all buckets returns writable buckets. -func TestTxWritableBuckets(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - tx.CreateBucket("woojits") - return nil - }) - db.Update(func(tx *Tx) error { - buckets := tx.Buckets() - assert.Equal(t, len(buckets), 2) - assert.Equal(t, buckets[0].Name(), "widgets") - assert.Equal(t, buckets[1].Name(), "woojits") - buckets[0].Put([]byte("foo"), []byte("0000")) - buckets[1].Put([]byte("bar"), []byte("0001")) - return nil - }) - db.View(func(tx *Tx) error { - assert.Equal(t, []byte("0000"), tx.Bucket("widgets").Get([]byte("foo"))) - assert.Equal(t, []byte("0001"), tx.Bucket("woojits").Get([]byte("bar"))) - return nil - }) - }) -} - // Ensure that a bucket can be created and retrieved. -func TestTxCreateBucket(t *testing.T) { +func TestTx_CreateBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { // Create a bucket. db.Update(func(tx *Tx) error { - assert.NoError(t, tx.CreateBucket("widgets")) + assert.NoError(t, tx.CreateBucket([]byte("widgets"))) return nil }) // Read the bucket through a separate transaction. db.View(func(tx *Tx) error { - b := tx.Bucket("widgets") + b := tx.Bucket([]byte("widgets")) assert.NotNil(t, b) return nil }) @@ -149,18 +102,19 @@ func TestTxCreateBucket(t *testing.T) { } // Ensure that a bucket can be created if it doesn't already exist. -func TestTxCreateBucketIfNotExists(t *testing.T) { +func TestTx_CreateBucketIfNotExists(t *testing.T) { withOpenDB(func(db *DB, path string) { db.Update(func(tx *Tx) error { - assert.NoError(t, tx.CreateBucketIfNotExists("widgets")) - assert.NoError(t, tx.CreateBucketIfNotExists("widgets")) - assert.Equal(t, tx.CreateBucketIfNotExists(""), ErrBucketNameRequired) + assert.NoError(t, tx.CreateBucketIfNotExists([]byte("widgets"))) + assert.NoError(t, tx.CreateBucketIfNotExists([]byte("widgets"))) + assert.Equal(t, ErrBucketNameRequired, tx.CreateBucketIfNotExists([]byte{})) + assert.Equal(t, ErrBucketNameRequired, tx.CreateBucketIfNotExists(nil)) return nil }) // Read the bucket through a separate transaction. db.View(func(tx *Tx) error { - b := tx.Bucket("widgets") + b := tx.Bucket([]byte("widgets")) assert.NotNil(t, b) return nil }) @@ -168,64 +122,53 @@ func TestTxCreateBucketIfNotExists(t *testing.T) { } // Ensure that a bucket cannot be created twice. -func TestTxRecreateBucket(t *testing.T) { +func TestTx_CreateBucket_Exists(t *testing.T) { withOpenDB(func(db *DB, path string) { // Create a bucket. db.Update(func(tx *Tx) error { - assert.NoError(t, tx.CreateBucket("widgets")) + assert.NoError(t, tx.CreateBucket([]byte("widgets"))) return nil }) // Create the same bucket again. db.Update(func(tx *Tx) error { - assert.Equal(t, ErrBucketExists, tx.CreateBucket("widgets")) + assert.Equal(t, ErrBucketExists, tx.CreateBucket([]byte("widgets"))) return nil }) }) } // Ensure that a bucket is created with a non-blank name. -func TestTxCreateBucketWithoutName(t *testing.T) { +func TestTx_CreateBucket_NameRequired(t *testing.T) { withOpenDB(func(db *DB, path string) { db.Update(func(tx *Tx) error { - assert.Equal(t, ErrBucketNameRequired, tx.CreateBucket("")) - return nil - }) - }) -} - -// Ensure that a bucket name is not too long. -func TestTxCreateBucketWithLongName(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - assert.NoError(t, tx.CreateBucket(strings.Repeat("X", 255))) - assert.Equal(t, ErrBucketNameTooLarge, tx.CreateBucket(strings.Repeat("X", 256))) + assert.Equal(t, ErrBucketNameRequired, tx.CreateBucket(nil)) return nil }) }) } // Ensure that a bucket can be deleted. -func TestTxDeleteBucket(t *testing.T) { +func TestTx_DeleteBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { // Create a bucket and add a value. db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + tx.CreateBucket([]byte("widgets")) + tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) return nil }) // Save root page id. var root pgid db.View(func(tx *Tx) error { - root = tx.Bucket("widgets").root + root = tx.Bucket([]byte("widgets")).root return nil }) // Delete the bucket and make sure we can't get the value. db.Update(func(tx *Tx) error { - assert.NoError(t, tx.DeleteBucket("widgets")) - assert.Nil(t, tx.Bucket("widgets")) + assert.NoError(t, tx.DeleteBucket([]byte("widgets"))) + assert.Nil(t, tx.Bucket([]byte("widgets"))) return nil }) @@ -234,257 +177,42 @@ func TestTxDeleteBucket(t *testing.T) { assert.Equal(t, []pgid{7, 6, root, 2}, db.freelist.all()) // Create the bucket again and make sure there's not a phantom value. - assert.NoError(t, tx.CreateBucket("widgets")) - assert.Nil(t, tx.Bucket("widgets").Get([]byte("foo"))) + assert.NoError(t, tx.CreateBucket([]byte("widgets"))) + assert.Nil(t, tx.Bucket([]byte("widgets")).Get([]byte("foo"))) return nil }) }) } // Ensure that deleting a bucket on a closed transaction returns an error. -func TestTxDeleteBucketClosed(t *testing.T) { +func TestTx_DeleteBucket_Closed(t *testing.T) { withOpenDB(func(db *DB, path string) { tx, _ := db.Begin(true) tx.Commit() - assert.Equal(t, tx.DeleteBucket("foo"), ErrTxClosed) + assert.Equal(t, tx.DeleteBucket([]byte("foo")), ErrTxClosed) }) } // Ensure that deleting a bucket with a read-only transaction returns an error. -func TestTxDeleteBucketReadOnly(t *testing.T) { +func TestTx_DeleteBucket_ReadOnly(t *testing.T) { withOpenDB(func(db *DB, path string) { db.View(func(tx *Tx) error { - assert.Equal(t, tx.DeleteBucket("foo"), ErrTxNotWritable) - return nil - }) - }) -} - -// Ensure that an error is returned when deleting from a bucket that doesn't exist. -func TestTxDeleteBucketNotFound(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - assert.Equal(t, ErrBucketNotFound, tx.DeleteBucket("widgets")) + assert.Equal(t, tx.DeleteBucket([]byte("foo")), ErrTxNotWritable) return nil }) }) } -// Ensure that a Tx cursor can iterate over an empty bucket without error. -func TestTxCursorEmptyBucket(t *testing.T) { +// Ensure that nothing happens when deleting a bucket that doesn't exist. +func TestTx_DeleteBucket_NotFound(t *testing.T) { withOpenDB(func(db *DB, path string) { db.Update(func(tx *Tx) error { - return tx.CreateBucket("widgets") - }) - db.View(func(tx *Tx) error { - c := tx.Bucket("widgets").Cursor() - k, v := c.First() - assert.Nil(t, k) - assert.Nil(t, v) + assert.Equal(t, ErrBucketNotFound, tx.DeleteBucket([]byte("widgets"))) return nil }) }) } -// Ensure that a Tx cursor can reverse iterate over an empty bucket without error. -func TestCursorEmptyBucketReverse(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - return tx.CreateBucket("widgets") - }) - db.View(func(tx *Tx) error { - c := tx.Bucket("widgets").Cursor() - k, v := c.Last() - assert.Nil(t, k) - assert.Nil(t, v) - return nil - }) - }) -} - -// Ensure that a Tx cursor can iterate over a single root with a couple elements. -func TestTxCursorLeafRoot(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - tx.Bucket("widgets").Put([]byte("baz"), []byte{}) - tx.Bucket("widgets").Put([]byte("foo"), []byte{0}) - tx.Bucket("widgets").Put([]byte("bar"), []byte{1}) - return nil - }) - tx, _ := db.Begin(false) - c := tx.Bucket("widgets").Cursor() - - k, v := c.First() - assert.Equal(t, string(k), "bar") - assert.Equal(t, v, []byte{1}) - - k, v = c.Next() - assert.Equal(t, string(k), "baz") - assert.Equal(t, v, []byte{}) - - k, v = c.Next() - assert.Equal(t, string(k), "foo") - assert.Equal(t, v, []byte{0}) - - k, v = c.Next() - assert.Nil(t, k) - assert.Nil(t, v) - - k, v = c.Next() - assert.Nil(t, k) - assert.Nil(t, v) - - tx.Rollback() - }) -} - -// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements. -func TestTxCursorLeafRootReverse(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - tx.Bucket("widgets").Put([]byte("baz"), []byte{}) - tx.Bucket("widgets").Put([]byte("foo"), []byte{0}) - tx.Bucket("widgets").Put([]byte("bar"), []byte{1}) - return nil - }) - tx, _ := db.Begin(false) - c := tx.Bucket("widgets").Cursor() - - k, v := c.Last() - assert.Equal(t, string(k), "foo") - assert.Equal(t, v, []byte{0}) - - k, v = c.Prev() - assert.Equal(t, string(k), "baz") - assert.Equal(t, v, []byte{}) - - k, v = c.Prev() - assert.Equal(t, string(k), "bar") - assert.Equal(t, v, []byte{1}) - - k, v = c.Prev() - assert.Nil(t, k) - assert.Nil(t, v) - - k, v = c.Prev() - assert.Nil(t, k) - assert.Nil(t, v) - - tx.Rollback() - }) -} - -// Ensure that a Tx cursor can restart from the beginning. -func TestTxCursorRestart(t *testing.T) { - withOpenDB(func(db *DB, path string) { - db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - tx.Bucket("widgets").Put([]byte("bar"), []byte{}) - tx.Bucket("widgets").Put([]byte("foo"), []byte{}) - return nil - }) - - tx, _ := db.Begin(false) - c := tx.Bucket("widgets").Cursor() - - k, _ := c.First() - assert.Equal(t, string(k), "bar") - - k, _ = c.Next() - assert.Equal(t, string(k), "foo") - - k, _ = c.First() - assert.Equal(t, string(k), "bar") - - k, _ = c.Next() - assert.Equal(t, string(k), "foo") - - tx.Rollback() - }) -} - -// Ensure that a Tx can iterate over all elements in a bucket. -func TestTxCursorIterate(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - f := func(items testdata) bool { - withOpenDB(func(db *DB, path string) { - // Bulk insert all values. - tx, _ := db.Begin(true) - tx.CreateBucket("widgets") - b := tx.Bucket("widgets") - for _, item := range items { - assert.NoError(t, b.Put(item.Key, item.Value)) - } - assert.NoError(t, tx.Commit()) - - // Sort test data. - sort.Sort(items) - - // Iterate over all items and check consistency. - var index = 0 - tx, _ = db.Begin(false) - c := tx.Bucket("widgets").Cursor() - for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() { - assert.Equal(t, k, items[index].Key) - assert.Equal(t, v, items[index].Value) - index++ - } - assert.Equal(t, len(items), index) - tx.Rollback() - }) - return true - } - if err := quick.Check(f, qconfig()); err != nil { - t.Error(err) - } - fmt.Fprint(os.Stderr, "\n") -} - -// Ensure that a transaction can iterate over all elements in a bucket in reverse. -func TestTxCursorIterateReverse(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - - f := func(items testdata) bool { - withOpenDB(func(db *DB, path string) { - // Bulk insert all values. - tx, _ := db.Begin(true) - tx.CreateBucket("widgets") - b := tx.Bucket("widgets") - for _, item := range items { - assert.NoError(t, b.Put(item.Key, item.Value)) - } - assert.NoError(t, tx.Commit()) - - // Sort test data. - sort.Sort(revtestdata(items)) - - // Iterate over all items and check consistency. - var index = 0 - tx, _ = db.Begin(false) - c := tx.Bucket("widgets").Cursor() - for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() { - assert.Equal(t, k, items[index].Key) - assert.Equal(t, v, items[index].Value) - index++ - } - assert.Equal(t, len(items), index) - tx.Rollback() - }) - return true - } - if err := quick.Check(f, qconfig()); err != nil { - t.Error(err) - } - fmt.Fprint(os.Stderr, "\n") -} - // Ensure that Tx commit handlers are called after a transaction successfully commits. func TestTx_OnCommit(t *testing.T) { var x int @@ -492,7 +220,7 @@ func TestTx_OnCommit(t *testing.T) { db.Update(func(tx *Tx) error { tx.OnCommit(func() { x += 1 }) tx.OnCommit(func() { x += 2 }) - return tx.CreateBucket("widgets") + return tx.CreateBucket([]byte("widgets")) }) }) assert.Equal(t, 3, x) @@ -505,7 +233,7 @@ func TestTx_OnCommit_Rollback(t *testing.T) { db.Update(func(tx *Tx) error { tx.OnCommit(func() { x += 1 }) tx.OnCommit(func() { x += 2 }) - tx.CreateBucket("widgets") + tx.CreateBucket([]byte("widgets")) return errors.New("rollback this commit") }) }) @@ -522,8 +250,8 @@ func BenchmarkTxCursor(b *testing.B) { withOpenDB(func(db *DB, path string) { // Write data to bucket. db.Update(func(tx *Tx) error { - tx.CreateBucket("widgets") - bucket := tx.Bucket("widgets") + tx.CreateBucket([]byte("widgets")) + bucket := tx.Bucket([]byte("widgets")) for i := 0; i < total; i++ { bucket.Put([]byte(fmt.Sprintf("%016d", indexes[i])), value) } @@ -535,7 +263,7 @@ func BenchmarkTxCursor(b *testing.B) { for i := 0; i < b.N; i++ { db.View(func(tx *Tx) error { count := 0 - c := tx.Bucket("widgets").Cursor() + c := tx.Bucket([]byte("widgets")).Cursor() for k, _ := c.First(); k != nil; k, _ = c.Next() { count++ } @@ -554,7 +282,7 @@ func BenchmarkTxPutRandom(b *testing.B) { value := []byte(strings.Repeat("0", 64)) withOpenDB(func(db *DB, path string) { db.Update(func(tx *Tx) error { - return tx.CreateBucket("widgets") + return tx.CreateBucket([]byte("widgets")) }) var tx *Tx var bucket *Bucket @@ -564,7 +292,7 @@ func BenchmarkTxPutRandom(b *testing.B) { tx.Commit() } tx, _ = db.Begin(true) - bucket = tx.Bucket("widgets") + bucket = tx.Bucket([]byte("widgets")) } bucket.Put([]byte(strconv.Itoa(indexes[i])), value) } @@ -577,10 +305,10 @@ func BenchmarkTxPutSequential(b *testing.B) { value := []byte(strings.Repeat("0", 64)) withOpenDB(func(db *DB, path string) { db.Update(func(tx *Tx) error { - return tx.CreateBucket("widgets") + return tx.CreateBucket([]byte("widgets")) }) db.Update(func(tx *Tx) error { - bucket := tx.Bucket("widgets") + bucket := tx.Bucket([]byte("widgets")) for i := 0; i < b.N; i++ { bucket.Put([]byte(strconv.Itoa(i)), value) } @@ -588,3 +316,36 @@ func BenchmarkTxPutSequential(b *testing.B) { }) }) } + +func ExampleTx_Rollback() { + // Open the database. + db, _ := Open(tempfile(), 0666) + defer os.Remove(db.Path()) + defer db.Close() + + // Create a bucket. + db.Update(func(tx *Tx) error { + return tx.CreateBucket([]byte("widgets")) + }) + + // Set a value for a key. + db.Update(func(tx *Tx) error { + return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) + }) + + // Update the key but rollback the transaction so it never saves. + tx, _ := db.Begin(true) + b := tx.Bucket([]byte("widgets")) + b.Put([]byte("foo"), []byte("baz")) + tx.Rollback() + + // Ensure that our original value is still set. + db.View(func(tx *Tx) error { + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + fmt.Printf("The value for 'foo' is still: %s\n", string(value)) + return nil + }) + + // Output: + // The value for 'foo' is still: bar +} |