X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=blobdiff_plain;f=vendor%2Fgithub.com%2Fbtcsuite%2Fbtcd%2Fdatabase%2Fffldb%2Finterface_test.go;fp=vendor%2Fgithub.com%2Fbtcsuite%2Fbtcd%2Fdatabase%2Fffldb%2Finterface_test.go;h=0000000000000000000000000000000000000000;hp=4989913de9f93a067e9f5f8c6bc8b8208902a319;hb=d09b7a78d44dc259725902b8141cdba0d716b121;hpb=ee01d543fdfe1fd0a4d548965c66f7923ea7b062 diff --git a/vendor/github.com/btcsuite/btcd/database/ffldb/interface_test.go b/vendor/github.com/btcsuite/btcd/database/ffldb/interface_test.go deleted file mode 100644 index 4989913d..00000000 --- a/vendor/github.com/btcsuite/btcd/database/ffldb/interface_test.go +++ /dev/null @@ -1,2301 +0,0 @@ -// Copyright (c) 2015-2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -// This file intended to be copied into each backend driver directory. Each -// driver should have their own driver_test.go file which creates a database and -// invokes the testInterface function in this file to ensure the driver properly -// implements the interface. -// -// NOTE: When copying this file into the backend driver folder, the package name -// will need to be changed accordingly. - -package ffldb_test - -import ( - "bytes" - "compress/bzip2" - "encoding/binary" - "fmt" - "io" - "os" - "path/filepath" - "reflect" - "sync/atomic" - "testing" - "time" - - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/database" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" -) - -var ( - // blockDataNet is the expected network in the test block data. - blockDataNet = wire.MainNet - - // blockDataFile is the path to a file containing the first 256 blocks - // of the block chain. - blockDataFile = filepath.Join("..", "testdata", "blocks1-256.bz2") - - // errSubTestFail is used to signal that a sub test returned false. - errSubTestFail = fmt.Errorf("sub test failure") -) - -// loadBlocks loads the blocks contained in the testdata directory and returns -// a slice of them. -func loadBlocks(t *testing.T, dataFile string, network wire.BitcoinNet) ([]*btcutil.Block, error) { - // Open the file that contains the blocks for reading. - fi, err := os.Open(dataFile) - if err != nil { - t.Errorf("failed to open file %v, err %v", dataFile, err) - return nil, err - } - defer func() { - if err := fi.Close(); err != nil { - t.Errorf("failed to close file %v %v", dataFile, - err) - } - }() - dr := bzip2.NewReader(fi) - - // Set the first block as the genesis block. - blocks := make([]*btcutil.Block, 0, 256) - genesis := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) - blocks = append(blocks, genesis) - - // Load the remaining blocks. - for height := 1; ; height++ { - var net uint32 - err := binary.Read(dr, binary.LittleEndian, &net) - if err == io.EOF { - // Hit end of file at the expected offset. No error. - break - } - if err != nil { - t.Errorf("Failed to load network type for block %d: %v", - height, err) - return nil, err - } - if net != uint32(network) { - t.Errorf("Block doesn't match network: %v expects %v", - net, network) - return nil, err - } - - var blockLen uint32 - err = binary.Read(dr, binary.LittleEndian, &blockLen) - if err != nil { - t.Errorf("Failed to load block size for block %d: %v", - height, err) - return nil, err - } - - // Read the block. - blockBytes := make([]byte, blockLen) - _, err = io.ReadFull(dr, blockBytes) - if err != nil { - t.Errorf("Failed to load block %d: %v", height, err) - return nil, err - } - - // Deserialize and store the block. - block, err := btcutil.NewBlockFromBytes(blockBytes) - if err != nil { - t.Errorf("Failed to parse block %v: %v", height, err) - return nil, err - } - blocks = append(blocks, block) - } - - return blocks, nil -} - -// checkDbError ensures the passed error is a database.Error with an error code -// that matches the passed error code. -func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool { - dbErr, ok := gotErr.(database.Error) - if !ok { - t.Errorf("%s: unexpected error type - got %T, want %T", - testName, gotErr, database.Error{}) - return false - } - if dbErr.ErrorCode != wantErrCode { - t.Errorf("%s: unexpected error code - got %s (%s), want %s", - testName, dbErr.ErrorCode, dbErr.Description, - wantErrCode) - return false - } - - return true -} - -// testContext is used to store context information about a running test which -// is passed into helper functions. -type testContext struct { - t *testing.T - db database.DB - bucketDepth int - isWritable bool - blocks []*btcutil.Block -} - -// keyPair houses a key/value pair. It is used over maps so ordering can be -// maintained. -type keyPair struct { - key []byte - value []byte -} - -// lookupKey is a convenience method to lookup the requested key from the -// provided keypair slice along with whether or not the key was found. -func lookupKey(key []byte, values []keyPair) ([]byte, bool) { - for _, item := range values { - if bytes.Equal(item.key, key) { - return item.value, true - } - } - - return nil, false -} - -// toGetValues returns a copy of the provided keypairs with all of the nil -// values set to an empty byte slice. This is used to ensure that keys set to -// nil values result in empty byte slices when retrieved instead of nil. -func toGetValues(values []keyPair) []keyPair { - ret := make([]keyPair, len(values)) - copy(ret, values) - for i := range ret { - if ret[i].value == nil { - ret[i].value = make([]byte, 0) - } - } - return ret -} - -// rollbackValues returns a copy of the provided keypairs with all values set to -// nil. This is used to test that values are properly rolled back. -func rollbackValues(values []keyPair) []keyPair { - ret := make([]keyPair, len(values)) - copy(ret, values) - for i := range ret { - ret[i].value = nil - } - return ret -} - -// testCursorKeyPair checks that the provide key and value match the expected -// keypair at the provided index. It also ensures the index is in range for the -// provided slice of expected keypairs. -func testCursorKeyPair(tc *testContext, k, v []byte, index int, values []keyPair) bool { - if index >= len(values) || index < 0 { - tc.t.Errorf("Cursor: exceeded the expected range of values - "+ - "index %d, num values %d", index, len(values)) - return false - } - - pair := &values[index] - if !bytes.Equal(k, pair.key) { - tc.t.Errorf("Mismatched cursor key: index %d does not match "+ - "the expected key - got %q, want %q", index, k, - pair.key) - return false - } - if !bytes.Equal(v, pair.value) { - tc.t.Errorf("Mismatched cursor value: index %d does not match "+ - "the expected value - got %q, want %q", index, v, - pair.value) - return false - } - - return true -} - -// testGetValues checks that all of the provided key/value pairs can be -// retrieved from the database and the retrieved values match the provided -// values. -func testGetValues(tc *testContext, bucket database.Bucket, values []keyPair) bool { - for _, item := range values { - gotValue := bucket.Get(item.key) - if !reflect.DeepEqual(gotValue, item.value) { - tc.t.Errorf("Get: unexpected value for %q - got %q, "+ - "want %q", item.key, gotValue, item.value) - return false - } - } - - return true -} - -// testPutValues stores all of the provided key/value pairs in the provided -// bucket while checking for errors. -func testPutValues(tc *testContext, bucket database.Bucket, values []keyPair) bool { - for _, item := range values { - if err := bucket.Put(item.key, item.value); err != nil { - tc.t.Errorf("Put: unexpected error: %v", err) - return false - } - } - - return true -} - -// testDeleteValues removes all of the provided key/value pairs from the -// provided bucket. -func testDeleteValues(tc *testContext, bucket database.Bucket, values []keyPair) bool { - for _, item := range values { - if err := bucket.Delete(item.key); err != nil { - tc.t.Errorf("Delete: unexpected error: %v", err) - return false - } - } - - return true -} - -// testCursorInterface ensures the cursor itnerface is working properly by -// exercising all of its functions on the passed bucket. -func testCursorInterface(tc *testContext, bucket database.Bucket) bool { - // Ensure a cursor can be obtained for the bucket. - cursor := bucket.Cursor() - if cursor == nil { - tc.t.Error("Bucket.Cursor: unexpected nil cursor returned") - return false - } - - // Ensure the cursor returns the same bucket it was created for. - if cursor.Bucket() != bucket { - tc.t.Error("Cursor.Bucket: does not match the bucket it was " + - "created for") - return false - } - - if tc.isWritable { - unsortedValues := []keyPair{ - {[]byte("cursor"), []byte("val1")}, - {[]byte("abcd"), []byte("val2")}, - {[]byte("bcd"), []byte("val3")}, - {[]byte("defg"), nil}, - } - sortedValues := []keyPair{ - {[]byte("abcd"), []byte("val2")}, - {[]byte("bcd"), []byte("val3")}, - {[]byte("cursor"), []byte("val1")}, - {[]byte("defg"), nil}, - } - - // Store the values to be used in the cursor tests in unsorted - // order and ensure they were actually stored. - if !testPutValues(tc, bucket, unsortedValues) { - return false - } - if !testGetValues(tc, bucket, toGetValues(unsortedValues)) { - return false - } - - // Ensure the cursor returns all items in byte-sorted order when - // iterating forward. - curIdx := 0 - for ok := cursor.First(); ok; ok = cursor.Next() { - k, v := cursor.Key(), cursor.Value() - if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) { - return false - } - curIdx++ - } - if curIdx != len(unsortedValues) { - tc.t.Errorf("Cursor: expected to iterate %d values, "+ - "but only iterated %d", len(unsortedValues), - curIdx) - return false - } - - // Ensure the cursor returns all items in reverse byte-sorted - // order when iterating in reverse. - curIdx = len(sortedValues) - 1 - for ok := cursor.Last(); ok; ok = cursor.Prev() { - k, v := cursor.Key(), cursor.Value() - if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) { - return false - } - curIdx-- - } - if curIdx > -1 { - tc.t.Errorf("Reverse cursor: expected to iterate %d "+ - "values, but only iterated %d", - len(sortedValues), len(sortedValues)-(curIdx+1)) - return false - } - - // Ensure forward iteration works as expected after seeking. - middleIdx := (len(sortedValues) - 1) / 2 - seekKey := sortedValues[middleIdx].key - curIdx = middleIdx - for ok := cursor.Seek(seekKey); ok; ok = cursor.Next() { - k, v := cursor.Key(), cursor.Value() - if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) { - return false - } - curIdx++ - } - if curIdx != len(sortedValues) { - tc.t.Errorf("Cursor after seek: expected to iterate "+ - "%d values, but only iterated %d", - len(sortedValues)-middleIdx, curIdx-middleIdx) - return false - } - - // Ensure reverse iteration works as expected after seeking. - curIdx = middleIdx - for ok := cursor.Seek(seekKey); ok; ok = cursor.Prev() { - k, v := cursor.Key(), cursor.Value() - if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) { - return false - } - curIdx-- - } - if curIdx > -1 { - tc.t.Errorf("Reverse cursor after seek: expected to "+ - "iterate %d values, but only iterated %d", - len(sortedValues)-middleIdx, middleIdx-curIdx) - return false - } - - // Ensure the cursor deletes items properly. - if !cursor.First() { - tc.t.Errorf("Cursor.First: no value") - return false - } - k := cursor.Key() - if err := cursor.Delete(); err != nil { - tc.t.Errorf("Cursor.Delete: unexpected error: %v", err) - return false - } - if val := bucket.Get(k); val != nil { - tc.t.Errorf("Cursor.Delete: value for key %q was not "+ - "deleted", k) - return false - } - } - - return true -} - -// testNestedBucket reruns the testBucketInterface against a nested bucket along -// with a counter to only test a couple of level deep. -func testNestedBucket(tc *testContext, testBucket database.Bucket) bool { - // Don't go more than 2 nested levels deep. - if tc.bucketDepth > 1 { - return true - } - - tc.bucketDepth++ - defer func() { - tc.bucketDepth-- - }() - return testBucketInterface(tc, testBucket) -} - -// testBucketInterface ensures the bucket interface is working properly by -// exercising all of its functions. This includes the cursor interface for the -// cursor returned from the bucket. -func testBucketInterface(tc *testContext, bucket database.Bucket) bool { - if bucket.Writable() != tc.isWritable { - tc.t.Errorf("Bucket writable state does not match.") - return false - } - - if tc.isWritable { - // keyValues holds the keys and values to use when putting - // values into the bucket. - keyValues := []keyPair{ - {[]byte("bucketkey1"), []byte("foo1")}, - {[]byte("bucketkey2"), []byte("foo2")}, - {[]byte("bucketkey3"), []byte("foo3")}, - {[]byte("bucketkey4"), nil}, - } - expectedKeyValues := toGetValues(keyValues) - if !testPutValues(tc, bucket, keyValues) { - return false - } - - if !testGetValues(tc, bucket, expectedKeyValues) { - return false - } - - // Ensure errors returned from the user-supplied ForEach - // function are returned. - forEachError := fmt.Errorf("example foreach error") - err := bucket.ForEach(func(k, v []byte) error { - return forEachError - }) - if err != forEachError { - tc.t.Errorf("ForEach: inner function error not "+ - "returned - got %v, want %v", err, forEachError) - return false - } - - // Iterate all of the keys using ForEach while making sure the - // stored values are the expected values. - keysFound := make(map[string]struct{}, len(keyValues)) - err = bucket.ForEach(func(k, v []byte) error { - wantV, found := lookupKey(k, expectedKeyValues) - if !found { - return fmt.Errorf("ForEach: key '%s' should "+ - "exist", k) - } - - if !reflect.DeepEqual(v, wantV) { - return fmt.Errorf("ForEach: value for key '%s' "+ - "does not match - got %s, want %s", k, - v, wantV) - } - - keysFound[string(k)] = struct{}{} - return nil - }) - if err != nil { - tc.t.Errorf("%v", err) - return false - } - - // Ensure all keys were iterated. - for _, item := range keyValues { - if _, ok := keysFound[string(item.key)]; !ok { - tc.t.Errorf("ForEach: key '%s' was not iterated "+ - "when it should have been", item.key) - return false - } - } - - // Delete the keys and ensure they were deleted. - if !testDeleteValues(tc, bucket, keyValues) { - return false - } - if !testGetValues(tc, bucket, rollbackValues(keyValues)) { - return false - } - - // Ensure creating a new bucket works as expected. - testBucketName := []byte("testbucket") - testBucket, err := bucket.CreateBucket(testBucketName) - if err != nil { - tc.t.Errorf("CreateBucket: unexpected error: %v", err) - return false - } - if !testNestedBucket(tc, testBucket) { - return false - } - - // Ensure errors returned from the user-supplied ForEachBucket - // function are returned. - err = bucket.ForEachBucket(func(k []byte) error { - return forEachError - }) - if err != forEachError { - tc.t.Errorf("ForEachBucket: inner function error not "+ - "returned - got %v, want %v", err, forEachError) - return false - } - - // Ensure creating a bucket that already exists fails with the - // expected error. - wantErrCode := database.ErrBucketExists - _, err = bucket.CreateBucket(testBucketName) - if !checkDbError(tc.t, "CreateBucket", err, wantErrCode) { - return false - } - - // Ensure CreateBucketIfNotExists returns an existing bucket. - testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) - if err != nil { - tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ - "error: %v", err) - return false - } - if !testNestedBucket(tc, testBucket) { - return false - } - - // Ensure retrieving an existing bucket works as expected. - testBucket = bucket.Bucket(testBucketName) - if !testNestedBucket(tc, testBucket) { - return false - } - - // Ensure deleting a bucket works as intended. - if err := bucket.DeleteBucket(testBucketName); err != nil { - tc.t.Errorf("DeleteBucket: unexpected error: %v", err) - return false - } - if b := bucket.Bucket(testBucketName); b != nil { - tc.t.Errorf("DeleteBucket: bucket '%s' still exists", - testBucketName) - return false - } - - // Ensure deleting a bucket that doesn't exist returns the - // expected error. - wantErrCode = database.ErrBucketNotFound - err = bucket.DeleteBucket(testBucketName) - if !checkDbError(tc.t, "DeleteBucket", err, wantErrCode) { - return false - } - - // Ensure CreateBucketIfNotExists creates a new bucket when - // it doesn't already exist. - testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) - if err != nil { - tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ - "error: %v", err) - return false - } - if !testNestedBucket(tc, testBucket) { - return false - } - - // Ensure the cursor interface works as expected. - if !testCursorInterface(tc, testBucket) { - return false - } - - // Delete the test bucket to avoid leaving it around for future - // calls. - if err := bucket.DeleteBucket(testBucketName); err != nil { - tc.t.Errorf("DeleteBucket: unexpected error: %v", err) - return false - } - if b := bucket.Bucket(testBucketName); b != nil { - tc.t.Errorf("DeleteBucket: bucket '%s' still exists", - testBucketName) - return false - } - } else { - // Put should fail with bucket that is not writable. - testName := "unwritable tx put" - wantErrCode := database.ErrTxNotWritable - failBytes := []byte("fail") - err := bucket.Put(failBytes, failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Delete should fail with bucket that is not writable. - testName = "unwritable tx delete" - err = bucket.Delete(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // CreateBucket should fail with bucket that is not writable. - testName = "unwritable tx create bucket" - _, err = bucket.CreateBucket(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // CreateBucketIfNotExists should fail with bucket that is not - // writable. - testName = "unwritable tx create bucket if not exists" - _, err = bucket.CreateBucketIfNotExists(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // DeleteBucket should fail with bucket that is not writable. - testName = "unwritable tx delete bucket" - err = bucket.DeleteBucket(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure the cursor interface works as expected with read-only - // buckets. - if !testCursorInterface(tc, bucket) { - return false - } - } - - return true -} - -// rollbackOnPanic rolls the passed transaction back if the code in the calling -// function panics. This is useful in case the tests unexpectedly panic which -// would leave any manually created transactions with the database mutex locked -// thereby leading to a deadlock and masking the real reason for the panic. It -// also logs a test error and repanics so the original panic can be traced. -func rollbackOnPanic(t *testing.T, tx database.Tx) { - if err := recover(); err != nil { - t.Errorf("Unexpected panic: %v", err) - _ = tx.Rollback() - panic(err) - } -} - -// testMetadataManualTxInterface ensures that the manual transactions metadata -// interface works as expected. -func testMetadataManualTxInterface(tc *testContext) bool { - // populateValues tests that populating values works as expected. - // - // When the writable flag is false, a read-only tranasction is created, - // standard bucket tests for read-only transactions are performed, and - // the Commit function is checked to ensure it fails as expected. - // - // Otherwise, a read-write transaction is created, the values are - // written, standard bucket tests for read-write transactions are - // performed, and then the transaction is either committed or rolled - // back depending on the flag. - bucket1Name := []byte("bucket1") - populateValues := func(writable, rollback bool, putValues []keyPair) bool { - tx, err := tc.db.Begin(writable) - if err != nil { - tc.t.Errorf("Begin: unexpected error %v", err) - return false - } - defer rollbackOnPanic(tc.t, tx) - - metadataBucket := tx.Metadata() - if metadataBucket == nil { - tc.t.Errorf("Metadata: unexpected nil bucket") - _ = tx.Rollback() - return false - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - tc.t.Errorf("Bucket1: unexpected nil bucket") - return false - } - - tc.isWritable = writable - if !testBucketInterface(tc, bucket1) { - _ = tx.Rollback() - return false - } - - if !writable { - // The transaction is not writable, so it should fail - // the commit. - testName := "unwritable tx commit" - wantErrCode := database.ErrTxNotWritable - err := tx.Commit() - if !checkDbError(tc.t, testName, err, wantErrCode) { - _ = tx.Rollback() - return false - } - } else { - if !testPutValues(tc, bucket1, putValues) { - return false - } - - if rollback { - // Rollback the transaction. - if err := tx.Rollback(); err != nil { - tc.t.Errorf("Rollback: unexpected "+ - "error %v", err) - return false - } - } else { - // The commit should succeed. - if err := tx.Commit(); err != nil { - tc.t.Errorf("Commit: unexpected error "+ - "%v", err) - return false - } - } - } - - return true - } - - // checkValues starts a read-only transaction and checks that all of - // the key/value pairs specified in the expectedValues parameter match - // what's in the database. - checkValues := func(expectedValues []keyPair) bool { - tx, err := tc.db.Begin(false) - if err != nil { - tc.t.Errorf("Begin: unexpected error %v", err) - return false - } - defer rollbackOnPanic(tc.t, tx) - - metadataBucket := tx.Metadata() - if metadataBucket == nil { - tc.t.Errorf("Metadata: unexpected nil bucket") - _ = tx.Rollback() - return false - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - tc.t.Errorf("Bucket1: unexpected nil bucket") - return false - } - - if !testGetValues(tc, bucket1, expectedValues) { - _ = tx.Rollback() - return false - } - - // Rollback the read-only transaction. - if err := tx.Rollback(); err != nil { - tc.t.Errorf("Commit: unexpected error %v", err) - return false - } - - return true - } - - // deleteValues starts a read-write transaction and deletes the keys - // in the passed key/value pairs. - deleteValues := func(values []keyPair) bool { - tx, err := tc.db.Begin(true) - if err != nil { - - } - defer rollbackOnPanic(tc.t, tx) - - metadataBucket := tx.Metadata() - if metadataBucket == nil { - tc.t.Errorf("Metadata: unexpected nil bucket") - _ = tx.Rollback() - return false - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - tc.t.Errorf("Bucket1: unexpected nil bucket") - return false - } - - // Delete the keys and ensure they were deleted. - if !testDeleteValues(tc, bucket1, values) { - _ = tx.Rollback() - return false - } - if !testGetValues(tc, bucket1, rollbackValues(values)) { - _ = tx.Rollback() - return false - } - - // Commit the changes and ensure it was successful. - if err := tx.Commit(); err != nil { - tc.t.Errorf("Commit: unexpected error %v", err) - return false - } - - return true - } - - // keyValues holds the keys and values to use when putting values into a - // bucket. - var keyValues = []keyPair{ - {[]byte("umtxkey1"), []byte("foo1")}, - {[]byte("umtxkey2"), []byte("foo2")}, - {[]byte("umtxkey3"), []byte("foo3")}, - {[]byte("umtxkey4"), nil}, - } - - // Ensure that attempting populating the values using a read-only - // transaction fails as expected. - if !populateValues(false, true, keyValues) { - return false - } - if !checkValues(rollbackValues(keyValues)) { - return false - } - - // Ensure that attempting populating the values using a read-write - // transaction and then rolling it back yields the expected values. - if !populateValues(true, true, keyValues) { - return false - } - if !checkValues(rollbackValues(keyValues)) { - return false - } - - // Ensure that attempting populating the values using a read-write - // transaction and then committing it stores the expected values. - if !populateValues(true, false, keyValues) { - return false - } - if !checkValues(toGetValues(keyValues)) { - return false - } - - // Clean up the keys. - if !deleteValues(keyValues) { - return false - } - - return true -} - -// testManagedTxPanics ensures calling Rollback of Commit inside a managed -// transaction panics. -func testManagedTxPanics(tc *testContext) bool { - testPanic := func(fn func()) (paniced bool) { - // Setup a defer to catch the expected panic and update the - // return variable. - defer func() { - if err := recover(); err != nil { - paniced = true - } - }() - - fn() - return false - } - - // Ensure calling Commit on a managed read-only transaction panics. - paniced := testPanic(func() { - tc.db.View(func(tx database.Tx) error { - tx.Commit() - return nil - }) - }) - if !paniced { - tc.t.Error("Commit called inside View did not panic") - return false - } - - // Ensure calling Rollback on a managed read-only transaction panics. - paniced = testPanic(func() { - tc.db.View(func(tx database.Tx) error { - tx.Rollback() - return nil - }) - }) - if !paniced { - tc.t.Error("Rollback called inside View did not panic") - return false - } - - // Ensure calling Commit on a managed read-write transaction panics. - paniced = testPanic(func() { - tc.db.Update(func(tx database.Tx) error { - tx.Commit() - return nil - }) - }) - if !paniced { - tc.t.Error("Commit called inside Update did not panic") - return false - } - - // Ensure calling Rollback on a managed read-write transaction panics. - paniced = testPanic(func() { - tc.db.Update(func(tx database.Tx) error { - tx.Rollback() - return nil - }) - }) - if !paniced { - tc.t.Error("Rollback called inside Update did not panic") - return false - } - - return true -} - -// testMetadataTxInterface tests all facets of the managed read/write and -// manual transaction metadata interfaces as well as the bucket interfaces under -// them. -func testMetadataTxInterface(tc *testContext) bool { - if !testManagedTxPanics(tc) { - return false - } - - bucket1Name := []byte("bucket1") - err := tc.db.Update(func(tx database.Tx) error { - _, err := tx.Metadata().CreateBucket(bucket1Name) - return err - }) - if err != nil { - tc.t.Errorf("Update: unexpected error creating bucket: %v", err) - return false - } - - if !testMetadataManualTxInterface(tc) { - return false - } - - // keyValues holds the keys and values to use when putting values - // into a bucket. - keyValues := []keyPair{ - {[]byte("mtxkey1"), []byte("foo1")}, - {[]byte("mtxkey2"), []byte("foo2")}, - {[]byte("mtxkey3"), []byte("foo3")}, - {[]byte("mtxkey4"), nil}, - } - - // Test the bucket interface via a managed read-only transaction. - err = tc.db.View(func(tx database.Tx) error { - metadataBucket := tx.Metadata() - if metadataBucket == nil { - return fmt.Errorf("Metadata: unexpected nil bucket") - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - return fmt.Errorf("Bucket1: unexpected nil bucket") - } - - tc.isWritable = false - if !testBucketInterface(tc, bucket1) { - return errSubTestFail - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Ensure errors returned from the user-supplied View function are - // returned. - viewError := fmt.Errorf("example view error") - err = tc.db.View(func(tx database.Tx) error { - return viewError - }) - if err != viewError { - tc.t.Errorf("View: inner function error not returned - got "+ - "%v, want %v", err, viewError) - return false - } - - // Test the bucket interface via a managed read-write transaction. - // Also, put a series of values and force a rollback so the following - // code can ensure the values were not stored. - forceRollbackError := fmt.Errorf("force rollback") - err = tc.db.Update(func(tx database.Tx) error { - metadataBucket := tx.Metadata() - if metadataBucket == nil { - return fmt.Errorf("Metadata: unexpected nil bucket") - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - return fmt.Errorf("Bucket1: unexpected nil bucket") - } - - tc.isWritable = true - if !testBucketInterface(tc, bucket1) { - return errSubTestFail - } - - if !testPutValues(tc, bucket1, keyValues) { - return errSubTestFail - } - - // Return an error to force a rollback. - return forceRollbackError - }) - if err != forceRollbackError { - if err == errSubTestFail { - return false - } - - tc.t.Errorf("Update: inner function error not returned - got "+ - "%v, want %v", err, forceRollbackError) - return false - } - - // Ensure the values that should not have been stored due to the forced - // rollback above were not actually stored. - err = tc.db.View(func(tx database.Tx) error { - metadataBucket := tx.Metadata() - if metadataBucket == nil { - return fmt.Errorf("Metadata: unexpected nil bucket") - } - - if !testGetValues(tc, metadataBucket, rollbackValues(keyValues)) { - return errSubTestFail - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Store a series of values via a managed read-write transaction. - err = tc.db.Update(func(tx database.Tx) error { - metadataBucket := tx.Metadata() - if metadataBucket == nil { - return fmt.Errorf("Metadata: unexpected nil bucket") - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - return fmt.Errorf("Bucket1: unexpected nil bucket") - } - - if !testPutValues(tc, bucket1, keyValues) { - return errSubTestFail - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Ensure the values stored above were committed as expected. - err = tc.db.View(func(tx database.Tx) error { - metadataBucket := tx.Metadata() - if metadataBucket == nil { - return fmt.Errorf("Metadata: unexpected nil bucket") - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - return fmt.Errorf("Bucket1: unexpected nil bucket") - } - - if !testGetValues(tc, bucket1, toGetValues(keyValues)) { - return errSubTestFail - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Clean up the values stored above in a managed read-write transaction. - err = tc.db.Update(func(tx database.Tx) error { - metadataBucket := tx.Metadata() - if metadataBucket == nil { - return fmt.Errorf("Metadata: unexpected nil bucket") - } - - bucket1 := metadataBucket.Bucket(bucket1Name) - if bucket1 == nil { - return fmt.Errorf("Bucket1: unexpected nil bucket") - } - - if !testDeleteValues(tc, bucket1, keyValues) { - return errSubTestFail - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - return true -} - -// testFetchBlockIOMissing ensures that all of the block retrieval API functions -// work as expected when requesting blocks that don't exist. -func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool { - wantErrCode := database.ErrBlockNotFound - - // --------------------- - // Non-bulk Block IO API - // --------------------- - - // Test the individual block APIs one block at a time to ensure they - // return the expected error. Also, build the data needed to test the - // bulk APIs below while looping. - allBlockHashes := make([]chainhash.Hash, len(tc.blocks)) - allBlockRegions := make([]database.BlockRegion, len(tc.blocks)) - for i, block := range tc.blocks { - blockHash := block.Hash() - allBlockHashes[i] = *blockHash - - txLocs, err := block.TxLoc() - if err != nil { - tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i, - err) - return false - } - - // Ensure FetchBlock returns expected error. - testName := fmt.Sprintf("FetchBlock #%d on missing block", i) - _, err = tx.FetchBlock(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure FetchBlockHeader returns expected error. - testName = fmt.Sprintf("FetchBlockHeader #%d on missing block", - i) - _, err = tx.FetchBlockHeader(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure the first transaction fetched as a block region from - // the database returns the expected error. - region := database.BlockRegion{ - Hash: blockHash, - Offset: uint32(txLocs[0].TxStart), - Len: uint32(txLocs[0].TxLen), - } - allBlockRegions[i] = region - _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure HasBlock returns false. - hasBlock, err := tx.HasBlock(blockHash) - if err != nil { - tc.t.Errorf("HasBlock #%d: unexpected err: %v", i, err) - return false - } - if hasBlock { - tc.t.Errorf("HasBlock #%d: should not have block", i) - return false - } - } - - // ----------------- - // Bulk Block IO API - // ----------------- - - // Ensure FetchBlocks returns expected error. - testName := "FetchBlocks on missing blocks" - _, err := tx.FetchBlocks(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure FetchBlockHeaders returns expected error. - testName = "FetchBlockHeaders on missing blocks" - _, err = tx.FetchBlockHeaders(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure FetchBlockRegions returns expected error. - testName = "FetchBlockRegions on missing blocks" - _, err = tx.FetchBlockRegions(allBlockRegions) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure HasBlocks returns false for all blocks. - hasBlocks, err := tx.HasBlocks(allBlockHashes) - if err != nil { - tc.t.Errorf("HasBlocks: unexpected err: %v", err) - } - for i, hasBlock := range hasBlocks { - if hasBlock { - tc.t.Errorf("HasBlocks #%d: should not have block", i) - return false - } - } - - return true -} - -// testFetchBlockIO ensures all of the block retrieval API functions work as -// expected for the provide set of blocks. The blocks must already be stored in -// the database, or at least stored into the the passed transaction. It also -// tests several error conditions such as ensuring the expected errors are -// returned when fetching blocks, headers, and regions that don't exist. -func testFetchBlockIO(tc *testContext, tx database.Tx) bool { - // --------------------- - // Non-bulk Block IO API - // --------------------- - - // Test the individual block APIs one block at a time. Also, build the - // data needed to test the bulk APIs below while looping. - allBlockHashes := make([]chainhash.Hash, len(tc.blocks)) - allBlockBytes := make([][]byte, len(tc.blocks)) - allBlockTxLocs := make([][]wire.TxLoc, len(tc.blocks)) - allBlockRegions := make([]database.BlockRegion, len(tc.blocks)) - for i, block := range tc.blocks { - blockHash := block.Hash() - allBlockHashes[i] = *blockHash - - blockBytes, err := block.Bytes() - if err != nil { - tc.t.Errorf("block.Bytes(%d): unexpected error: %v", i, - err) - return false - } - allBlockBytes[i] = blockBytes - - txLocs, err := block.TxLoc() - if err != nil { - tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i, - err) - return false - } - allBlockTxLocs[i] = txLocs - - // Ensure the block data fetched from the database matches the - // expected bytes. - gotBlockBytes, err := tx.FetchBlock(blockHash) - if err != nil { - tc.t.Errorf("FetchBlock(%s): unexpected error: %v", - blockHash, err) - return false - } - if !bytes.Equal(gotBlockBytes, blockBytes) { - tc.t.Errorf("FetchBlock(%s): bytes mismatch: got %x, "+ - "want %x", blockHash, gotBlockBytes, blockBytes) - return false - } - - // Ensure the block header fetched from the database matches the - // expected bytes. - wantHeaderBytes := blockBytes[0:wire.MaxBlockHeaderPayload] - gotHeaderBytes, err := tx.FetchBlockHeader(blockHash) - if err != nil { - tc.t.Errorf("FetchBlockHeader(%s): unexpected error: %v", - blockHash, err) - return false - } - if !bytes.Equal(gotHeaderBytes, wantHeaderBytes) { - tc.t.Errorf("FetchBlockHeader(%s): bytes mismatch: "+ - "got %x, want %x", blockHash, gotHeaderBytes, - wantHeaderBytes) - return false - } - - // Ensure the first transaction fetched as a block region from - // the database matches the expected bytes. - region := database.BlockRegion{ - Hash: blockHash, - Offset: uint32(txLocs[0].TxStart), - Len: uint32(txLocs[0].TxLen), - } - allBlockRegions[i] = region - endRegionOffset := region.Offset + region.Len - wantRegionBytes := blockBytes[region.Offset:endRegionOffset] - gotRegionBytes, err := tx.FetchBlockRegion(®ion) - if err != nil { - tc.t.Errorf("FetchBlockRegion(%s): unexpected error: %v", - blockHash, err) - return false - } - if !bytes.Equal(gotRegionBytes, wantRegionBytes) { - tc.t.Errorf("FetchBlockRegion(%s): bytes mismatch: "+ - "got %x, want %x", blockHash, gotRegionBytes, - wantRegionBytes) - return false - } - - // Ensure the block header fetched from the database matches the - // expected bytes. - hasBlock, err := tx.HasBlock(blockHash) - if err != nil { - tc.t.Errorf("HasBlock(%s): unexpected error: %v", - blockHash, err) - return false - } - if !hasBlock { - tc.t.Errorf("HasBlock(%s): database claims it doesn't "+ - "have the block when it should", blockHash) - return false - } - - // ----------------------- - // Invalid blocks/regions. - // ----------------------- - - // Ensure fetching a block that doesn't exist returns the - // expected error. - badBlockHash := &chainhash.Hash{} - testName := fmt.Sprintf("FetchBlock(%s) invalid block", - badBlockHash) - wantErrCode := database.ErrBlockNotFound - _, err = tx.FetchBlock(badBlockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure fetching a block header that doesn't exist returns - // the expected error. - testName = fmt.Sprintf("FetchBlockHeader(%s) invalid block", - badBlockHash) - _, err = tx.FetchBlockHeader(badBlockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure fetching a block region in a block that doesn't exist - // return the expected error. - testName = fmt.Sprintf("FetchBlockRegion(%s) invalid hash", - badBlockHash) - wantErrCode = database.ErrBlockNotFound - region.Hash = badBlockHash - region.Offset = ^uint32(0) - _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure fetching a block region that is out of bounds returns - // the expected error. - testName = fmt.Sprintf("FetchBlockRegion(%s) invalid region", - blockHash) - wantErrCode = database.ErrBlockRegionInvalid - region.Hash = blockHash - region.Offset = ^uint32(0) - _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - } - - // ----------------- - // Bulk Block IO API - // ----------------- - - // Ensure the bulk block data fetched from the database matches the - // expected bytes. - blockData, err := tx.FetchBlocks(allBlockHashes) - if err != nil { - tc.t.Errorf("FetchBlocks: unexpected error: %v", err) - return false - } - if len(blockData) != len(allBlockBytes) { - tc.t.Errorf("FetchBlocks: unexpected number of results - got "+ - "%d, want %d", len(blockData), len(allBlockBytes)) - return false - } - for i := 0; i < len(blockData); i++ { - blockHash := allBlockHashes[i] - wantBlockBytes := allBlockBytes[i] - gotBlockBytes := blockData[i] - if !bytes.Equal(gotBlockBytes, wantBlockBytes) { - tc.t.Errorf("FetchBlocks(%s): bytes mismatch: got %x, "+ - "want %x", blockHash, gotBlockBytes, - wantBlockBytes) - return false - } - } - - // Ensure the bulk block headers fetched from the database match the - // expected bytes. - blockHeaderData, err := tx.FetchBlockHeaders(allBlockHashes) - if err != nil { - tc.t.Errorf("FetchBlockHeaders: unexpected error: %v", err) - return false - } - if len(blockHeaderData) != len(allBlockBytes) { - tc.t.Errorf("FetchBlockHeaders: unexpected number of results "+ - "- got %d, want %d", len(blockHeaderData), - len(allBlockBytes)) - return false - } - for i := 0; i < len(blockHeaderData); i++ { - blockHash := allBlockHashes[i] - wantHeaderBytes := allBlockBytes[i][0:wire.MaxBlockHeaderPayload] - gotHeaderBytes := blockHeaderData[i] - if !bytes.Equal(gotHeaderBytes, wantHeaderBytes) { - tc.t.Errorf("FetchBlockHeaders(%s): bytes mismatch: "+ - "got %x, want %x", blockHash, gotHeaderBytes, - wantHeaderBytes) - return false - } - } - - // Ensure the first transaction of every block fetched in bulk block - // regions from the database matches the expected bytes. - allRegionBytes, err := tx.FetchBlockRegions(allBlockRegions) - if err != nil { - tc.t.Errorf("FetchBlockRegions: unexpected error: %v", err) - return false - - } - if len(allRegionBytes) != len(allBlockRegions) { - tc.t.Errorf("FetchBlockRegions: unexpected number of results "+ - "- got %d, want %d", len(allRegionBytes), - len(allBlockRegions)) - return false - } - for i, gotRegionBytes := range allRegionBytes { - region := &allBlockRegions[i] - endRegionOffset := region.Offset + region.Len - wantRegionBytes := blockData[i][region.Offset:endRegionOffset] - if !bytes.Equal(gotRegionBytes, wantRegionBytes) { - tc.t.Errorf("FetchBlockRegions(%d): bytes mismatch: "+ - "got %x, want %x", i, gotRegionBytes, - wantRegionBytes) - return false - } - } - - // Ensure the bulk determination of whether a set of block hashes are in - // the database returns true for all loaded blocks. - hasBlocks, err := tx.HasBlocks(allBlockHashes) - if err != nil { - tc.t.Errorf("HasBlocks: unexpected error: %v", err) - return false - } - for i, hasBlock := range hasBlocks { - if !hasBlock { - tc.t.Errorf("HasBlocks(%d): should have block", i) - return false - } - } - - // ----------------------- - // Invalid blocks/regions. - // ----------------------- - - // Ensure fetching blocks for which one doesn't exist returns the - // expected error. - testName := "FetchBlocks invalid hash" - badBlockHashes := make([]chainhash.Hash, len(allBlockHashes)+1) - copy(badBlockHashes, allBlockHashes) - badBlockHashes[len(badBlockHashes)-1] = chainhash.Hash{} - wantErrCode := database.ErrBlockNotFound - _, err = tx.FetchBlocks(badBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure fetching block headers for which one doesn't exist returns the - // expected error. - testName = "FetchBlockHeaders invalid hash" - _, err = tx.FetchBlockHeaders(badBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure fetching block regions for which one of blocks doesn't exist - // returns expected error. - testName = "FetchBlockRegions invalid hash" - badBlockRegions := make([]database.BlockRegion, len(allBlockRegions)+1) - copy(badBlockRegions, allBlockRegions) - badBlockRegions[len(badBlockRegions)-1].Hash = &chainhash.Hash{} - wantErrCode = database.ErrBlockNotFound - _, err = tx.FetchBlockRegions(badBlockRegions) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure fetching block regions that are out of bounds returns the - // expected error. - testName = "FetchBlockRegions invalid regions" - badBlockRegions = badBlockRegions[:len(badBlockRegions)-1] - for i := range badBlockRegions { - badBlockRegions[i].Offset = ^uint32(0) - } - wantErrCode = database.ErrBlockRegionInvalid - _, err = tx.FetchBlockRegions(badBlockRegions) - return checkDbError(tc.t, testName, err, wantErrCode) -} - -// testBlockIOTxInterface ensures that the block IO interface works as expected -// for both managed read/write and manual transactions. This function leaves -// all of the stored blocks in the database. -func testBlockIOTxInterface(tc *testContext) bool { - // Ensure attempting to store a block with a read-only transaction fails - // with the expected error. - err := tc.db.View(func(tx database.Tx) error { - wantErrCode := database.ErrTxNotWritable - for i, block := range tc.blocks { - testName := fmt.Sprintf("StoreBlock(%d) on ro tx", i) - err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return errSubTestFail - } - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Populate the database with loaded blocks and ensure all of the data - // fetching APIs work properly on them within the transaction before a - // commit or rollback. Then, force a rollback so the code below can - // ensure none of the data actually gets stored. - forceRollbackError := fmt.Errorf("force rollback") - err = tc.db.Update(func(tx database.Tx) error { - // Store all blocks in the same transaction. - for i, block := range tc.blocks { - err := tx.StoreBlock(block) - if err != nil { - tc.t.Errorf("StoreBlock #%d: unexpected error: "+ - "%v", i, err) - return errSubTestFail - } - } - - // Ensure attempting to store the same block again, before the - // transaction has been committed, returns the expected error. - wantErrCode := database.ErrBlockExists - for i, block := range tc.blocks { - testName := fmt.Sprintf("duplicate block entry #%d "+ - "(before commit)", i) - err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return errSubTestFail - } - } - - // Ensure that all data fetches from the stored blocks before - // the transaction has been committed work as expected. - if !testFetchBlockIO(tc, tx) { - return errSubTestFail - } - - return forceRollbackError - }) - if err != forceRollbackError { - if err == errSubTestFail { - return false - } - - tc.t.Errorf("Update: inner function error not returned - got "+ - "%v, want %v", err, forceRollbackError) - return false - } - - // Ensure rollback was successful - err = tc.db.View(func(tx database.Tx) error { - if !testFetchBlockIOMissing(tc, tx) { - return errSubTestFail - } - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Populate the database with loaded blocks and ensure all of the data - // fetching APIs work properly. - err = tc.db.Update(func(tx database.Tx) error { - // Store a bunch of blocks in the same transaction. - for i, block := range tc.blocks { - err := tx.StoreBlock(block) - if err != nil { - tc.t.Errorf("StoreBlock #%d: unexpected error: "+ - "%v", i, err) - return errSubTestFail - } - } - - // Ensure attempting to store the same block again while in the - // same transaction, but before it has been committed, returns - // the expected error. - for i, block := range tc.blocks { - testName := fmt.Sprintf("duplicate block entry #%d "+ - "(before commit)", i) - wantErrCode := database.ErrBlockExists - err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return errSubTestFail - } - } - - // Ensure that all data fetches from the stored blocks before - // the transaction has been committed work as expected. - if !testFetchBlockIO(tc, tx) { - return errSubTestFail - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Ensure all data fetch tests work as expected using a managed - // read-only transaction after the data was successfully committed - // above. - err = tc.db.View(func(tx database.Tx) error { - if !testFetchBlockIO(tc, tx) { - return errSubTestFail - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - // Ensure all data fetch tests work as expected using a managed - // read-write transaction after the data was successfully committed - // above. - err = tc.db.Update(func(tx database.Tx) error { - if !testFetchBlockIO(tc, tx) { - return errSubTestFail - } - - // Ensure attempting to store existing blocks again returns the - // expected error. Note that this is different from the - // previous version since this is a new transaction after the - // blocks have been committed. - wantErrCode := database.ErrBlockExists - for i, block := range tc.blocks { - testName := fmt.Sprintf("duplicate block entry #%d "+ - "(before commit)", i) - err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return errSubTestFail - } - } - - return nil - }) - if err != nil { - if err != errSubTestFail { - tc.t.Errorf("%v", err) - } - return false - } - - return true -} - -// testClosedTxInterface ensures that both the metadata and block IO API -// functions behave as expected when attempted against a closed transaction. -func testClosedTxInterface(tc *testContext, tx database.Tx) bool { - wantErrCode := database.ErrTxClosed - bucket := tx.Metadata() - cursor := tx.Metadata().Cursor() - bucketName := []byte("closedtxbucket") - keyName := []byte("closedtxkey") - - // ------------ - // Metadata API - // ------------ - - // Ensure that attempting to get an existing bucket returns nil when the - // transaction is closed. - if b := bucket.Bucket(bucketName); b != nil { - tc.t.Errorf("Bucket: did not return nil on closed tx") - return false - } - - // Ensure CreateBucket returns expected error. - testName := "CreateBucket on closed tx" - _, err := bucket.CreateBucket(bucketName) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure CreateBucketIfNotExists returns expected error. - testName = "CreateBucketIfNotExists on closed tx" - _, err = bucket.CreateBucketIfNotExists(bucketName) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure Delete returns expected error. - testName = "Delete on closed tx" - err = bucket.Delete(keyName) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure DeleteBucket returns expected error. - testName = "DeleteBucket on closed tx" - err = bucket.DeleteBucket(bucketName) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure ForEach returns expected error. - testName = "ForEach on closed tx" - err = bucket.ForEach(nil) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure ForEachBucket returns expected error. - testName = "ForEachBucket on closed tx" - err = bucket.ForEachBucket(nil) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure Get returns expected error. - testName = "Get on closed tx" - if k := bucket.Get(keyName); k != nil { - tc.t.Errorf("Get: did not return nil on closed tx") - return false - } - - // Ensure Put returns expected error. - testName = "Put on closed tx" - err = bucket.Put(keyName, []byte("test")) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // ------------------- - // Metadata Cursor API - // ------------------- - - // Ensure attempting to get a bucket from a cursor on a closed tx gives - // back nil. - if b := cursor.Bucket(); b != nil { - tc.t.Error("Cursor.Bucket: returned non-nil on closed tx") - return false - } - - // Ensure Cursor.Delete returns expected error. - testName = "Cursor.Delete on closed tx" - err = cursor.Delete() - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure Cursor.First on a closed tx returns false and nil key/value. - if cursor.First() { - tc.t.Error("Cursor.First: claims ok on closed tx") - return false - } - if cursor.Key() != nil || cursor.Value() != nil { - tc.t.Error("Cursor.First: key and/or value are not nil on " + - "closed tx") - return false - } - - // Ensure Cursor.Last on a closed tx returns false and nil key/value. - if cursor.Last() { - tc.t.Error("Cursor.Last: claims ok on closed tx") - return false - } - if cursor.Key() != nil || cursor.Value() != nil { - tc.t.Error("Cursor.Last: key and/or value are not nil on " + - "closed tx") - return false - } - - // Ensure Cursor.Next on a closed tx returns false and nil key/value. - if cursor.Next() { - tc.t.Error("Cursor.Next: claims ok on closed tx") - return false - } - if cursor.Key() != nil || cursor.Value() != nil { - tc.t.Error("Cursor.Next: key and/or value are not nil on " + - "closed tx") - return false - } - - // Ensure Cursor.Prev on a closed tx returns false and nil key/value. - if cursor.Prev() { - tc.t.Error("Cursor.Prev: claims ok on closed tx") - return false - } - if cursor.Key() != nil || cursor.Value() != nil { - tc.t.Error("Cursor.Prev: key and/or value are not nil on " + - "closed tx") - return false - } - - // Ensure Cursor.Seek on a closed tx returns false and nil key/value. - if cursor.Seek([]byte{}) { - tc.t.Error("Cursor.Seek: claims ok on closed tx") - return false - } - if cursor.Key() != nil || cursor.Value() != nil { - tc.t.Error("Cursor.Seek: key and/or value are not nil on " + - "closed tx") - return false - } - - // --------------------- - // Non-bulk Block IO API - // --------------------- - - // Test the individual block APIs one block at a time to ensure they - // return the expected error. Also, build the data needed to test the - // bulk APIs below while looping. - allBlockHashes := make([]chainhash.Hash, len(tc.blocks)) - allBlockRegions := make([]database.BlockRegion, len(tc.blocks)) - for i, block := range tc.blocks { - blockHash := block.Hash() - allBlockHashes[i] = *blockHash - - txLocs, err := block.TxLoc() - if err != nil { - tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i, - err) - return false - } - - // Ensure StoreBlock returns expected error. - testName = "StoreBlock on closed tx" - err = tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure FetchBlock returns expected error. - testName = fmt.Sprintf("FetchBlock #%d on closed tx", i) - _, err = tx.FetchBlock(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure FetchBlockHeader returns expected error. - testName = fmt.Sprintf("FetchBlockHeader #%d on closed tx", i) - _, err = tx.FetchBlockHeader(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure the first transaction fetched as a block region from - // the database returns the expected error. - region := database.BlockRegion{ - Hash: blockHash, - Offset: uint32(txLocs[0].TxStart), - Len: uint32(txLocs[0].TxLen), - } - allBlockRegions[i] = region - _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure HasBlock returns expected error. - testName = fmt.Sprintf("HasBlock #%d on closed tx", i) - _, err = tx.HasBlock(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - } - - // ----------------- - // Bulk Block IO API - // ----------------- - - // Ensure FetchBlocks returns expected error. - testName = "FetchBlocks on closed tx" - _, err = tx.FetchBlocks(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure FetchBlockHeaders returns expected error. - testName = "FetchBlockHeaders on closed tx" - _, err = tx.FetchBlockHeaders(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure FetchBlockRegions returns expected error. - testName = "FetchBlockRegions on closed tx" - _, err = tx.FetchBlockRegions(allBlockRegions) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // Ensure HasBlocks returns expected error. - testName = "HasBlocks on closed tx" - _, err = tx.HasBlocks(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { - return false - } - - // --------------- - // Commit/Rollback - // --------------- - - // Ensure that attempting to rollback or commit a transaction that is - // already closed returns the expected error. - err = tx.Rollback() - if !checkDbError(tc.t, "closed tx rollback", err, wantErrCode) { - return false - } - err = tx.Commit() - return checkDbError(tc.t, "closed tx commit", err, wantErrCode) -} - -// testTxClosed ensures that both the metadata and block IO API functions behave -// as expected when attempted against both read-only and read-write -// transactions. -func testTxClosed(tc *testContext) bool { - bucketName := []byte("closedtxbucket") - keyName := []byte("closedtxkey") - - // Start a transaction, create a bucket and key used for testing, and - // immediately perform a commit on it so it is closed. - tx, err := tc.db.Begin(true) - if err != nil { - tc.t.Errorf("Begin(true): unexpected error: %v", err) - return false - } - defer rollbackOnPanic(tc.t, tx) - if _, err := tx.Metadata().CreateBucket(bucketName); err != nil { - tc.t.Errorf("CreateBucket: unexpected error: %v", err) - return false - } - if err := tx.Metadata().Put(keyName, []byte("test")); err != nil { - tc.t.Errorf("Put: unexpected error: %v", err) - return false - } - if err := tx.Commit(); err != nil { - tc.t.Errorf("Commit: unexpected error: %v", err) - return false - } - - // Ensure invoking all of the functions on the closed read-write - // transaction behave as expected. - if !testClosedTxInterface(tc, tx) { - return false - } - - // Repeat the tests with a rolled-back read-only transaction. - tx, err = tc.db.Begin(false) - if err != nil { - tc.t.Errorf("Begin(false): unexpected error: %v", err) - return false - } - defer rollbackOnPanic(tc.t, tx) - if err := tx.Rollback(); err != nil { - tc.t.Errorf("Rollback: unexpected error: %v", err) - return false - } - - // Ensure invoking all of the functions on the closed read-only - // transaction behave as expected. - return testClosedTxInterface(tc, tx) -} - -// testConcurrecy ensure the database properly supports concurrent readers and -// only a single writer. It also ensures views act as snapshots at the time -// they are acquired. -func testConcurrecy(tc *testContext) bool { - // sleepTime is how long each of the concurrent readers should sleep to - // aid in detection of whether or not the data is actually being read - // concurrently. It starts with a sane lower bound. - var sleepTime = time.Millisecond * 250 - - // Determine about how long it takes for a single block read. When it's - // longer than the default minimum sleep time, adjust the sleep time to - // help prevent durations that are too short which would cause erroneous - // test failures on slower systems. - startTime := time.Now() - err := tc.db.View(func(tx database.Tx) error { - _, err := tx.FetchBlock(tc.blocks[0].Hash()) - return err - }) - if err != nil { - tc.t.Errorf("Unexpected error in view: %v", err) - return false - } - elapsed := time.Since(startTime) - if sleepTime < elapsed { - sleepTime = elapsed - } - tc.t.Logf("Time to load block 0: %v, using sleep time: %v", elapsed, - sleepTime) - - // reader takes a block number to load and channel to return the result - // of the operation on. It is used below to launch multiple concurrent - // readers. - numReaders := len(tc.blocks) - resultChan := make(chan bool, numReaders) - reader := func(blockNum int) { - err := tc.db.View(func(tx database.Tx) error { - time.Sleep(sleepTime) - _, err := tx.FetchBlock(tc.blocks[blockNum].Hash()) - return err - }) - if err != nil { - tc.t.Errorf("Unexpected error in concurrent view: %v", - err) - resultChan <- false - } - resultChan <- true - } - - // Start up several concurrent readers for the same block and wait for - // the results. - startTime = time.Now() - for i := 0; i < numReaders; i++ { - go reader(0) - } - for i := 0; i < numReaders; i++ { - if result := <-resultChan; !result { - return false - } - } - elapsed = time.Since(startTime) - tc.t.Logf("%d concurrent reads of same block elapsed: %v", numReaders, - elapsed) - - // Consider it a failure if it took longer than half the time it would - // take with no concurrency. - if elapsed > sleepTime*time.Duration(numReaders/2) { - tc.t.Errorf("Concurrent views for same block did not appear to "+ - "run simultaneously: elapsed %v", elapsed) - return false - } - - // Start up several concurrent readers for different blocks and wait for - // the results. - startTime = time.Now() - for i := 0; i < numReaders; i++ { - go reader(i) - } - for i := 0; i < numReaders; i++ { - if result := <-resultChan; !result { - return false - } - } - elapsed = time.Since(startTime) - tc.t.Logf("%d concurrent reads of different blocks elapsed: %v", - numReaders, elapsed) - - // Consider it a failure if it took longer than half the time it would - // take with no concurrency. - if elapsed > sleepTime*time.Duration(numReaders/2) { - tc.t.Errorf("Concurrent views for different blocks did not "+ - "appear to run simultaneously: elapsed %v", elapsed) - return false - } - - // Start up a few readers and wait for them to acquire views. Each - // reader waits for a signal from the writer to be finished to ensure - // that the data written by the writer is not seen by the view since it - // was started before the data was set. - concurrentKey := []byte("notthere") - concurrentVal := []byte("someval") - started := make(chan struct{}) - writeComplete := make(chan struct{}) - reader = func(blockNum int) { - err := tc.db.View(func(tx database.Tx) error { - started <- struct{}{} - - // Wait for the writer to complete. - <-writeComplete - - // Since this reader was created before the write took - // place, the data it added should not be visible. - val := tx.Metadata().Get(concurrentKey) - if val != nil { - return fmt.Errorf("%s should not be visible", - concurrentKey) - } - return nil - }) - if err != nil { - tc.t.Errorf("Unexpected error in concurrent view: %v", - err) - resultChan <- false - } - resultChan <- true - } - for i := 0; i < numReaders; i++ { - go reader(0) - } - for i := 0; i < numReaders; i++ { - <-started - } - - // All readers are started and waiting for completion of the writer. - // Set some data the readers are expecting to not find and signal the - // readers the write is done by closing the writeComplete channel. - err = tc.db.Update(func(tx database.Tx) error { - return tx.Metadata().Put(concurrentKey, concurrentVal) - }) - if err != nil { - tc.t.Errorf("Unexpected error in update: %v", err) - return false - } - close(writeComplete) - - // Wait for reader results. - for i := 0; i < numReaders; i++ { - if result := <-resultChan; !result { - return false - } - } - - // Start a few writers and ensure the total time is at least the - // writeSleepTime * numWriters. This ensures only one write transaction - // can be active at a time. - writeSleepTime := time.Millisecond * 250 - writer := func() { - err := tc.db.Update(func(tx database.Tx) error { - time.Sleep(writeSleepTime) - return nil - }) - if err != nil { - tc.t.Errorf("Unexpected error in concurrent view: %v", - err) - resultChan <- false - } - resultChan <- true - } - numWriters := 3 - startTime = time.Now() - for i := 0; i < numWriters; i++ { - go writer() - } - for i := 0; i < numWriters; i++ { - if result := <-resultChan; !result { - return false - } - } - elapsed = time.Since(startTime) - tc.t.Logf("%d concurrent writers elapsed using sleep time %v: %v", - numWriters, writeSleepTime, elapsed) - - // The total time must have been at least the sum of all sleeps if the - // writes blocked properly. - if elapsed < writeSleepTime*time.Duration(numWriters) { - tc.t.Errorf("Concurrent writes appeared to run simultaneously: "+ - "elapsed %v", elapsed) - return false - } - - return true -} - -// testConcurrentClose ensures that closing the database with open transactions -// blocks until the transactions are finished. -// -// The database will be closed upon returning from this function. -func testConcurrentClose(tc *testContext) bool { - // Start up a few readers and wait for them to acquire views. Each - // reader waits for a signal to complete to ensure the transactions stay - // open until they are explicitly signalled to be closed. - var activeReaders int32 - numReaders := 3 - started := make(chan struct{}) - finishReaders := make(chan struct{}) - resultChan := make(chan bool, numReaders+1) - reader := func() { - err := tc.db.View(func(tx database.Tx) error { - atomic.AddInt32(&activeReaders, 1) - started <- struct{}{} - <-finishReaders - atomic.AddInt32(&activeReaders, -1) - return nil - }) - if err != nil { - tc.t.Errorf("Unexpected error in concurrent view: %v", - err) - resultChan <- false - } - resultChan <- true - } - for i := 0; i < numReaders; i++ { - go reader() - } - for i := 0; i < numReaders; i++ { - <-started - } - - // Close the database in a separate goroutine. This should block until - // the transactions are finished. Once the close has taken place, the - // dbClosed channel is closed to signal the main goroutine below. - dbClosed := make(chan struct{}) - go func() { - started <- struct{}{} - err := tc.db.Close() - if err != nil { - tc.t.Errorf("Unexpected error in concurrent view: %v", - err) - resultChan <- false - } - close(dbClosed) - resultChan <- true - }() - <-started - - // Wait a short period and then signal the reader transactions to - // finish. When the db closed channel is received, ensure there are no - // active readers open. - time.AfterFunc(time.Millisecond*250, func() { close(finishReaders) }) - <-dbClosed - if nr := atomic.LoadInt32(&activeReaders); nr != 0 { - tc.t.Errorf("Close did not appear to block with active "+ - "readers: %d active", nr) - return false - } - - // Wait for all results. - for i := 0; i < numReaders+1; i++ { - if result := <-resultChan; !result { - return false - } - } - - return true -} - -// testInterface tests performs tests for the various interfaces of the database -// package which require state in the database for the given database type. -func testInterface(t *testing.T, db database.DB) { - // Create a test context to pass around. - context := testContext{t: t, db: db} - - // Load the test blocks and store in the test context for use throughout - // the tests. - blocks, err := loadBlocks(t, blockDataFile, blockDataNet) - if err != nil { - t.Errorf("loadBlocks: Unexpected error: %v", err) - return - } - context.blocks = blocks - - // Test the transaction metadata interface including managed and manual - // transactions as well as buckets. - if !testMetadataTxInterface(&context) { - return - } - - // Test the transaction block IO interface using managed and manual - // transactions. This function leaves all of the stored blocks in the - // database since they're used later. - if !testBlockIOTxInterface(&context) { - return - } - - // Test all of the transaction interface functions against a closed - // transaction work as expected. - if !testTxClosed(&context) { - return - } - - // Test the database properly supports concurrency. - if !testConcurrecy(&context) { - return - } - - // Test that closing the database with open transactions blocks until - // the transactions are finished. - // - // The database will be closed upon returning from this function, so it - // must be the last thing called. - testConcurrentClose(&context) -}