1 // Copyright (c) 2015-2016 The btcsuite developers
2 // Use of this source code is governed by an ISC
3 // license that can be found in the LICENSE file.
5 // This file intended to be copied into each backend driver directory. Each
6 // driver should have their own driver_test.go file which creates a database and
7 // invokes the testInterface function in this file to ensure the driver properly
8 // implements the interface.
10 // NOTE: When copying this file into the backend driver folder, the package name
11 // will need to be changed accordingly.
28 "github.com/btcsuite/btcd/chaincfg"
29 "github.com/btcsuite/btcd/chaincfg/chainhash"
30 "github.com/btcsuite/btcd/database"
31 "github.com/btcsuite/btcd/wire"
32 "github.com/btcsuite/btcutil"
36 // blockDataNet is the expected network in the test block data.
37 blockDataNet = wire.MainNet
39 // blockDataFile is the path to a file containing the first 256 blocks
40 // of the block chain.
41 blockDataFile = filepath.Join("..", "testdata", "blocks1-256.bz2")
43 // errSubTestFail is used to signal that a sub test returned false.
44 errSubTestFail = fmt.Errorf("sub test failure")
47 // loadBlocks loads the blocks contained in the testdata directory and returns
49 func loadBlocks(t *testing.T, dataFile string, network wire.BitcoinNet) ([]*btcutil.Block, error) {
50 // Open the file that contains the blocks for reading.
51 fi, err := os.Open(dataFile)
53 t.Errorf("failed to open file %v, err %v", dataFile, err)
57 if err := fi.Close(); err != nil {
58 t.Errorf("failed to close file %v %v", dataFile,
62 dr := bzip2.NewReader(fi)
64 // Set the first block as the genesis block.
65 blocks := make([]*btcutil.Block, 0, 256)
66 genesis := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
67 blocks = append(blocks, genesis)
69 // Load the remaining blocks.
70 for height := 1; ; height++ {
72 err := binary.Read(dr, binary.LittleEndian, &net)
74 // Hit end of file at the expected offset. No error.
78 t.Errorf("Failed to load network type for block %d: %v",
82 if net != uint32(network) {
83 t.Errorf("Block doesn't match network: %v expects %v",
89 err = binary.Read(dr, binary.LittleEndian, &blockLen)
91 t.Errorf("Failed to load block size for block %d: %v",
97 blockBytes := make([]byte, blockLen)
98 _, err = io.ReadFull(dr, blockBytes)
100 t.Errorf("Failed to load block %d: %v", height, err)
104 // Deserialize and store the block.
105 block, err := btcutil.NewBlockFromBytes(blockBytes)
107 t.Errorf("Failed to parse block %v: %v", height, err)
110 blocks = append(blocks, block)
116 // checkDbError ensures the passed error is a database.Error with an error code
117 // that matches the passed error code.
118 func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool {
119 dbErr, ok := gotErr.(database.Error)
121 t.Errorf("%s: unexpected error type - got %T, want %T",
122 testName, gotErr, database.Error{})
125 if dbErr.ErrorCode != wantErrCode {
126 t.Errorf("%s: unexpected error code - got %s (%s), want %s",
127 testName, dbErr.ErrorCode, dbErr.Description,
135 // testContext is used to store context information about a running test which
136 // is passed into helper functions.
137 type testContext struct {
142 blocks []*btcutil.Block
145 // keyPair houses a key/value pair. It is used over maps so ordering can be
147 type keyPair struct {
152 // lookupKey is a convenience method to lookup the requested key from the
153 // provided keypair slice along with whether or not the key was found.
154 func lookupKey(key []byte, values []keyPair) ([]byte, bool) {
155 for _, item := range values {
156 if bytes.Equal(item.key, key) {
157 return item.value, true
164 // toGetValues returns a copy of the provided keypairs with all of the nil
165 // values set to an empty byte slice. This is used to ensure that keys set to
166 // nil values result in empty byte slices when retrieved instead of nil.
167 func toGetValues(values []keyPair) []keyPair {
168 ret := make([]keyPair, len(values))
171 if ret[i].value == nil {
172 ret[i].value = make([]byte, 0)
178 // rollbackValues returns a copy of the provided keypairs with all values set to
179 // nil. This is used to test that values are properly rolled back.
180 func rollbackValues(values []keyPair) []keyPair {
181 ret := make([]keyPair, len(values))
189 // testCursorKeyPair checks that the provide key and value match the expected
190 // keypair at the provided index. It also ensures the index is in range for the
191 // provided slice of expected keypairs.
192 func testCursorKeyPair(tc *testContext, k, v []byte, index int, values []keyPair) bool {
193 if index >= len(values) || index < 0 {
194 tc.t.Errorf("Cursor: exceeded the expected range of values - "+
195 "index %d, num values %d", index, len(values))
199 pair := &values[index]
200 if !bytes.Equal(k, pair.key) {
201 tc.t.Errorf("Mismatched cursor key: index %d does not match "+
202 "the expected key - got %q, want %q", index, k,
206 if !bytes.Equal(v, pair.value) {
207 tc.t.Errorf("Mismatched cursor value: index %d does not match "+
208 "the expected value - got %q, want %q", index, v,
216 // testGetValues checks that all of the provided key/value pairs can be
217 // retrieved from the database and the retrieved values match the provided
219 func testGetValues(tc *testContext, bucket database.Bucket, values []keyPair) bool {
220 for _, item := range values {
221 gotValue := bucket.Get(item.key)
222 if !reflect.DeepEqual(gotValue, item.value) {
223 tc.t.Errorf("Get: unexpected value for %q - got %q, "+
224 "want %q", item.key, gotValue, item.value)
232 // testPutValues stores all of the provided key/value pairs in the provided
233 // bucket while checking for errors.
234 func testPutValues(tc *testContext, bucket database.Bucket, values []keyPair) bool {
235 for _, item := range values {
236 if err := bucket.Put(item.key, item.value); err != nil {
237 tc.t.Errorf("Put: unexpected error: %v", err)
245 // testDeleteValues removes all of the provided key/value pairs from the
247 func testDeleteValues(tc *testContext, bucket database.Bucket, values []keyPair) bool {
248 for _, item := range values {
249 if err := bucket.Delete(item.key); err != nil {
250 tc.t.Errorf("Delete: unexpected error: %v", err)
258 // testCursorInterface ensures the cursor itnerface is working properly by
259 // exercising all of its functions on the passed bucket.
260 func testCursorInterface(tc *testContext, bucket database.Bucket) bool {
261 // Ensure a cursor can be obtained for the bucket.
262 cursor := bucket.Cursor()
264 tc.t.Error("Bucket.Cursor: unexpected nil cursor returned")
268 // Ensure the cursor returns the same bucket it was created for.
269 if cursor.Bucket() != bucket {
270 tc.t.Error("Cursor.Bucket: does not match the bucket it was " +
276 unsortedValues := []keyPair{
277 {[]byte("cursor"), []byte("val1")},
278 {[]byte("abcd"), []byte("val2")},
279 {[]byte("bcd"), []byte("val3")},
280 {[]byte("defg"), nil},
282 sortedValues := []keyPair{
283 {[]byte("abcd"), []byte("val2")},
284 {[]byte("bcd"), []byte("val3")},
285 {[]byte("cursor"), []byte("val1")},
286 {[]byte("defg"), nil},
289 // Store the values to be used in the cursor tests in unsorted
290 // order and ensure they were actually stored.
291 if !testPutValues(tc, bucket, unsortedValues) {
294 if !testGetValues(tc, bucket, toGetValues(unsortedValues)) {
298 // Ensure the cursor returns all items in byte-sorted order when
299 // iterating forward.
301 for ok := cursor.First(); ok; ok = cursor.Next() {
302 k, v := cursor.Key(), cursor.Value()
303 if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) {
308 if curIdx != len(unsortedValues) {
309 tc.t.Errorf("Cursor: expected to iterate %d values, "+
310 "but only iterated %d", len(unsortedValues),
315 // Ensure the cursor returns all items in reverse byte-sorted
316 // order when iterating in reverse.
317 curIdx = len(sortedValues) - 1
318 for ok := cursor.Last(); ok; ok = cursor.Prev() {
319 k, v := cursor.Key(), cursor.Value()
320 if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) {
326 tc.t.Errorf("Reverse cursor: expected to iterate %d "+
327 "values, but only iterated %d",
328 len(sortedValues), len(sortedValues)-(curIdx+1))
332 // Ensure forward iteration works as expected after seeking.
333 middleIdx := (len(sortedValues) - 1) / 2
334 seekKey := sortedValues[middleIdx].key
336 for ok := cursor.Seek(seekKey); ok; ok = cursor.Next() {
337 k, v := cursor.Key(), cursor.Value()
338 if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) {
343 if curIdx != len(sortedValues) {
344 tc.t.Errorf("Cursor after seek: expected to iterate "+
345 "%d values, but only iterated %d",
346 len(sortedValues)-middleIdx, curIdx-middleIdx)
350 // Ensure reverse iteration works as expected after seeking.
352 for ok := cursor.Seek(seekKey); ok; ok = cursor.Prev() {
353 k, v := cursor.Key(), cursor.Value()
354 if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) {
360 tc.t.Errorf("Reverse cursor after seek: expected to "+
361 "iterate %d values, but only iterated %d",
362 len(sortedValues)-middleIdx, middleIdx-curIdx)
366 // Ensure the cursor deletes items properly.
368 tc.t.Errorf("Cursor.First: no value")
372 if err := cursor.Delete(); err != nil {
373 tc.t.Errorf("Cursor.Delete: unexpected error: %v", err)
376 if val := bucket.Get(k); val != nil {
377 tc.t.Errorf("Cursor.Delete: value for key %q was not "+
386 // testNestedBucket reruns the testBucketInterface against a nested bucket along
387 // with a counter to only test a couple of level deep.
388 func testNestedBucket(tc *testContext, testBucket database.Bucket) bool {
389 // Don't go more than 2 nested levels deep.
390 if tc.bucketDepth > 1 {
398 return testBucketInterface(tc, testBucket)
401 // testBucketInterface ensures the bucket interface is working properly by
402 // exercising all of its functions. This includes the cursor interface for the
403 // cursor returned from the bucket.
404 func testBucketInterface(tc *testContext, bucket database.Bucket) bool {
405 if bucket.Writable() != tc.isWritable {
406 tc.t.Errorf("Bucket writable state does not match.")
411 // keyValues holds the keys and values to use when putting
412 // values into the bucket.
413 keyValues := []keyPair{
414 {[]byte("bucketkey1"), []byte("foo1")},
415 {[]byte("bucketkey2"), []byte("foo2")},
416 {[]byte("bucketkey3"), []byte("foo3")},
417 {[]byte("bucketkey4"), nil},
419 expectedKeyValues := toGetValues(keyValues)
420 if !testPutValues(tc, bucket, keyValues) {
424 if !testGetValues(tc, bucket, expectedKeyValues) {
428 // Ensure errors returned from the user-supplied ForEach
429 // function are returned.
430 forEachError := fmt.Errorf("example foreach error")
431 err := bucket.ForEach(func(k, v []byte) error {
434 if err != forEachError {
435 tc.t.Errorf("ForEach: inner function error not "+
436 "returned - got %v, want %v", err, forEachError)
440 // Iterate all of the keys using ForEach while making sure the
441 // stored values are the expected values.
442 keysFound := make(map[string]struct{}, len(keyValues))
443 err = bucket.ForEach(func(k, v []byte) error {
444 wantV, found := lookupKey(k, expectedKeyValues)
446 return fmt.Errorf("ForEach: key '%s' should "+
450 if !reflect.DeepEqual(v, wantV) {
451 return fmt.Errorf("ForEach: value for key '%s' "+
452 "does not match - got %s, want %s", k,
456 keysFound[string(k)] = struct{}{}
460 tc.t.Errorf("%v", err)
464 // Ensure all keys were iterated.
465 for _, item := range keyValues {
466 if _, ok := keysFound[string(item.key)]; !ok {
467 tc.t.Errorf("ForEach: key '%s' was not iterated "+
468 "when it should have been", item.key)
473 // Delete the keys and ensure they were deleted.
474 if !testDeleteValues(tc, bucket, keyValues) {
477 if !testGetValues(tc, bucket, rollbackValues(keyValues)) {
481 // Ensure creating a new bucket works as expected.
482 testBucketName := []byte("testbucket")
483 testBucket, err := bucket.CreateBucket(testBucketName)
485 tc.t.Errorf("CreateBucket: unexpected error: %v", err)
488 if !testNestedBucket(tc, testBucket) {
492 // Ensure errors returned from the user-supplied ForEachBucket
493 // function are returned.
494 err = bucket.ForEachBucket(func(k []byte) error {
497 if err != forEachError {
498 tc.t.Errorf("ForEachBucket: inner function error not "+
499 "returned - got %v, want %v", err, forEachError)
503 // Ensure creating a bucket that already exists fails with the
505 wantErrCode := database.ErrBucketExists
506 _, err = bucket.CreateBucket(testBucketName)
507 if !checkDbError(tc.t, "CreateBucket", err, wantErrCode) {
511 // Ensure CreateBucketIfNotExists returns an existing bucket.
512 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName)
514 tc.t.Errorf("CreateBucketIfNotExists: unexpected "+
518 if !testNestedBucket(tc, testBucket) {
522 // Ensure retrieving an existing bucket works as expected.
523 testBucket = bucket.Bucket(testBucketName)
524 if !testNestedBucket(tc, testBucket) {
528 // Ensure deleting a bucket works as intended.
529 if err := bucket.DeleteBucket(testBucketName); err != nil {
530 tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
533 if b := bucket.Bucket(testBucketName); b != nil {
534 tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
539 // Ensure deleting a bucket that doesn't exist returns the
541 wantErrCode = database.ErrBucketNotFound
542 err = bucket.DeleteBucket(testBucketName)
543 if !checkDbError(tc.t, "DeleteBucket", err, wantErrCode) {
547 // Ensure CreateBucketIfNotExists creates a new bucket when
548 // it doesn't already exist.
549 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName)
551 tc.t.Errorf("CreateBucketIfNotExists: unexpected "+
555 if !testNestedBucket(tc, testBucket) {
559 // Ensure the cursor interface works as expected.
560 if !testCursorInterface(tc, testBucket) {
564 // Delete the test bucket to avoid leaving it around for future
566 if err := bucket.DeleteBucket(testBucketName); err != nil {
567 tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
570 if b := bucket.Bucket(testBucketName); b != nil {
571 tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
576 // Put should fail with bucket that is not writable.
577 testName := "unwritable tx put"
578 wantErrCode := database.ErrTxNotWritable
579 failBytes := []byte("fail")
580 err := bucket.Put(failBytes, failBytes)
581 if !checkDbError(tc.t, testName, err, wantErrCode) {
585 // Delete should fail with bucket that is not writable.
586 testName = "unwritable tx delete"
587 err = bucket.Delete(failBytes)
588 if !checkDbError(tc.t, testName, err, wantErrCode) {
592 // CreateBucket should fail with bucket that is not writable.
593 testName = "unwritable tx create bucket"
594 _, err = bucket.CreateBucket(failBytes)
595 if !checkDbError(tc.t, testName, err, wantErrCode) {
599 // CreateBucketIfNotExists should fail with bucket that is not
601 testName = "unwritable tx create bucket if not exists"
602 _, err = bucket.CreateBucketIfNotExists(failBytes)
603 if !checkDbError(tc.t, testName, err, wantErrCode) {
607 // DeleteBucket should fail with bucket that is not writable.
608 testName = "unwritable tx delete bucket"
609 err = bucket.DeleteBucket(failBytes)
610 if !checkDbError(tc.t, testName, err, wantErrCode) {
614 // Ensure the cursor interface works as expected with read-only
616 if !testCursorInterface(tc, bucket) {
624 // rollbackOnPanic rolls the passed transaction back if the code in the calling
625 // function panics. This is useful in case the tests unexpectedly panic which
626 // would leave any manually created transactions with the database mutex locked
627 // thereby leading to a deadlock and masking the real reason for the panic. It
628 // also logs a test error and repanics so the original panic can be traced.
629 func rollbackOnPanic(t *testing.T, tx database.Tx) {
630 if err := recover(); err != nil {
631 t.Errorf("Unexpected panic: %v", err)
637 // testMetadataManualTxInterface ensures that the manual transactions metadata
638 // interface works as expected.
639 func testMetadataManualTxInterface(tc *testContext) bool {
640 // populateValues tests that populating values works as expected.
642 // When the writable flag is false, a read-only tranasction is created,
643 // standard bucket tests for read-only transactions are performed, and
644 // the Commit function is checked to ensure it fails as expected.
646 // Otherwise, a read-write transaction is created, the values are
647 // written, standard bucket tests for read-write transactions are
648 // performed, and then the transaction is either committed or rolled
649 // back depending on the flag.
650 bucket1Name := []byte("bucket1")
651 populateValues := func(writable, rollback bool, putValues []keyPair) bool {
652 tx, err := tc.db.Begin(writable)
654 tc.t.Errorf("Begin: unexpected error %v", err)
657 defer rollbackOnPanic(tc.t, tx)
659 metadataBucket := tx.Metadata()
660 if metadataBucket == nil {
661 tc.t.Errorf("Metadata: unexpected nil bucket")
666 bucket1 := metadataBucket.Bucket(bucket1Name)
668 tc.t.Errorf("Bucket1: unexpected nil bucket")
672 tc.isWritable = writable
673 if !testBucketInterface(tc, bucket1) {
679 // The transaction is not writable, so it should fail
681 testName := "unwritable tx commit"
682 wantErrCode := database.ErrTxNotWritable
684 if !checkDbError(tc.t, testName, err, wantErrCode) {
689 if !testPutValues(tc, bucket1, putValues) {
694 // Rollback the transaction.
695 if err := tx.Rollback(); err != nil {
696 tc.t.Errorf("Rollback: unexpected "+
701 // The commit should succeed.
702 if err := tx.Commit(); err != nil {
703 tc.t.Errorf("Commit: unexpected error "+
713 // checkValues starts a read-only transaction and checks that all of
714 // the key/value pairs specified in the expectedValues parameter match
715 // what's in the database.
716 checkValues := func(expectedValues []keyPair) bool {
717 tx, err := tc.db.Begin(false)
719 tc.t.Errorf("Begin: unexpected error %v", err)
722 defer rollbackOnPanic(tc.t, tx)
724 metadataBucket := tx.Metadata()
725 if metadataBucket == nil {
726 tc.t.Errorf("Metadata: unexpected nil bucket")
731 bucket1 := metadataBucket.Bucket(bucket1Name)
733 tc.t.Errorf("Bucket1: unexpected nil bucket")
737 if !testGetValues(tc, bucket1, expectedValues) {
742 // Rollback the read-only transaction.
743 if err := tx.Rollback(); err != nil {
744 tc.t.Errorf("Commit: unexpected error %v", err)
751 // deleteValues starts a read-write transaction and deletes the keys
752 // in the passed key/value pairs.
753 deleteValues := func(values []keyPair) bool {
754 tx, err := tc.db.Begin(true)
758 defer rollbackOnPanic(tc.t, tx)
760 metadataBucket := tx.Metadata()
761 if metadataBucket == nil {
762 tc.t.Errorf("Metadata: unexpected nil bucket")
767 bucket1 := metadataBucket.Bucket(bucket1Name)
769 tc.t.Errorf("Bucket1: unexpected nil bucket")
773 // Delete the keys and ensure they were deleted.
774 if !testDeleteValues(tc, bucket1, values) {
778 if !testGetValues(tc, bucket1, rollbackValues(values)) {
783 // Commit the changes and ensure it was successful.
784 if err := tx.Commit(); err != nil {
785 tc.t.Errorf("Commit: unexpected error %v", err)
792 // keyValues holds the keys and values to use when putting values into a
794 var keyValues = []keyPair{
795 {[]byte("umtxkey1"), []byte("foo1")},
796 {[]byte("umtxkey2"), []byte("foo2")},
797 {[]byte("umtxkey3"), []byte("foo3")},
798 {[]byte("umtxkey4"), nil},
801 // Ensure that attempting populating the values using a read-only
802 // transaction fails as expected.
803 if !populateValues(false, true, keyValues) {
806 if !checkValues(rollbackValues(keyValues)) {
810 // Ensure that attempting populating the values using a read-write
811 // transaction and then rolling it back yields the expected values.
812 if !populateValues(true, true, keyValues) {
815 if !checkValues(rollbackValues(keyValues)) {
819 // Ensure that attempting populating the values using a read-write
820 // transaction and then committing it stores the expected values.
821 if !populateValues(true, false, keyValues) {
824 if !checkValues(toGetValues(keyValues)) {
828 // Clean up the keys.
829 if !deleteValues(keyValues) {
836 // testManagedTxPanics ensures calling Rollback of Commit inside a managed
837 // transaction panics.
838 func testManagedTxPanics(tc *testContext) bool {
839 testPanic := func(fn func()) (paniced bool) {
840 // Setup a defer to catch the expected panic and update the
843 if err := recover(); err != nil {
852 // Ensure calling Commit on a managed read-only transaction panics.
853 paniced := testPanic(func() {
854 tc.db.View(func(tx database.Tx) error {
860 tc.t.Error("Commit called inside View did not panic")
864 // Ensure calling Rollback on a managed read-only transaction panics.
865 paniced = testPanic(func() {
866 tc.db.View(func(tx database.Tx) error {
872 tc.t.Error("Rollback called inside View did not panic")
876 // Ensure calling Commit on a managed read-write transaction panics.
877 paniced = testPanic(func() {
878 tc.db.Update(func(tx database.Tx) error {
884 tc.t.Error("Commit called inside Update did not panic")
888 // Ensure calling Rollback on a managed read-write transaction panics.
889 paniced = testPanic(func() {
890 tc.db.Update(func(tx database.Tx) error {
896 tc.t.Error("Rollback called inside Update did not panic")
903 // testMetadataTxInterface tests all facets of the managed read/write and
904 // manual transaction metadata interfaces as well as the bucket interfaces under
906 func testMetadataTxInterface(tc *testContext) bool {
907 if !testManagedTxPanics(tc) {
911 bucket1Name := []byte("bucket1")
912 err := tc.db.Update(func(tx database.Tx) error {
913 _, err := tx.Metadata().CreateBucket(bucket1Name)
917 tc.t.Errorf("Update: unexpected error creating bucket: %v", err)
921 if !testMetadataManualTxInterface(tc) {
925 // keyValues holds the keys and values to use when putting values
927 keyValues := []keyPair{
928 {[]byte("mtxkey1"), []byte("foo1")},
929 {[]byte("mtxkey2"), []byte("foo2")},
930 {[]byte("mtxkey3"), []byte("foo3")},
931 {[]byte("mtxkey4"), nil},
934 // Test the bucket interface via a managed read-only transaction.
935 err = tc.db.View(func(tx database.Tx) error {
936 metadataBucket := tx.Metadata()
937 if metadataBucket == nil {
938 return fmt.Errorf("Metadata: unexpected nil bucket")
941 bucket1 := metadataBucket.Bucket(bucket1Name)
943 return fmt.Errorf("Bucket1: unexpected nil bucket")
946 tc.isWritable = false
947 if !testBucketInterface(tc, bucket1) {
948 return errSubTestFail
954 if err != errSubTestFail {
955 tc.t.Errorf("%v", err)
960 // Ensure errors returned from the user-supplied View function are
962 viewError := fmt.Errorf("example view error")
963 err = tc.db.View(func(tx database.Tx) error {
966 if err != viewError {
967 tc.t.Errorf("View: inner function error not returned - got "+
968 "%v, want %v", err, viewError)
972 // Test the bucket interface via a managed read-write transaction.
973 // Also, put a series of values and force a rollback so the following
974 // code can ensure the values were not stored.
975 forceRollbackError := fmt.Errorf("force rollback")
976 err = tc.db.Update(func(tx database.Tx) error {
977 metadataBucket := tx.Metadata()
978 if metadataBucket == nil {
979 return fmt.Errorf("Metadata: unexpected nil bucket")
982 bucket1 := metadataBucket.Bucket(bucket1Name)
984 return fmt.Errorf("Bucket1: unexpected nil bucket")
988 if !testBucketInterface(tc, bucket1) {
989 return errSubTestFail
992 if !testPutValues(tc, bucket1, keyValues) {
993 return errSubTestFail
996 // Return an error to force a rollback.
997 return forceRollbackError
999 if err != forceRollbackError {
1000 if err == errSubTestFail {
1004 tc.t.Errorf("Update: inner function error not returned - got "+
1005 "%v, want %v", err, forceRollbackError)
1009 // Ensure the values that should not have been stored due to the forced
1010 // rollback above were not actually stored.
1011 err = tc.db.View(func(tx database.Tx) error {
1012 metadataBucket := tx.Metadata()
1013 if metadataBucket == nil {
1014 return fmt.Errorf("Metadata: unexpected nil bucket")
1017 if !testGetValues(tc, metadataBucket, rollbackValues(keyValues)) {
1018 return errSubTestFail
1024 if err != errSubTestFail {
1025 tc.t.Errorf("%v", err)
1030 // Store a series of values via a managed read-write transaction.
1031 err = tc.db.Update(func(tx database.Tx) error {
1032 metadataBucket := tx.Metadata()
1033 if metadataBucket == nil {
1034 return fmt.Errorf("Metadata: unexpected nil bucket")
1037 bucket1 := metadataBucket.Bucket(bucket1Name)
1039 return fmt.Errorf("Bucket1: unexpected nil bucket")
1042 if !testPutValues(tc, bucket1, keyValues) {
1043 return errSubTestFail
1049 if err != errSubTestFail {
1050 tc.t.Errorf("%v", err)
1055 // Ensure the values stored above were committed as expected.
1056 err = tc.db.View(func(tx database.Tx) error {
1057 metadataBucket := tx.Metadata()
1058 if metadataBucket == nil {
1059 return fmt.Errorf("Metadata: unexpected nil bucket")
1062 bucket1 := metadataBucket.Bucket(bucket1Name)
1064 return fmt.Errorf("Bucket1: unexpected nil bucket")
1067 if !testGetValues(tc, bucket1, toGetValues(keyValues)) {
1068 return errSubTestFail
1074 if err != errSubTestFail {
1075 tc.t.Errorf("%v", err)
1080 // Clean up the values stored above in a managed read-write transaction.
1081 err = tc.db.Update(func(tx database.Tx) error {
1082 metadataBucket := tx.Metadata()
1083 if metadataBucket == nil {
1084 return fmt.Errorf("Metadata: unexpected nil bucket")
1087 bucket1 := metadataBucket.Bucket(bucket1Name)
1089 return fmt.Errorf("Bucket1: unexpected nil bucket")
1092 if !testDeleteValues(tc, bucket1, keyValues) {
1093 return errSubTestFail
1099 if err != errSubTestFail {
1100 tc.t.Errorf("%v", err)
1108 // testFetchBlockIOMissing ensures that all of the block retrieval API functions
1109 // work as expected when requesting blocks that don't exist.
1110 func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool {
1111 wantErrCode := database.ErrBlockNotFound
1113 // ---------------------
1114 // Non-bulk Block IO API
1115 // ---------------------
1117 // Test the individual block APIs one block at a time to ensure they
1118 // return the expected error. Also, build the data needed to test the
1119 // bulk APIs below while looping.
1120 allBlockHashes := make([]chainhash.Hash, len(tc.blocks))
1121 allBlockRegions := make([]database.BlockRegion, len(tc.blocks))
1122 for i, block := range tc.blocks {
1123 blockHash := block.Hash()
1124 allBlockHashes[i] = *blockHash
1126 txLocs, err := block.TxLoc()
1128 tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i,
1133 // Ensure FetchBlock returns expected error.
1134 testName := fmt.Sprintf("FetchBlock #%d on missing block", i)
1135 _, err = tx.FetchBlock(blockHash)
1136 if !checkDbError(tc.t, testName, err, wantErrCode) {
1140 // Ensure FetchBlockHeader returns expected error.
1141 testName = fmt.Sprintf("FetchBlockHeader #%d on missing block",
1143 _, err = tx.FetchBlockHeader(blockHash)
1144 if !checkDbError(tc.t, testName, err, wantErrCode) {
1148 // Ensure the first transaction fetched as a block region from
1149 // the database returns the expected error.
1150 region := database.BlockRegion{
1152 Offset: uint32(txLocs[0].TxStart),
1153 Len: uint32(txLocs[0].TxLen),
1155 allBlockRegions[i] = region
1156 _, err = tx.FetchBlockRegion(®ion)
1157 if !checkDbError(tc.t, testName, err, wantErrCode) {
1161 // Ensure HasBlock returns false.
1162 hasBlock, err := tx.HasBlock(blockHash)
1164 tc.t.Errorf("HasBlock #%d: unexpected err: %v", i, err)
1168 tc.t.Errorf("HasBlock #%d: should not have block", i)
1173 // -----------------
1174 // Bulk Block IO API
1175 // -----------------
1177 // Ensure FetchBlocks returns expected error.
1178 testName := "FetchBlocks on missing blocks"
1179 _, err := tx.FetchBlocks(allBlockHashes)
1180 if !checkDbError(tc.t, testName, err, wantErrCode) {
1184 // Ensure FetchBlockHeaders returns expected error.
1185 testName = "FetchBlockHeaders on missing blocks"
1186 _, err = tx.FetchBlockHeaders(allBlockHashes)
1187 if !checkDbError(tc.t, testName, err, wantErrCode) {
1191 // Ensure FetchBlockRegions returns expected error.
1192 testName = "FetchBlockRegions on missing blocks"
1193 _, err = tx.FetchBlockRegions(allBlockRegions)
1194 if !checkDbError(tc.t, testName, err, wantErrCode) {
1198 // Ensure HasBlocks returns false for all blocks.
1199 hasBlocks, err := tx.HasBlocks(allBlockHashes)
1201 tc.t.Errorf("HasBlocks: unexpected err: %v", err)
1203 for i, hasBlock := range hasBlocks {
1205 tc.t.Errorf("HasBlocks #%d: should not have block", i)
1213 // testFetchBlockIO ensures all of the block retrieval API functions work as
1214 // expected for the provide set of blocks. The blocks must already be stored in
1215 // the database, or at least stored into the the passed transaction. It also
1216 // tests several error conditions such as ensuring the expected errors are
1217 // returned when fetching blocks, headers, and regions that don't exist.
1218 func testFetchBlockIO(tc *testContext, tx database.Tx) bool {
1219 // ---------------------
1220 // Non-bulk Block IO API
1221 // ---------------------
1223 // Test the individual block APIs one block at a time. Also, build the
1224 // data needed to test the bulk APIs below while looping.
1225 allBlockHashes := make([]chainhash.Hash, len(tc.blocks))
1226 allBlockBytes := make([][]byte, len(tc.blocks))
1227 allBlockTxLocs := make([][]wire.TxLoc, len(tc.blocks))
1228 allBlockRegions := make([]database.BlockRegion, len(tc.blocks))
1229 for i, block := range tc.blocks {
1230 blockHash := block.Hash()
1231 allBlockHashes[i] = *blockHash
1233 blockBytes, err := block.Bytes()
1235 tc.t.Errorf("block.Bytes(%d): unexpected error: %v", i,
1239 allBlockBytes[i] = blockBytes
1241 txLocs, err := block.TxLoc()
1243 tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i,
1247 allBlockTxLocs[i] = txLocs
1249 // Ensure the block data fetched from the database matches the
1251 gotBlockBytes, err := tx.FetchBlock(blockHash)
1253 tc.t.Errorf("FetchBlock(%s): unexpected error: %v",
1257 if !bytes.Equal(gotBlockBytes, blockBytes) {
1258 tc.t.Errorf("FetchBlock(%s): bytes mismatch: got %x, "+
1259 "want %x", blockHash, gotBlockBytes, blockBytes)
1263 // Ensure the block header fetched from the database matches the
1265 wantHeaderBytes := blockBytes[0:wire.MaxBlockHeaderPayload]
1266 gotHeaderBytes, err := tx.FetchBlockHeader(blockHash)
1268 tc.t.Errorf("FetchBlockHeader(%s): unexpected error: %v",
1272 if !bytes.Equal(gotHeaderBytes, wantHeaderBytes) {
1273 tc.t.Errorf("FetchBlockHeader(%s): bytes mismatch: "+
1274 "got %x, want %x", blockHash, gotHeaderBytes,
1279 // Ensure the first transaction fetched as a block region from
1280 // the database matches the expected bytes.
1281 region := database.BlockRegion{
1283 Offset: uint32(txLocs[0].TxStart),
1284 Len: uint32(txLocs[0].TxLen),
1286 allBlockRegions[i] = region
1287 endRegionOffset := region.Offset + region.Len
1288 wantRegionBytes := blockBytes[region.Offset:endRegionOffset]
1289 gotRegionBytes, err := tx.FetchBlockRegion(®ion)
1291 tc.t.Errorf("FetchBlockRegion(%s): unexpected error: %v",
1295 if !bytes.Equal(gotRegionBytes, wantRegionBytes) {
1296 tc.t.Errorf("FetchBlockRegion(%s): bytes mismatch: "+
1297 "got %x, want %x", blockHash, gotRegionBytes,
1302 // Ensure the block header fetched from the database matches the
1304 hasBlock, err := tx.HasBlock(blockHash)
1306 tc.t.Errorf("HasBlock(%s): unexpected error: %v",
1311 tc.t.Errorf("HasBlock(%s): database claims it doesn't "+
1312 "have the block when it should", blockHash)
1316 // -----------------------
1317 // Invalid blocks/regions.
1318 // -----------------------
1320 // Ensure fetching a block that doesn't exist returns the
1322 badBlockHash := &chainhash.Hash{}
1323 testName := fmt.Sprintf("FetchBlock(%s) invalid block",
1325 wantErrCode := database.ErrBlockNotFound
1326 _, err = tx.FetchBlock(badBlockHash)
1327 if !checkDbError(tc.t, testName, err, wantErrCode) {
1331 // Ensure fetching a block header that doesn't exist returns
1332 // the expected error.
1333 testName = fmt.Sprintf("FetchBlockHeader(%s) invalid block",
1335 _, err = tx.FetchBlockHeader(badBlockHash)
1336 if !checkDbError(tc.t, testName, err, wantErrCode) {
1340 // Ensure fetching a block region in a block that doesn't exist
1341 // return the expected error.
1342 testName = fmt.Sprintf("FetchBlockRegion(%s) invalid hash",
1344 wantErrCode = database.ErrBlockNotFound
1345 region.Hash = badBlockHash
1346 region.Offset = ^uint32(0)
1347 _, err = tx.FetchBlockRegion(®ion)
1348 if !checkDbError(tc.t, testName, err, wantErrCode) {
1352 // Ensure fetching a block region that is out of bounds returns
1353 // the expected error.
1354 testName = fmt.Sprintf("FetchBlockRegion(%s) invalid region",
1356 wantErrCode = database.ErrBlockRegionInvalid
1357 region.Hash = blockHash
1358 region.Offset = ^uint32(0)
1359 _, err = tx.FetchBlockRegion(®ion)
1360 if !checkDbError(tc.t, testName, err, wantErrCode) {
1365 // -----------------
1366 // Bulk Block IO API
1367 // -----------------
1369 // Ensure the bulk block data fetched from the database matches the
1371 blockData, err := tx.FetchBlocks(allBlockHashes)
1373 tc.t.Errorf("FetchBlocks: unexpected error: %v", err)
1376 if len(blockData) != len(allBlockBytes) {
1377 tc.t.Errorf("FetchBlocks: unexpected number of results - got "+
1378 "%d, want %d", len(blockData), len(allBlockBytes))
1381 for i := 0; i < len(blockData); i++ {
1382 blockHash := allBlockHashes[i]
1383 wantBlockBytes := allBlockBytes[i]
1384 gotBlockBytes := blockData[i]
1385 if !bytes.Equal(gotBlockBytes, wantBlockBytes) {
1386 tc.t.Errorf("FetchBlocks(%s): bytes mismatch: got %x, "+
1387 "want %x", blockHash, gotBlockBytes,
1393 // Ensure the bulk block headers fetched from the database match the
1395 blockHeaderData, err := tx.FetchBlockHeaders(allBlockHashes)
1397 tc.t.Errorf("FetchBlockHeaders: unexpected error: %v", err)
1400 if len(blockHeaderData) != len(allBlockBytes) {
1401 tc.t.Errorf("FetchBlockHeaders: unexpected number of results "+
1402 "- got %d, want %d", len(blockHeaderData),
1406 for i := 0; i < len(blockHeaderData); i++ {
1407 blockHash := allBlockHashes[i]
1408 wantHeaderBytes := allBlockBytes[i][0:wire.MaxBlockHeaderPayload]
1409 gotHeaderBytes := blockHeaderData[i]
1410 if !bytes.Equal(gotHeaderBytes, wantHeaderBytes) {
1411 tc.t.Errorf("FetchBlockHeaders(%s): bytes mismatch: "+
1412 "got %x, want %x", blockHash, gotHeaderBytes,
1418 // Ensure the first transaction of every block fetched in bulk block
1419 // regions from the database matches the expected bytes.
1420 allRegionBytes, err := tx.FetchBlockRegions(allBlockRegions)
1422 tc.t.Errorf("FetchBlockRegions: unexpected error: %v", err)
1426 if len(allRegionBytes) != len(allBlockRegions) {
1427 tc.t.Errorf("FetchBlockRegions: unexpected number of results "+
1428 "- got %d, want %d", len(allRegionBytes),
1429 len(allBlockRegions))
1432 for i, gotRegionBytes := range allRegionBytes {
1433 region := &allBlockRegions[i]
1434 endRegionOffset := region.Offset + region.Len
1435 wantRegionBytes := blockData[i][region.Offset:endRegionOffset]
1436 if !bytes.Equal(gotRegionBytes, wantRegionBytes) {
1437 tc.t.Errorf("FetchBlockRegions(%d): bytes mismatch: "+
1438 "got %x, want %x", i, gotRegionBytes,
1444 // Ensure the bulk determination of whether a set of block hashes are in
1445 // the database returns true for all loaded blocks.
1446 hasBlocks, err := tx.HasBlocks(allBlockHashes)
1448 tc.t.Errorf("HasBlocks: unexpected error: %v", err)
1451 for i, hasBlock := range hasBlocks {
1453 tc.t.Errorf("HasBlocks(%d): should have block", i)
1458 // -----------------------
1459 // Invalid blocks/regions.
1460 // -----------------------
1462 // Ensure fetching blocks for which one doesn't exist returns the
1464 testName := "FetchBlocks invalid hash"
1465 badBlockHashes := make([]chainhash.Hash, len(allBlockHashes)+1)
1466 copy(badBlockHashes, allBlockHashes)
1467 badBlockHashes[len(badBlockHashes)-1] = chainhash.Hash{}
1468 wantErrCode := database.ErrBlockNotFound
1469 _, err = tx.FetchBlocks(badBlockHashes)
1470 if !checkDbError(tc.t, testName, err, wantErrCode) {
1474 // Ensure fetching block headers for which one doesn't exist returns the
1476 testName = "FetchBlockHeaders invalid hash"
1477 _, err = tx.FetchBlockHeaders(badBlockHashes)
1478 if !checkDbError(tc.t, testName, err, wantErrCode) {
1482 // Ensure fetching block regions for which one of blocks doesn't exist
1483 // returns expected error.
1484 testName = "FetchBlockRegions invalid hash"
1485 badBlockRegions := make([]database.BlockRegion, len(allBlockRegions)+1)
1486 copy(badBlockRegions, allBlockRegions)
1487 badBlockRegions[len(badBlockRegions)-1].Hash = &chainhash.Hash{}
1488 wantErrCode = database.ErrBlockNotFound
1489 _, err = tx.FetchBlockRegions(badBlockRegions)
1490 if !checkDbError(tc.t, testName, err, wantErrCode) {
1494 // Ensure fetching block regions that are out of bounds returns the
1496 testName = "FetchBlockRegions invalid regions"
1497 badBlockRegions = badBlockRegions[:len(badBlockRegions)-1]
1498 for i := range badBlockRegions {
1499 badBlockRegions[i].Offset = ^uint32(0)
1501 wantErrCode = database.ErrBlockRegionInvalid
1502 _, err = tx.FetchBlockRegions(badBlockRegions)
1503 return checkDbError(tc.t, testName, err, wantErrCode)
1506 // testBlockIOTxInterface ensures that the block IO interface works as expected
1507 // for both managed read/write and manual transactions. This function leaves
1508 // all of the stored blocks in the database.
1509 func testBlockIOTxInterface(tc *testContext) bool {
1510 // Ensure attempting to store a block with a read-only transaction fails
1511 // with the expected error.
1512 err := tc.db.View(func(tx database.Tx) error {
1513 wantErrCode := database.ErrTxNotWritable
1514 for i, block := range tc.blocks {
1515 testName := fmt.Sprintf("StoreBlock(%d) on ro tx", i)
1516 err := tx.StoreBlock(block)
1517 if !checkDbError(tc.t, testName, err, wantErrCode) {
1518 return errSubTestFail
1525 if err != errSubTestFail {
1526 tc.t.Errorf("%v", err)
1531 // Populate the database with loaded blocks and ensure all of the data
1532 // fetching APIs work properly on them within the transaction before a
1533 // commit or rollback. Then, force a rollback so the code below can
1534 // ensure none of the data actually gets stored.
1535 forceRollbackError := fmt.Errorf("force rollback")
1536 err = tc.db.Update(func(tx database.Tx) error {
1537 // Store all blocks in the same transaction.
1538 for i, block := range tc.blocks {
1539 err := tx.StoreBlock(block)
1541 tc.t.Errorf("StoreBlock #%d: unexpected error: "+
1543 return errSubTestFail
1547 // Ensure attempting to store the same block again, before the
1548 // transaction has been committed, returns the expected error.
1549 wantErrCode := database.ErrBlockExists
1550 for i, block := range tc.blocks {
1551 testName := fmt.Sprintf("duplicate block entry #%d "+
1552 "(before commit)", i)
1553 err := tx.StoreBlock(block)
1554 if !checkDbError(tc.t, testName, err, wantErrCode) {
1555 return errSubTestFail
1559 // Ensure that all data fetches from the stored blocks before
1560 // the transaction has been committed work as expected.
1561 if !testFetchBlockIO(tc, tx) {
1562 return errSubTestFail
1565 return forceRollbackError
1567 if err != forceRollbackError {
1568 if err == errSubTestFail {
1572 tc.t.Errorf("Update: inner function error not returned - got "+
1573 "%v, want %v", err, forceRollbackError)
1577 // Ensure rollback was successful
1578 err = tc.db.View(func(tx database.Tx) error {
1579 if !testFetchBlockIOMissing(tc, tx) {
1580 return errSubTestFail
1585 if err != errSubTestFail {
1586 tc.t.Errorf("%v", err)
1591 // Populate the database with loaded blocks and ensure all of the data
1592 // fetching APIs work properly.
1593 err = tc.db.Update(func(tx database.Tx) error {
1594 // Store a bunch of blocks in the same transaction.
1595 for i, block := range tc.blocks {
1596 err := tx.StoreBlock(block)
1598 tc.t.Errorf("StoreBlock #%d: unexpected error: "+
1600 return errSubTestFail
1604 // Ensure attempting to store the same block again while in the
1605 // same transaction, but before it has been committed, returns
1606 // the expected error.
1607 for i, block := range tc.blocks {
1608 testName := fmt.Sprintf("duplicate block entry #%d "+
1609 "(before commit)", i)
1610 wantErrCode := database.ErrBlockExists
1611 err := tx.StoreBlock(block)
1612 if !checkDbError(tc.t, testName, err, wantErrCode) {
1613 return errSubTestFail
1617 // Ensure that all data fetches from the stored blocks before
1618 // the transaction has been committed work as expected.
1619 if !testFetchBlockIO(tc, tx) {
1620 return errSubTestFail
1626 if err != errSubTestFail {
1627 tc.t.Errorf("%v", err)
1632 // Ensure all data fetch tests work as expected using a managed
1633 // read-only transaction after the data was successfully committed
1635 err = tc.db.View(func(tx database.Tx) error {
1636 if !testFetchBlockIO(tc, tx) {
1637 return errSubTestFail
1643 if err != errSubTestFail {
1644 tc.t.Errorf("%v", err)
1649 // Ensure all data fetch tests work as expected using a managed
1650 // read-write transaction after the data was successfully committed
1652 err = tc.db.Update(func(tx database.Tx) error {
1653 if !testFetchBlockIO(tc, tx) {
1654 return errSubTestFail
1657 // Ensure attempting to store existing blocks again returns the
1658 // expected error. Note that this is different from the
1659 // previous version since this is a new transaction after the
1660 // blocks have been committed.
1661 wantErrCode := database.ErrBlockExists
1662 for i, block := range tc.blocks {
1663 testName := fmt.Sprintf("duplicate block entry #%d "+
1664 "(before commit)", i)
1665 err := tx.StoreBlock(block)
1666 if !checkDbError(tc.t, testName, err, wantErrCode) {
1667 return errSubTestFail
1674 if err != errSubTestFail {
1675 tc.t.Errorf("%v", err)
1683 // testClosedTxInterface ensures that both the metadata and block IO API
1684 // functions behave as expected when attempted against a closed transaction.
1685 func testClosedTxInterface(tc *testContext, tx database.Tx) bool {
1686 wantErrCode := database.ErrTxClosed
1687 bucket := tx.Metadata()
1688 cursor := tx.Metadata().Cursor()
1689 bucketName := []byte("closedtxbucket")
1690 keyName := []byte("closedtxkey")
1696 // Ensure that attempting to get an existing bucket returns nil when the
1697 // transaction is closed.
1698 if b := bucket.Bucket(bucketName); b != nil {
1699 tc.t.Errorf("Bucket: did not return nil on closed tx")
1703 // Ensure CreateBucket returns expected error.
1704 testName := "CreateBucket on closed tx"
1705 _, err := bucket.CreateBucket(bucketName)
1706 if !checkDbError(tc.t, testName, err, wantErrCode) {
1710 // Ensure CreateBucketIfNotExists returns expected error.
1711 testName = "CreateBucketIfNotExists on closed tx"
1712 _, err = bucket.CreateBucketIfNotExists(bucketName)
1713 if !checkDbError(tc.t, testName, err, wantErrCode) {
1717 // Ensure Delete returns expected error.
1718 testName = "Delete on closed tx"
1719 err = bucket.Delete(keyName)
1720 if !checkDbError(tc.t, testName, err, wantErrCode) {
1724 // Ensure DeleteBucket returns expected error.
1725 testName = "DeleteBucket on closed tx"
1726 err = bucket.DeleteBucket(bucketName)
1727 if !checkDbError(tc.t, testName, err, wantErrCode) {
1731 // Ensure ForEach returns expected error.
1732 testName = "ForEach on closed tx"
1733 err = bucket.ForEach(nil)
1734 if !checkDbError(tc.t, testName, err, wantErrCode) {
1738 // Ensure ForEachBucket returns expected error.
1739 testName = "ForEachBucket on closed tx"
1740 err = bucket.ForEachBucket(nil)
1741 if !checkDbError(tc.t, testName, err, wantErrCode) {
1745 // Ensure Get returns expected error.
1746 testName = "Get on closed tx"
1747 if k := bucket.Get(keyName); k != nil {
1748 tc.t.Errorf("Get: did not return nil on closed tx")
1752 // Ensure Put returns expected error.
1753 testName = "Put on closed tx"
1754 err = bucket.Put(keyName, []byte("test"))
1755 if !checkDbError(tc.t, testName, err, wantErrCode) {
1759 // -------------------
1760 // Metadata Cursor API
1761 // -------------------
1763 // Ensure attempting to get a bucket from a cursor on a closed tx gives
1765 if b := cursor.Bucket(); b != nil {
1766 tc.t.Error("Cursor.Bucket: returned non-nil on closed tx")
1770 // Ensure Cursor.Delete returns expected error.
1771 testName = "Cursor.Delete on closed tx"
1772 err = cursor.Delete()
1773 if !checkDbError(tc.t, testName, err, wantErrCode) {
1777 // Ensure Cursor.First on a closed tx returns false and nil key/value.
1779 tc.t.Error("Cursor.First: claims ok on closed tx")
1782 if cursor.Key() != nil || cursor.Value() != nil {
1783 tc.t.Error("Cursor.First: key and/or value are not nil on " +
1788 // Ensure Cursor.Last on a closed tx returns false and nil key/value.
1790 tc.t.Error("Cursor.Last: claims ok on closed tx")
1793 if cursor.Key() != nil || cursor.Value() != nil {
1794 tc.t.Error("Cursor.Last: key and/or value are not nil on " +
1799 // Ensure Cursor.Next on a closed tx returns false and nil key/value.
1801 tc.t.Error("Cursor.Next: claims ok on closed tx")
1804 if cursor.Key() != nil || cursor.Value() != nil {
1805 tc.t.Error("Cursor.Next: key and/or value are not nil on " +
1810 // Ensure Cursor.Prev on a closed tx returns false and nil key/value.
1812 tc.t.Error("Cursor.Prev: claims ok on closed tx")
1815 if cursor.Key() != nil || cursor.Value() != nil {
1816 tc.t.Error("Cursor.Prev: key and/or value are not nil on " +
1821 // Ensure Cursor.Seek on a closed tx returns false and nil key/value.
1822 if cursor.Seek([]byte{}) {
1823 tc.t.Error("Cursor.Seek: claims ok on closed tx")
1826 if cursor.Key() != nil || cursor.Value() != nil {
1827 tc.t.Error("Cursor.Seek: key and/or value are not nil on " +
1832 // ---------------------
1833 // Non-bulk Block IO API
1834 // ---------------------
1836 // Test the individual block APIs one block at a time to ensure they
1837 // return the expected error. Also, build the data needed to test the
1838 // bulk APIs below while looping.
1839 allBlockHashes := make([]chainhash.Hash, len(tc.blocks))
1840 allBlockRegions := make([]database.BlockRegion, len(tc.blocks))
1841 for i, block := range tc.blocks {
1842 blockHash := block.Hash()
1843 allBlockHashes[i] = *blockHash
1845 txLocs, err := block.TxLoc()
1847 tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i,
1852 // Ensure StoreBlock returns expected error.
1853 testName = "StoreBlock on closed tx"
1854 err = tx.StoreBlock(block)
1855 if !checkDbError(tc.t, testName, err, wantErrCode) {
1859 // Ensure FetchBlock returns expected error.
1860 testName = fmt.Sprintf("FetchBlock #%d on closed tx", i)
1861 _, err = tx.FetchBlock(blockHash)
1862 if !checkDbError(tc.t, testName, err, wantErrCode) {
1866 // Ensure FetchBlockHeader returns expected error.
1867 testName = fmt.Sprintf("FetchBlockHeader #%d on closed tx", i)
1868 _, err = tx.FetchBlockHeader(blockHash)
1869 if !checkDbError(tc.t, testName, err, wantErrCode) {
1873 // Ensure the first transaction fetched as a block region from
1874 // the database returns the expected error.
1875 region := database.BlockRegion{
1877 Offset: uint32(txLocs[0].TxStart),
1878 Len: uint32(txLocs[0].TxLen),
1880 allBlockRegions[i] = region
1881 _, err = tx.FetchBlockRegion(®ion)
1882 if !checkDbError(tc.t, testName, err, wantErrCode) {
1886 // Ensure HasBlock returns expected error.
1887 testName = fmt.Sprintf("HasBlock #%d on closed tx", i)
1888 _, err = tx.HasBlock(blockHash)
1889 if !checkDbError(tc.t, testName, err, wantErrCode) {
1894 // -----------------
1895 // Bulk Block IO API
1896 // -----------------
1898 // Ensure FetchBlocks returns expected error.
1899 testName = "FetchBlocks on closed tx"
1900 _, err = tx.FetchBlocks(allBlockHashes)
1901 if !checkDbError(tc.t, testName, err, wantErrCode) {
1905 // Ensure FetchBlockHeaders returns expected error.
1906 testName = "FetchBlockHeaders on closed tx"
1907 _, err = tx.FetchBlockHeaders(allBlockHashes)
1908 if !checkDbError(tc.t, testName, err, wantErrCode) {
1912 // Ensure FetchBlockRegions returns expected error.
1913 testName = "FetchBlockRegions on closed tx"
1914 _, err = tx.FetchBlockRegions(allBlockRegions)
1915 if !checkDbError(tc.t, testName, err, wantErrCode) {
1919 // Ensure HasBlocks returns expected error.
1920 testName = "HasBlocks on closed tx"
1921 _, err = tx.HasBlocks(allBlockHashes)
1922 if !checkDbError(tc.t, testName, err, wantErrCode) {
1930 // Ensure that attempting to rollback or commit a transaction that is
1931 // already closed returns the expected error.
1933 if !checkDbError(tc.t, "closed tx rollback", err, wantErrCode) {
1937 return checkDbError(tc.t, "closed tx commit", err, wantErrCode)
1940 // testTxClosed ensures that both the metadata and block IO API functions behave
1941 // as expected when attempted against both read-only and read-write
1943 func testTxClosed(tc *testContext) bool {
1944 bucketName := []byte("closedtxbucket")
1945 keyName := []byte("closedtxkey")
1947 // Start a transaction, create a bucket and key used for testing, and
1948 // immediately perform a commit on it so it is closed.
1949 tx, err := tc.db.Begin(true)
1951 tc.t.Errorf("Begin(true): unexpected error: %v", err)
1954 defer rollbackOnPanic(tc.t, tx)
1955 if _, err := tx.Metadata().CreateBucket(bucketName); err != nil {
1956 tc.t.Errorf("CreateBucket: unexpected error: %v", err)
1959 if err := tx.Metadata().Put(keyName, []byte("test")); err != nil {
1960 tc.t.Errorf("Put: unexpected error: %v", err)
1963 if err := tx.Commit(); err != nil {
1964 tc.t.Errorf("Commit: unexpected error: %v", err)
1968 // Ensure invoking all of the functions on the closed read-write
1969 // transaction behave as expected.
1970 if !testClosedTxInterface(tc, tx) {
1974 // Repeat the tests with a rolled-back read-only transaction.
1975 tx, err = tc.db.Begin(false)
1977 tc.t.Errorf("Begin(false): unexpected error: %v", err)
1980 defer rollbackOnPanic(tc.t, tx)
1981 if err := tx.Rollback(); err != nil {
1982 tc.t.Errorf("Rollback: unexpected error: %v", err)
1986 // Ensure invoking all of the functions on the closed read-only
1987 // transaction behave as expected.
1988 return testClosedTxInterface(tc, tx)
1991 // testConcurrecy ensure the database properly supports concurrent readers and
1992 // only a single writer. It also ensures views act as snapshots at the time
1993 // they are acquired.
1994 func testConcurrecy(tc *testContext) bool {
1995 // sleepTime is how long each of the concurrent readers should sleep to
1996 // aid in detection of whether or not the data is actually being read
1997 // concurrently. It starts with a sane lower bound.
1998 var sleepTime = time.Millisecond * 250
2000 // Determine about how long it takes for a single block read. When it's
2001 // longer than the default minimum sleep time, adjust the sleep time to
2002 // help prevent durations that are too short which would cause erroneous
2003 // test failures on slower systems.
2004 startTime := time.Now()
2005 err := tc.db.View(func(tx database.Tx) error {
2006 _, err := tx.FetchBlock(tc.blocks[0].Hash())
2010 tc.t.Errorf("Unexpected error in view: %v", err)
2013 elapsed := time.Since(startTime)
2014 if sleepTime < elapsed {
2017 tc.t.Logf("Time to load block 0: %v, using sleep time: %v", elapsed,
2020 // reader takes a block number to load and channel to return the result
2021 // of the operation on. It is used below to launch multiple concurrent
2023 numReaders := len(tc.blocks)
2024 resultChan := make(chan bool, numReaders)
2025 reader := func(blockNum int) {
2026 err := tc.db.View(func(tx database.Tx) error {
2027 time.Sleep(sleepTime)
2028 _, err := tx.FetchBlock(tc.blocks[blockNum].Hash())
2032 tc.t.Errorf("Unexpected error in concurrent view: %v",
2039 // Start up several concurrent readers for the same block and wait for
2041 startTime = time.Now()
2042 for i := 0; i < numReaders; i++ {
2045 for i := 0; i < numReaders; i++ {
2046 if result := <-resultChan; !result {
2050 elapsed = time.Since(startTime)
2051 tc.t.Logf("%d concurrent reads of same block elapsed: %v", numReaders,
2054 // Consider it a failure if it took longer than half the time it would
2055 // take with no concurrency.
2056 if elapsed > sleepTime*time.Duration(numReaders/2) {
2057 tc.t.Errorf("Concurrent views for same block did not appear to "+
2058 "run simultaneously: elapsed %v", elapsed)
2062 // Start up several concurrent readers for different blocks and wait for
2064 startTime = time.Now()
2065 for i := 0; i < numReaders; i++ {
2068 for i := 0; i < numReaders; i++ {
2069 if result := <-resultChan; !result {
2073 elapsed = time.Since(startTime)
2074 tc.t.Logf("%d concurrent reads of different blocks elapsed: %v",
2075 numReaders, elapsed)
2077 // Consider it a failure if it took longer than half the time it would
2078 // take with no concurrency.
2079 if elapsed > sleepTime*time.Duration(numReaders/2) {
2080 tc.t.Errorf("Concurrent views for different blocks did not "+
2081 "appear to run simultaneously: elapsed %v", elapsed)
2085 // Start up a few readers and wait for them to acquire views. Each
2086 // reader waits for a signal from the writer to be finished to ensure
2087 // that the data written by the writer is not seen by the view since it
2088 // was started before the data was set.
2089 concurrentKey := []byte("notthere")
2090 concurrentVal := []byte("someval")
2091 started := make(chan struct{})
2092 writeComplete := make(chan struct{})
2093 reader = func(blockNum int) {
2094 err := tc.db.View(func(tx database.Tx) error {
2095 started <- struct{}{}
2097 // Wait for the writer to complete.
2100 // Since this reader was created before the write took
2101 // place, the data it added should not be visible.
2102 val := tx.Metadata().Get(concurrentKey)
2104 return fmt.Errorf("%s should not be visible",
2110 tc.t.Errorf("Unexpected error in concurrent view: %v",
2116 for i := 0; i < numReaders; i++ {
2119 for i := 0; i < numReaders; i++ {
2123 // All readers are started and waiting for completion of the writer.
2124 // Set some data the readers are expecting to not find and signal the
2125 // readers the write is done by closing the writeComplete channel.
2126 err = tc.db.Update(func(tx database.Tx) error {
2127 return tx.Metadata().Put(concurrentKey, concurrentVal)
2130 tc.t.Errorf("Unexpected error in update: %v", err)
2133 close(writeComplete)
2135 // Wait for reader results.
2136 for i := 0; i < numReaders; i++ {
2137 if result := <-resultChan; !result {
2142 // Start a few writers and ensure the total time is at least the
2143 // writeSleepTime * numWriters. This ensures only one write transaction
2144 // can be active at a time.
2145 writeSleepTime := time.Millisecond * 250
2147 err := tc.db.Update(func(tx database.Tx) error {
2148 time.Sleep(writeSleepTime)
2152 tc.t.Errorf("Unexpected error in concurrent view: %v",
2159 startTime = time.Now()
2160 for i := 0; i < numWriters; i++ {
2163 for i := 0; i < numWriters; i++ {
2164 if result := <-resultChan; !result {
2168 elapsed = time.Since(startTime)
2169 tc.t.Logf("%d concurrent writers elapsed using sleep time %v: %v",
2170 numWriters, writeSleepTime, elapsed)
2172 // The total time must have been at least the sum of all sleeps if the
2173 // writes blocked properly.
2174 if elapsed < writeSleepTime*time.Duration(numWriters) {
2175 tc.t.Errorf("Concurrent writes appeared to run simultaneously: "+
2176 "elapsed %v", elapsed)
2183 // testConcurrentClose ensures that closing the database with open transactions
2184 // blocks until the transactions are finished.
2186 // The database will be closed upon returning from this function.
2187 func testConcurrentClose(tc *testContext) bool {
2188 // Start up a few readers and wait for them to acquire views. Each
2189 // reader waits for a signal to complete to ensure the transactions stay
2190 // open until they are explicitly signalled to be closed.
2191 var activeReaders int32
2193 started := make(chan struct{})
2194 finishReaders := make(chan struct{})
2195 resultChan := make(chan bool, numReaders+1)
2197 err := tc.db.View(func(tx database.Tx) error {
2198 atomic.AddInt32(&activeReaders, 1)
2199 started <- struct{}{}
2201 atomic.AddInt32(&activeReaders, -1)
2205 tc.t.Errorf("Unexpected error in concurrent view: %v",
2211 for i := 0; i < numReaders; i++ {
2214 for i := 0; i < numReaders; i++ {
2218 // Close the database in a separate goroutine. This should block until
2219 // the transactions are finished. Once the close has taken place, the
2220 // dbClosed channel is closed to signal the main goroutine below.
2221 dbClosed := make(chan struct{})
2223 started <- struct{}{}
2224 err := tc.db.Close()
2226 tc.t.Errorf("Unexpected error in concurrent view: %v",
2235 // Wait a short period and then signal the reader transactions to
2236 // finish. When the db closed channel is received, ensure there are no
2237 // active readers open.
2238 time.AfterFunc(time.Millisecond*250, func() { close(finishReaders) })
2240 if nr := atomic.LoadInt32(&activeReaders); nr != 0 {
2241 tc.t.Errorf("Close did not appear to block with active "+
2242 "readers: %d active", nr)
2246 // Wait for all results.
2247 for i := 0; i < numReaders+1; i++ {
2248 if result := <-resultChan; !result {
2256 // testInterface tests performs tests for the various interfaces of the database
2257 // package which require state in the database for the given database type.
2258 func testInterface(t *testing.T, db database.DB) {
2259 // Create a test context to pass around.
2260 context := testContext{t: t, db: db}
2262 // Load the test blocks and store in the test context for use throughout
2264 blocks, err := loadBlocks(t, blockDataFile, blockDataNet)
2266 t.Errorf("loadBlocks: Unexpected error: %v", err)
2269 context.blocks = blocks
2271 // Test the transaction metadata interface including managed and manual
2272 // transactions as well as buckets.
2273 if !testMetadataTxInterface(&context) {
2277 // Test the transaction block IO interface using managed and manual
2278 // transactions. This function leaves all of the stored blocks in the
2279 // database since they're used later.
2280 if !testBlockIOTxInterface(&context) {
2284 // Test all of the transaction interface functions against a closed
2285 // transaction work as expected.
2286 if !testTxClosed(&context) {
2290 // Test the database properly supports concurrency.
2291 if !testConcurrecy(&context) {
2295 // Test that closing the database with open transactions blocks until
2296 // the transactions are finished.
2298 // The database will be closed upon returning from this function, so it
2299 // must be the last thing called.
2300 testConcurrentClose(&context)