github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/database/ffldb/interface_test.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Copyright (c) 2016 The Dash developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 // This file intended to be copied into each backend driver directory. Each 7 // driver should have their own driver_test.go file which creates a database and 8 // invokes the testInterface function in this file to ensure the driver properly 9 // implements the interface. 10 // 11 // NOTE: When copying this file into the backend driver folder, the package name 12 // will need to be changed accordingly. 13 14 package ffldb_test 15 16 import ( 17 "bytes" 18 "compress/bzip2" 19 "encoding/binary" 20 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "reflect" 25 "sync/atomic" 26 "testing" 27 "time" 28 29 "github.com/dashpay/godash/chaincfg" 30 "github.com/dashpay/godash/database" 31 "github.com/dashpay/godash/wire" 32 "github.com/dashpay/godashutil" 33 ) 34 35 var ( 36 // blockDataNet is the expected network in the test block data. 37 blockDataNet = wire.MainNet 38 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") 42 43 // errSubTestFail is used to signal that a sub test returned false. 44 errSubTestFail = fmt.Errorf("sub test failure") 45 ) 46 47 // loadBlocks loads the blocks contained in the testdata directory and returns 48 // a slice of them. 49 func loadBlocks(t *testing.T, dataFile string, network wire.BitcoinNet) ([]*godashutil.Block, error) { 50 // Open the file that contains the blocks for reading. 51 fi, err := os.Open(dataFile) 52 if err != nil { 53 t.Errorf("failed to open file %v, err %v", dataFile, err) 54 return nil, err 55 } 56 defer func() { 57 if err := fi.Close(); err != nil { 58 t.Errorf("failed to close file %v %v", dataFile, 59 err) 60 } 61 }() 62 dr := bzip2.NewReader(fi) 63 64 // Set the first block as the genesis block. 65 blocks := make([]*godashutil.Block, 0, 256) 66 genesis := godashutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) 67 blocks = append(blocks, genesis) 68 69 // Load the remaining blocks. 70 for height := 1; ; height++ { 71 var net uint32 72 err := binary.Read(dr, binary.LittleEndian, &net) 73 if err == io.EOF { 74 // Hit end of file at the expected offset. No error. 75 break 76 } 77 if err != nil { 78 t.Errorf("Failed to load network type for block %d: %v", 79 height, err) 80 return nil, err 81 } 82 if net != uint32(network) { 83 t.Errorf("Block doesn't match network: %v expects %v", 84 net, network) 85 return nil, err 86 } 87 88 var blockLen uint32 89 err = binary.Read(dr, binary.LittleEndian, &blockLen) 90 if err != nil { 91 t.Errorf("Failed to load block size for block %d: %v", 92 height, err) 93 return nil, err 94 } 95 96 // Read the block. 97 blockBytes := make([]byte, blockLen) 98 _, err = io.ReadFull(dr, blockBytes) 99 if err != nil { 100 t.Errorf("Failed to load block %d: %v", height, err) 101 return nil, err 102 } 103 104 // Deserialize and store the block. 105 block, err := godashutil.NewBlockFromBytes(blockBytes) 106 if err != nil { 107 t.Errorf("Failed to parse block %v: %v", height, err) 108 return nil, err 109 } 110 blocks = append(blocks, block) 111 } 112 113 return blocks, nil 114 } 115 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) 120 if !ok { 121 t.Errorf("%s: unexpected error type - got %T, want %T", 122 testName, gotErr, database.Error{}) 123 return false 124 } 125 if dbErr.ErrorCode != wantErrCode { 126 t.Errorf("%s: unexpected error code - got %s (%s), want %s", 127 testName, dbErr.ErrorCode, dbErr.Description, 128 wantErrCode) 129 return false 130 } 131 132 return true 133 } 134 135 // testContext is used to store context information about a running test which 136 // is passed into helper functions. 137 type testContext struct { 138 t *testing.T 139 db database.DB 140 bucketDepth int 141 isWritable bool 142 blocks []*godashutil.Block 143 } 144 145 // keyPair houses a key/value pair. It is used over maps so ordering can be 146 // maintained. 147 type keyPair struct { 148 key []byte 149 value []byte 150 } 151 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 158 } 159 } 160 161 return nil, false 162 } 163 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)) 169 copy(ret, values) 170 for i := range ret { 171 if ret[i].value == nil { 172 ret[i].value = make([]byte, 0) 173 } 174 } 175 return ret 176 } 177 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)) 182 copy(ret, values) 183 for i := range ret { 184 ret[i].value = nil 185 } 186 return ret 187 } 188 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)) 196 return false 197 } 198 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, 203 pair.key) 204 return false 205 } 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, 209 pair.value) 210 return false 211 } 212 213 return true 214 } 215 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 218 // values. 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) 225 return false 226 } 227 } 228 229 return true 230 } 231 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) 238 return false 239 } 240 } 241 242 return true 243 } 244 245 // testDeleteValues removes all of the provided key/value pairs from the 246 // provided bucket. 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) 251 return false 252 } 253 } 254 255 return true 256 } 257 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() 263 if cursor == nil { 264 tc.t.Error("Bucket.Cursor: unexpected nil cursor returned") 265 return false 266 } 267 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 " + 271 "created for") 272 return false 273 } 274 275 if tc.isWritable { 276 unsortedValues := []keyPair{ 277 {[]byte("cursor"), []byte("val1")}, 278 {[]byte("abcd"), []byte("val2")}, 279 {[]byte("bcd"), []byte("val3")}, 280 {[]byte("defg"), nil}, 281 } 282 sortedValues := []keyPair{ 283 {[]byte("abcd"), []byte("val2")}, 284 {[]byte("bcd"), []byte("val3")}, 285 {[]byte("cursor"), []byte("val1")}, 286 {[]byte("defg"), nil}, 287 } 288 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) { 292 return false 293 } 294 if !testGetValues(tc, bucket, toGetValues(unsortedValues)) { 295 return false 296 } 297 298 // Ensure the cursor returns all items in byte-sorted order when 299 // iterating forward. 300 curIdx := 0 301 for ok := cursor.First(); ok; ok = cursor.Next() { 302 k, v := cursor.Key(), cursor.Value() 303 if !testCursorKeyPair(tc, k, v, curIdx, sortedValues) { 304 return false 305 } 306 curIdx++ 307 } 308 if curIdx != len(unsortedValues) { 309 tc.t.Errorf("Cursor: expected to iterate %d values, "+ 310 "but only iterated %d", len(unsortedValues), 311 curIdx) 312 return false 313 } 314 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) { 321 return false 322 } 323 curIdx-- 324 } 325 if curIdx > -1 { 326 tc.t.Errorf("Reverse cursor: expected to iterate %d "+ 327 "values, but only iterated %d", 328 len(sortedValues), len(sortedValues)-(curIdx+1)) 329 return false 330 } 331 332 // Ensure forward iteration works as expected after seeking. 333 middleIdx := (len(sortedValues) - 1) / 2 334 seekKey := sortedValues[middleIdx].key 335 curIdx = middleIdx 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) { 339 return false 340 } 341 curIdx++ 342 } 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) 347 return false 348 } 349 350 // Ensure reverse iteration works as expected after seeking. 351 curIdx = middleIdx 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) { 355 return false 356 } 357 curIdx-- 358 } 359 if curIdx > -1 { 360 tc.t.Errorf("Reverse cursor after seek: expected to "+ 361 "iterate %d values, but only iterated %d", 362 len(sortedValues)-middleIdx, middleIdx-curIdx) 363 return false 364 } 365 366 // Ensure the cursor deletes items properly. 367 if !cursor.First() { 368 tc.t.Errorf("Cursor.First: no value") 369 return false 370 } 371 k := cursor.Key() 372 if err := cursor.Delete(); err != nil { 373 tc.t.Errorf("Cursor.Delete: unexpected error: %v", err) 374 return false 375 } 376 if val := bucket.Get(k); val != nil { 377 tc.t.Errorf("Cursor.Delete: value for key %q was not "+ 378 "deleted", k) 379 return false 380 } 381 } 382 383 return true 384 } 385 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 { 391 return true 392 } 393 394 tc.bucketDepth++ 395 defer func() { 396 tc.bucketDepth-- 397 }() 398 if !testBucketInterface(tc, testBucket) { 399 return false 400 } 401 402 return true 403 } 404 405 // testBucketInterface ensures the bucket interface is working properly by 406 // exercising all of its functions. This includes the cursor interface for the 407 // cursor returned from the bucket. 408 func testBucketInterface(tc *testContext, bucket database.Bucket) bool { 409 if bucket.Writable() != tc.isWritable { 410 tc.t.Errorf("Bucket writable state does not match.") 411 return false 412 } 413 414 if tc.isWritable { 415 // keyValues holds the keys and values to use when putting 416 // values into the bucket. 417 keyValues := []keyPair{ 418 {[]byte("bucketkey1"), []byte("foo1")}, 419 {[]byte("bucketkey2"), []byte("foo2")}, 420 {[]byte("bucketkey3"), []byte("foo3")}, 421 {[]byte("bucketkey4"), nil}, 422 } 423 expectedKeyValues := toGetValues(keyValues) 424 if !testPutValues(tc, bucket, keyValues) { 425 return false 426 } 427 428 if !testGetValues(tc, bucket, expectedKeyValues) { 429 return false 430 } 431 432 // Ensure errors returned from the user-supplied ForEach 433 // function are returned. 434 forEachError := fmt.Errorf("example foreach error") 435 err := bucket.ForEach(func(k, v []byte) error { 436 return forEachError 437 }) 438 if err != forEachError { 439 tc.t.Errorf("ForEach: inner function error not "+ 440 "returned - got %v, want %v", err, forEachError) 441 return false 442 } 443 444 // Iterate all of the keys using ForEach while making sure the 445 // stored values are the expected values. 446 keysFound := make(map[string]struct{}, len(keyValues)) 447 err = bucket.ForEach(func(k, v []byte) error { 448 wantV, found := lookupKey(k, expectedKeyValues) 449 if !found { 450 return fmt.Errorf("ForEach: key '%s' should "+ 451 "exist", k) 452 } 453 454 if !reflect.DeepEqual(v, wantV) { 455 return fmt.Errorf("ForEach: value for key '%s' "+ 456 "does not match - got %s, want %s", k, 457 v, wantV) 458 } 459 460 keysFound[string(k)] = struct{}{} 461 return nil 462 }) 463 if err != nil { 464 tc.t.Errorf("%v", err) 465 return false 466 } 467 468 // Ensure all keys were iterated. 469 for _, item := range keyValues { 470 if _, ok := keysFound[string(item.key)]; !ok { 471 tc.t.Errorf("ForEach: key '%s' was not iterated "+ 472 "when it should have been", item.key) 473 return false 474 } 475 } 476 477 // Delete the keys and ensure they were deleted. 478 if !testDeleteValues(tc, bucket, keyValues) { 479 return false 480 } 481 if !testGetValues(tc, bucket, rollbackValues(keyValues)) { 482 return false 483 } 484 485 // Ensure creating a new bucket works as expected. 486 testBucketName := []byte("testbucket") 487 testBucket, err := bucket.CreateBucket(testBucketName) 488 if err != nil { 489 tc.t.Errorf("CreateBucket: unexpected error: %v", err) 490 return false 491 } 492 if !testNestedBucket(tc, testBucket) { 493 return false 494 } 495 496 // Ensure errors returned from the user-supplied ForEachBucket 497 // function are returned. 498 err = bucket.ForEachBucket(func(k []byte) error { 499 return forEachError 500 }) 501 if err != forEachError { 502 tc.t.Errorf("ForEachBucket: inner function error not "+ 503 "returned - got %v, want %v", err, forEachError) 504 return false 505 } 506 507 // Ensure creating a bucket that already exists fails with the 508 // expected error. 509 wantErrCode := database.ErrBucketExists 510 _, err = bucket.CreateBucket(testBucketName) 511 if !checkDbError(tc.t, "CreateBucket", err, wantErrCode) { 512 return false 513 } 514 515 // Ensure CreateBucketIfNotExists returns an existing bucket. 516 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) 517 if err != nil { 518 tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ 519 "error: %v", err) 520 return false 521 } 522 if !testNestedBucket(tc, testBucket) { 523 return false 524 } 525 526 // Ensure retrieving an existing bucket works as expected. 527 testBucket = bucket.Bucket(testBucketName) 528 if !testNestedBucket(tc, testBucket) { 529 return false 530 } 531 532 // Ensure deleting a bucket works as intended. 533 if err := bucket.DeleteBucket(testBucketName); err != nil { 534 tc.t.Errorf("DeleteBucket: unexpected error: %v", err) 535 return false 536 } 537 if b := bucket.Bucket(testBucketName); b != nil { 538 tc.t.Errorf("DeleteBucket: bucket '%s' still exists", 539 testBucketName) 540 return false 541 } 542 543 // Ensure deleting a bucket that doesn't exist returns the 544 // expected error. 545 wantErrCode = database.ErrBucketNotFound 546 err = bucket.DeleteBucket(testBucketName) 547 if !checkDbError(tc.t, "DeleteBucket", err, wantErrCode) { 548 return false 549 } 550 551 // Ensure CreateBucketIfNotExists creates a new bucket when 552 // it doesn't already exist. 553 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) 554 if err != nil { 555 tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ 556 "error: %v", err) 557 return false 558 } 559 if !testNestedBucket(tc, testBucket) { 560 return false 561 } 562 563 // Ensure the cursor interface works as expected. 564 if !testCursorInterface(tc, testBucket) { 565 return false 566 } 567 568 // Delete the test bucket to avoid leaving it around for future 569 // calls. 570 if err := bucket.DeleteBucket(testBucketName); err != nil { 571 tc.t.Errorf("DeleteBucket: unexpected error: %v", err) 572 return false 573 } 574 if b := bucket.Bucket(testBucketName); b != nil { 575 tc.t.Errorf("DeleteBucket: bucket '%s' still exists", 576 testBucketName) 577 return false 578 } 579 } else { 580 // Put should fail with bucket that is not writable. 581 testName := "unwritable tx put" 582 wantErrCode := database.ErrTxNotWritable 583 failBytes := []byte("fail") 584 err := bucket.Put(failBytes, failBytes) 585 if !checkDbError(tc.t, testName, err, wantErrCode) { 586 return false 587 } 588 589 // Delete should fail with bucket that is not writable. 590 testName = "unwritable tx delete" 591 err = bucket.Delete(failBytes) 592 if !checkDbError(tc.t, testName, err, wantErrCode) { 593 return false 594 } 595 596 // CreateBucket should fail with bucket that is not writable. 597 testName = "unwritable tx create bucket" 598 _, err = bucket.CreateBucket(failBytes) 599 if !checkDbError(tc.t, testName, err, wantErrCode) { 600 return false 601 } 602 603 // CreateBucketIfNotExists should fail with bucket that is not 604 // writable. 605 testName = "unwritable tx create bucket if not exists" 606 _, err = bucket.CreateBucketIfNotExists(failBytes) 607 if !checkDbError(tc.t, testName, err, wantErrCode) { 608 return false 609 } 610 611 // DeleteBucket should fail with bucket that is not writable. 612 testName = "unwritable tx delete bucket" 613 err = bucket.DeleteBucket(failBytes) 614 if !checkDbError(tc.t, testName, err, wantErrCode) { 615 return false 616 } 617 618 // Ensure the cursor interface works as expected with read-only 619 // buckets. 620 if !testCursorInterface(tc, bucket) { 621 return false 622 } 623 } 624 625 return true 626 } 627 628 // rollbackOnPanic rolls the passed transaction back if the code in the calling 629 // function panics. This is useful in case the tests unexpectedly panic which 630 // would leave any manually created transactions with the database mutex locked 631 // thereby leading to a deadlock and masking the real reason for the panic. It 632 // also logs a test error and repanics so the original panic can be traced. 633 func rollbackOnPanic(t *testing.T, tx database.Tx) { 634 if err := recover(); err != nil { 635 t.Errorf("Unexpected panic: %v", err) 636 _ = tx.Rollback() 637 panic(err) 638 } 639 } 640 641 // testMetadataManualTxInterface ensures that the manual transactions metadata 642 // interface works as expected. 643 func testMetadataManualTxInterface(tc *testContext) bool { 644 // populateValues tests that populating values works as expected. 645 // 646 // When the writable flag is false, a read-only tranasction is created, 647 // standard bucket tests for read-only transactions are performed, and 648 // the Commit function is checked to ensure it fails as expected. 649 // 650 // Otherwise, a read-write transaction is created, the values are 651 // written, standard bucket tests for read-write transactions are 652 // performed, and then the transaction is either committed or rolled 653 // back depending on the flag. 654 bucket1Name := []byte("bucket1") 655 populateValues := func(writable, rollback bool, putValues []keyPair) bool { 656 tx, err := tc.db.Begin(writable) 657 if err != nil { 658 tc.t.Errorf("Begin: unexpected error %v", err) 659 return false 660 } 661 defer rollbackOnPanic(tc.t, tx) 662 663 metadataBucket := tx.Metadata() 664 if metadataBucket == nil { 665 tc.t.Errorf("Metadata: unexpected nil bucket") 666 _ = tx.Rollback() 667 return false 668 } 669 670 bucket1 := metadataBucket.Bucket(bucket1Name) 671 if bucket1 == nil { 672 tc.t.Errorf("Bucket1: unexpected nil bucket") 673 return false 674 } 675 676 tc.isWritable = writable 677 if !testBucketInterface(tc, bucket1) { 678 _ = tx.Rollback() 679 return false 680 } 681 682 if !writable { 683 // The transaction is not writable, so it should fail 684 // the commit. 685 testName := "unwritable tx commit" 686 wantErrCode := database.ErrTxNotWritable 687 err := tx.Commit() 688 if !checkDbError(tc.t, testName, err, wantErrCode) { 689 _ = tx.Rollback() 690 return false 691 } 692 } else { 693 if !testPutValues(tc, bucket1, putValues) { 694 return false 695 } 696 697 if rollback { 698 // Rollback the transaction. 699 if err := tx.Rollback(); err != nil { 700 tc.t.Errorf("Rollback: unexpected "+ 701 "error %v", err) 702 return false 703 } 704 } else { 705 // The commit should succeed. 706 if err := tx.Commit(); err != nil { 707 tc.t.Errorf("Commit: unexpected error "+ 708 "%v", err) 709 return false 710 } 711 } 712 } 713 714 return true 715 } 716 717 // checkValues starts a read-only transaction and checks that all of 718 // the key/value pairs specified in the expectedValues parameter match 719 // what's in the database. 720 checkValues := func(expectedValues []keyPair) bool { 721 tx, err := tc.db.Begin(false) 722 if err != nil { 723 tc.t.Errorf("Begin: unexpected error %v", err) 724 return false 725 } 726 defer rollbackOnPanic(tc.t, tx) 727 728 metadataBucket := tx.Metadata() 729 if metadataBucket == nil { 730 tc.t.Errorf("Metadata: unexpected nil bucket") 731 _ = tx.Rollback() 732 return false 733 } 734 735 bucket1 := metadataBucket.Bucket(bucket1Name) 736 if bucket1 == nil { 737 tc.t.Errorf("Bucket1: unexpected nil bucket") 738 return false 739 } 740 741 if !testGetValues(tc, bucket1, expectedValues) { 742 _ = tx.Rollback() 743 return false 744 } 745 746 // Rollback the read-only transaction. 747 if err := tx.Rollback(); err != nil { 748 tc.t.Errorf("Commit: unexpected error %v", err) 749 return false 750 } 751 752 return true 753 } 754 755 // deleteValues starts a read-write transaction and deletes the keys 756 // in the passed key/value pairs. 757 deleteValues := func(values []keyPair) bool { 758 tx, err := tc.db.Begin(true) 759 if err != nil { 760 761 } 762 defer rollbackOnPanic(tc.t, tx) 763 764 metadataBucket := tx.Metadata() 765 if metadataBucket == nil { 766 tc.t.Errorf("Metadata: unexpected nil bucket") 767 _ = tx.Rollback() 768 return false 769 } 770 771 bucket1 := metadataBucket.Bucket(bucket1Name) 772 if bucket1 == nil { 773 tc.t.Errorf("Bucket1: unexpected nil bucket") 774 return false 775 } 776 777 // Delete the keys and ensure they were deleted. 778 if !testDeleteValues(tc, bucket1, values) { 779 _ = tx.Rollback() 780 return false 781 } 782 if !testGetValues(tc, bucket1, rollbackValues(values)) { 783 _ = tx.Rollback() 784 return false 785 } 786 787 // Commit the changes and ensure it was successful. 788 if err := tx.Commit(); err != nil { 789 tc.t.Errorf("Commit: unexpected error %v", err) 790 return false 791 } 792 793 return true 794 } 795 796 // keyValues holds the keys and values to use when putting values into a 797 // bucket. 798 var keyValues = []keyPair{ 799 {[]byte("umtxkey1"), []byte("foo1")}, 800 {[]byte("umtxkey2"), []byte("foo2")}, 801 {[]byte("umtxkey3"), []byte("foo3")}, 802 {[]byte("umtxkey4"), nil}, 803 } 804 805 // Ensure that attempting populating the values using a read-only 806 // transaction fails as expected. 807 if !populateValues(false, true, keyValues) { 808 return false 809 } 810 if !checkValues(rollbackValues(keyValues)) { 811 return false 812 } 813 814 // Ensure that attempting populating the values using a read-write 815 // transaction and then rolling it back yields the expected values. 816 if !populateValues(true, true, keyValues) { 817 return false 818 } 819 if !checkValues(rollbackValues(keyValues)) { 820 return false 821 } 822 823 // Ensure that attempting populating the values using a read-write 824 // transaction and then committing it stores the expected values. 825 if !populateValues(true, false, keyValues) { 826 return false 827 } 828 if !checkValues(toGetValues(keyValues)) { 829 return false 830 } 831 832 // Clean up the keys. 833 if !deleteValues(keyValues) { 834 return false 835 } 836 837 return true 838 } 839 840 // testManagedTxPanics ensures calling Rollback of Commit inside a managed 841 // transaction panics. 842 func testManagedTxPanics(tc *testContext) bool { 843 testPanic := func(fn func()) (paniced bool) { 844 // Setup a defer to catch the expected panic and update the 845 // return variable. 846 defer func() { 847 if err := recover(); err != nil { 848 paniced = true 849 } 850 }() 851 852 fn() 853 return false 854 } 855 856 // Ensure calling Commit on a managed read-only transaction panics. 857 paniced := testPanic(func() { 858 tc.db.View(func(tx database.Tx) error { 859 tx.Commit() 860 return nil 861 }) 862 }) 863 if !paniced { 864 tc.t.Error("Commit called inside View did not panic") 865 return false 866 } 867 868 // Ensure calling Rollback on a managed read-only transaction panics. 869 paniced = testPanic(func() { 870 tc.db.View(func(tx database.Tx) error { 871 tx.Rollback() 872 return nil 873 }) 874 }) 875 if !paniced { 876 tc.t.Error("Rollback called inside View did not panic") 877 return false 878 } 879 880 // Ensure calling Commit on a managed read-write transaction panics. 881 paniced = testPanic(func() { 882 tc.db.Update(func(tx database.Tx) error { 883 tx.Commit() 884 return nil 885 }) 886 }) 887 if !paniced { 888 tc.t.Error("Commit called inside Update did not panic") 889 return false 890 } 891 892 // Ensure calling Rollback on a managed read-write transaction panics. 893 paniced = testPanic(func() { 894 tc.db.Update(func(tx database.Tx) error { 895 tx.Rollback() 896 return nil 897 }) 898 }) 899 if !paniced { 900 tc.t.Error("Rollback called inside Update did not panic") 901 return false 902 } 903 904 return true 905 } 906 907 // testMetadataTxInterface tests all facets of the managed read/write and 908 // manual transaction metadata interfaces as well as the bucket interfaces under 909 // them. 910 func testMetadataTxInterface(tc *testContext) bool { 911 if !testManagedTxPanics(tc) { 912 return false 913 } 914 915 bucket1Name := []byte("bucket1") 916 err := tc.db.Update(func(tx database.Tx) error { 917 _, err := tx.Metadata().CreateBucket(bucket1Name) 918 return err 919 }) 920 if err != nil { 921 tc.t.Errorf("Update: unexpected error creating bucket: %v", err) 922 return false 923 } 924 925 if !testMetadataManualTxInterface(tc) { 926 return false 927 } 928 929 // keyValues holds the keys and values to use when putting values 930 // into a bucket. 931 keyValues := []keyPair{ 932 {[]byte("mtxkey1"), []byte("foo1")}, 933 {[]byte("mtxkey2"), []byte("foo2")}, 934 {[]byte("mtxkey3"), []byte("foo3")}, 935 {[]byte("mtxkey4"), nil}, 936 } 937 938 // Test the bucket interface via a managed read-only transaction. 939 err = tc.db.View(func(tx database.Tx) error { 940 metadataBucket := tx.Metadata() 941 if metadataBucket == nil { 942 return fmt.Errorf("Metadata: unexpected nil bucket") 943 } 944 945 bucket1 := metadataBucket.Bucket(bucket1Name) 946 if bucket1 == nil { 947 return fmt.Errorf("Bucket1: unexpected nil bucket") 948 } 949 950 tc.isWritable = false 951 if !testBucketInterface(tc, bucket1) { 952 return errSubTestFail 953 } 954 955 return nil 956 }) 957 if err != nil { 958 if err != errSubTestFail { 959 tc.t.Errorf("%v", err) 960 } 961 return false 962 } 963 964 // Ensure errors returned from the user-supplied View function are 965 // returned. 966 viewError := fmt.Errorf("example view error") 967 err = tc.db.View(func(tx database.Tx) error { 968 return viewError 969 }) 970 if err != viewError { 971 tc.t.Errorf("View: inner function error not returned - got "+ 972 "%v, want %v", err, viewError) 973 return false 974 } 975 976 // Test the bucket interface via a managed read-write transaction. 977 // Also, put a series of values and force a rollback so the following 978 // code can ensure the values were not stored. 979 forceRollbackError := fmt.Errorf("force rollback") 980 err = tc.db.Update(func(tx database.Tx) error { 981 metadataBucket := tx.Metadata() 982 if metadataBucket == nil { 983 return fmt.Errorf("Metadata: unexpected nil bucket") 984 } 985 986 bucket1 := metadataBucket.Bucket(bucket1Name) 987 if bucket1 == nil { 988 return fmt.Errorf("Bucket1: unexpected nil bucket") 989 } 990 991 tc.isWritable = true 992 if !testBucketInterface(tc, bucket1) { 993 return errSubTestFail 994 } 995 996 if !testPutValues(tc, bucket1, keyValues) { 997 return errSubTestFail 998 } 999 1000 // Return an error to force a rollback. 1001 return forceRollbackError 1002 }) 1003 if err != forceRollbackError { 1004 if err == errSubTestFail { 1005 return false 1006 } 1007 1008 tc.t.Errorf("Update: inner function error not returned - got "+ 1009 "%v, want %v", err, forceRollbackError) 1010 return false 1011 } 1012 1013 // Ensure the values that should not have been stored due to the forced 1014 // rollback above were not actually stored. 1015 err = tc.db.View(func(tx database.Tx) error { 1016 metadataBucket := tx.Metadata() 1017 if metadataBucket == nil { 1018 return fmt.Errorf("Metadata: unexpected nil bucket") 1019 } 1020 1021 if !testGetValues(tc, metadataBucket, rollbackValues(keyValues)) { 1022 return errSubTestFail 1023 } 1024 1025 return nil 1026 }) 1027 if err != nil { 1028 if err != errSubTestFail { 1029 tc.t.Errorf("%v", err) 1030 } 1031 return false 1032 } 1033 1034 // Store a series of values via a managed read-write transaction. 1035 err = tc.db.Update(func(tx database.Tx) error { 1036 metadataBucket := tx.Metadata() 1037 if metadataBucket == nil { 1038 return fmt.Errorf("Metadata: unexpected nil bucket") 1039 } 1040 1041 bucket1 := metadataBucket.Bucket(bucket1Name) 1042 if bucket1 == nil { 1043 return fmt.Errorf("Bucket1: unexpected nil bucket") 1044 } 1045 1046 if !testPutValues(tc, bucket1, keyValues) { 1047 return errSubTestFail 1048 } 1049 1050 return nil 1051 }) 1052 if err != nil { 1053 if err != errSubTestFail { 1054 tc.t.Errorf("%v", err) 1055 } 1056 return false 1057 } 1058 1059 // Ensure the values stored above were committed as expected. 1060 err = tc.db.View(func(tx database.Tx) error { 1061 metadataBucket := tx.Metadata() 1062 if metadataBucket == nil { 1063 return fmt.Errorf("Metadata: unexpected nil bucket") 1064 } 1065 1066 bucket1 := metadataBucket.Bucket(bucket1Name) 1067 if bucket1 == nil { 1068 return fmt.Errorf("Bucket1: unexpected nil bucket") 1069 } 1070 1071 if !testGetValues(tc, bucket1, toGetValues(keyValues)) { 1072 return errSubTestFail 1073 } 1074 1075 return nil 1076 }) 1077 if err != nil { 1078 if err != errSubTestFail { 1079 tc.t.Errorf("%v", err) 1080 } 1081 return false 1082 } 1083 1084 // Clean up the values stored above in a managed read-write transaction. 1085 err = tc.db.Update(func(tx database.Tx) error { 1086 metadataBucket := tx.Metadata() 1087 if metadataBucket == nil { 1088 return fmt.Errorf("Metadata: unexpected nil bucket") 1089 } 1090 1091 bucket1 := metadataBucket.Bucket(bucket1Name) 1092 if bucket1 == nil { 1093 return fmt.Errorf("Bucket1: unexpected nil bucket") 1094 } 1095 1096 if !testDeleteValues(tc, bucket1, keyValues) { 1097 return errSubTestFail 1098 } 1099 1100 return nil 1101 }) 1102 if err != nil { 1103 if err != errSubTestFail { 1104 tc.t.Errorf("%v", err) 1105 } 1106 return false 1107 } 1108 1109 return true 1110 } 1111 1112 // testFetchBlockIOMissing ensures that all of the block retrieval API functions 1113 // work as expected when requesting blocks that don't exist. 1114 func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool { 1115 wantErrCode := database.ErrBlockNotFound 1116 1117 // --------------------- 1118 // Non-bulk Block IO API 1119 // --------------------- 1120 1121 // Test the individual block APIs one block at a time to ensure they 1122 // return the expected error. Also, build the data needed to test the 1123 // bulk APIs below while looping. 1124 allBlockHashes := make([]wire.ShaHash, len(tc.blocks)) 1125 allBlockRegions := make([]database.BlockRegion, len(tc.blocks)) 1126 for i, block := range tc.blocks { 1127 blockHash := block.Sha() 1128 allBlockHashes[i] = *blockHash 1129 1130 txLocs, err := block.TxLoc() 1131 if err != nil { 1132 tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i, 1133 err) 1134 return false 1135 } 1136 1137 // Ensure FetchBlock returns expected error. 1138 testName := fmt.Sprintf("FetchBlock #%d on missing block", i) 1139 _, err = tx.FetchBlock(blockHash) 1140 if !checkDbError(tc.t, testName, err, wantErrCode) { 1141 return false 1142 } 1143 1144 // Ensure FetchBlockHeader returns expected error. 1145 testName = fmt.Sprintf("FetchBlockHeader #%d on missing block", 1146 i) 1147 _, err = tx.FetchBlockHeader(blockHash) 1148 if !checkDbError(tc.t, testName, err, wantErrCode) { 1149 return false 1150 } 1151 1152 // Ensure the first transaction fetched as a block region from 1153 // the database returns the expected error. 1154 region := database.BlockRegion{ 1155 Hash: blockHash, 1156 Offset: uint32(txLocs[0].TxStart), 1157 Len: uint32(txLocs[0].TxLen), 1158 } 1159 allBlockRegions[i] = region 1160 _, err = tx.FetchBlockRegion(®ion) 1161 if !checkDbError(tc.t, testName, err, wantErrCode) { 1162 return false 1163 } 1164 1165 // Ensure HasBlock returns false. 1166 hasBlock, err := tx.HasBlock(blockHash) 1167 if err != nil { 1168 tc.t.Errorf("HasBlock #%d: unexpected err: %v", i, err) 1169 return false 1170 } 1171 if hasBlock { 1172 tc.t.Errorf("HasBlock #%d: should not have block", i) 1173 return false 1174 } 1175 } 1176 1177 // ----------------- 1178 // Bulk Block IO API 1179 // ----------------- 1180 1181 // Ensure FetchBlocks returns expected error. 1182 testName := "FetchBlocks on missing blocks" 1183 _, err := tx.FetchBlocks(allBlockHashes) 1184 if !checkDbError(tc.t, testName, err, wantErrCode) { 1185 return false 1186 } 1187 1188 // Ensure FetchBlockHeaders returns expected error. 1189 testName = "FetchBlockHeaders on missing blocks" 1190 _, err = tx.FetchBlockHeaders(allBlockHashes) 1191 if !checkDbError(tc.t, testName, err, wantErrCode) { 1192 return false 1193 } 1194 1195 // Ensure FetchBlockRegions returns expected error. 1196 testName = "FetchBlockRegions on missing blocks" 1197 _, err = tx.FetchBlockRegions(allBlockRegions) 1198 if !checkDbError(tc.t, testName, err, wantErrCode) { 1199 return false 1200 } 1201 1202 // Ensure HasBlocks returns false for all blocks. 1203 hasBlocks, err := tx.HasBlocks(allBlockHashes) 1204 if err != nil { 1205 tc.t.Errorf("HasBlocks: unexpected err: %v", err) 1206 } 1207 for i, hasBlock := range hasBlocks { 1208 if hasBlock { 1209 tc.t.Errorf("HasBlocks #%d: should not have block", i) 1210 return false 1211 } 1212 } 1213 1214 return true 1215 } 1216 1217 // testFetchBlockIO ensures all of the block retrieval API functions work as 1218 // expected for the provide set of blocks. The blocks must already be stored in 1219 // the database, or at least stored into the the passed transaction. It also 1220 // tests several error conditions such as ensuring the expected errors are 1221 // returned when fetching blocks, headers, and regions that don't exist. 1222 func testFetchBlockIO(tc *testContext, tx database.Tx) bool { 1223 // --------------------- 1224 // Non-bulk Block IO API 1225 // --------------------- 1226 1227 // Test the individual block APIs one block at a time. Also, build the 1228 // data needed to test the bulk APIs below while looping. 1229 allBlockHashes := make([]wire.ShaHash, len(tc.blocks)) 1230 allBlockBytes := make([][]byte, len(tc.blocks)) 1231 allBlockTxLocs := make([][]wire.TxLoc, len(tc.blocks)) 1232 allBlockRegions := make([]database.BlockRegion, len(tc.blocks)) 1233 for i, block := range tc.blocks { 1234 blockHash := block.Sha() 1235 allBlockHashes[i] = *blockHash 1236 1237 blockBytes, err := block.Bytes() 1238 if err != nil { 1239 tc.t.Errorf("block.Bytes(%d): unexpected error: %v", i, 1240 err) 1241 return false 1242 } 1243 allBlockBytes[i] = blockBytes 1244 1245 txLocs, err := block.TxLoc() 1246 if err != nil { 1247 tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i, 1248 err) 1249 return false 1250 } 1251 allBlockTxLocs[i] = txLocs 1252 1253 // Ensure the block data fetched from the database matches the 1254 // expected bytes. 1255 gotBlockBytes, err := tx.FetchBlock(blockHash) 1256 if err != nil { 1257 tc.t.Errorf("FetchBlock(%s): unexpected error: %v", 1258 blockHash, err) 1259 return false 1260 } 1261 if !bytes.Equal(gotBlockBytes, blockBytes) { 1262 tc.t.Errorf("FetchBlock(%s): bytes mismatch: got %x, "+ 1263 "want %x", blockHash, gotBlockBytes, blockBytes) 1264 return false 1265 } 1266 1267 // Ensure the block header fetched from the database matches the 1268 // expected bytes. 1269 wantHeaderBytes := blockBytes[0:wire.MaxBlockHeaderPayload] 1270 gotHeaderBytes, err := tx.FetchBlockHeader(blockHash) 1271 if err != nil { 1272 tc.t.Errorf("FetchBlockHeader(%s): unexpected error: %v", 1273 blockHash, err) 1274 return false 1275 } 1276 if !bytes.Equal(gotHeaderBytes, wantHeaderBytes) { 1277 tc.t.Errorf("FetchBlockHeader(%s): bytes mismatch: "+ 1278 "got %x, want %x", blockHash, gotHeaderBytes, 1279 wantHeaderBytes) 1280 return false 1281 } 1282 1283 // Ensure the first transaction fetched as a block region from 1284 // the database matches the expected bytes. 1285 region := database.BlockRegion{ 1286 Hash: blockHash, 1287 Offset: uint32(txLocs[0].TxStart), 1288 Len: uint32(txLocs[0].TxLen), 1289 } 1290 allBlockRegions[i] = region 1291 endRegionOffset := region.Offset + region.Len 1292 wantRegionBytes := blockBytes[region.Offset:endRegionOffset] 1293 gotRegionBytes, err := tx.FetchBlockRegion(®ion) 1294 if err != nil { 1295 tc.t.Errorf("FetchBlockRegion(%s): unexpected error: %v", 1296 blockHash, err) 1297 return false 1298 } 1299 if !bytes.Equal(gotRegionBytes, wantRegionBytes) { 1300 tc.t.Errorf("FetchBlockRegion(%s): bytes mismatch: "+ 1301 "got %x, want %x", blockHash, gotRegionBytes, 1302 wantRegionBytes) 1303 return false 1304 } 1305 1306 // Ensure the block header fetched from the database matches the 1307 // expected bytes. 1308 hasBlock, err := tx.HasBlock(blockHash) 1309 if err != nil { 1310 tc.t.Errorf("HasBlock(%s): unexpected error: %v", 1311 blockHash, err) 1312 return false 1313 } 1314 if !hasBlock { 1315 tc.t.Errorf("HasBlock(%s): database claims it doesn't "+ 1316 "have the block when it should", blockHash) 1317 return false 1318 } 1319 1320 // ----------------------- 1321 // Invalid blocks/regions. 1322 // ----------------------- 1323 1324 // Ensure fetching a block that doesn't exist returns the 1325 // expected error. 1326 badBlockHash := &wire.ShaHash{} 1327 testName := fmt.Sprintf("FetchBlock(%s) invalid block", 1328 badBlockHash) 1329 wantErrCode := database.ErrBlockNotFound 1330 _, err = tx.FetchBlock(badBlockHash) 1331 if !checkDbError(tc.t, testName, err, wantErrCode) { 1332 return false 1333 } 1334 1335 // Ensure fetching a block header that doesn't exist returns 1336 // the expected error. 1337 testName = fmt.Sprintf("FetchBlockHeader(%s) invalid block", 1338 badBlockHash) 1339 _, err = tx.FetchBlockHeader(badBlockHash) 1340 if !checkDbError(tc.t, testName, err, wantErrCode) { 1341 return false 1342 } 1343 1344 // Ensure fetching a block region in a block that doesn't exist 1345 // return the expected error. 1346 testName = fmt.Sprintf("FetchBlockRegion(%s) invalid hash", 1347 badBlockHash) 1348 wantErrCode = database.ErrBlockNotFound 1349 region.Hash = badBlockHash 1350 region.Offset = ^uint32(0) 1351 _, err = tx.FetchBlockRegion(®ion) 1352 if !checkDbError(tc.t, testName, err, wantErrCode) { 1353 return false 1354 } 1355 1356 // Ensure fetching a block region that is out of bounds returns 1357 // the expected error. 1358 testName = fmt.Sprintf("FetchBlockRegion(%s) invalid region", 1359 blockHash) 1360 wantErrCode = database.ErrBlockRegionInvalid 1361 region.Hash = blockHash 1362 region.Offset = ^uint32(0) 1363 _, err = tx.FetchBlockRegion(®ion) 1364 if !checkDbError(tc.t, testName, err, wantErrCode) { 1365 return false 1366 } 1367 } 1368 1369 // ----------------- 1370 // Bulk Block IO API 1371 // ----------------- 1372 1373 // Ensure the bulk block data fetched from the database matches the 1374 // expected bytes. 1375 blockData, err := tx.FetchBlocks(allBlockHashes) 1376 if err != nil { 1377 tc.t.Errorf("FetchBlocks: unexpected error: %v", err) 1378 return false 1379 } 1380 if len(blockData) != len(allBlockBytes) { 1381 tc.t.Errorf("FetchBlocks: unexpected number of results - got "+ 1382 "%d, want %d", len(blockData), len(allBlockBytes)) 1383 return false 1384 } 1385 for i := 0; i < len(blockData); i++ { 1386 blockHash := allBlockHashes[i] 1387 wantBlockBytes := allBlockBytes[i] 1388 gotBlockBytes := blockData[i] 1389 if !bytes.Equal(gotBlockBytes, wantBlockBytes) { 1390 tc.t.Errorf("FetchBlocks(%s): bytes mismatch: got %x, "+ 1391 "want %x", blockHash, gotBlockBytes, 1392 wantBlockBytes) 1393 return false 1394 } 1395 } 1396 1397 // Ensure the bulk block headers fetched from the database match the 1398 // expected bytes. 1399 blockHeaderData, err := tx.FetchBlockHeaders(allBlockHashes) 1400 if err != nil { 1401 tc.t.Errorf("FetchBlockHeaders: unexpected error: %v", err) 1402 return false 1403 } 1404 if len(blockHeaderData) != len(allBlockBytes) { 1405 tc.t.Errorf("FetchBlockHeaders: unexpected number of results "+ 1406 "- got %d, want %d", len(blockHeaderData), 1407 len(allBlockBytes)) 1408 return false 1409 } 1410 for i := 0; i < len(blockHeaderData); i++ { 1411 blockHash := allBlockHashes[i] 1412 wantHeaderBytes := allBlockBytes[i][0:wire.MaxBlockHeaderPayload] 1413 gotHeaderBytes := blockHeaderData[i] 1414 if !bytes.Equal(gotHeaderBytes, wantHeaderBytes) { 1415 tc.t.Errorf("FetchBlockHeaders(%s): bytes mismatch: "+ 1416 "got %x, want %x", blockHash, gotHeaderBytes, 1417 wantHeaderBytes) 1418 return false 1419 } 1420 } 1421 1422 // Ensure the first transaction of every block fetched in bulk block 1423 // regions from the database matches the expected bytes. 1424 allRegionBytes, err := tx.FetchBlockRegions(allBlockRegions) 1425 if err != nil { 1426 tc.t.Errorf("FetchBlockRegions: unexpected error: %v", err) 1427 return false 1428 1429 } 1430 if len(allRegionBytes) != len(allBlockRegions) { 1431 tc.t.Errorf("FetchBlockRegions: unexpected number of results "+ 1432 "- got %d, want %d", len(allRegionBytes), 1433 len(allBlockRegions)) 1434 return false 1435 } 1436 for i, gotRegionBytes := range allRegionBytes { 1437 region := &allBlockRegions[i] 1438 endRegionOffset := region.Offset + region.Len 1439 wantRegionBytes := blockData[i][region.Offset:endRegionOffset] 1440 if !bytes.Equal(gotRegionBytes, wantRegionBytes) { 1441 tc.t.Errorf("FetchBlockRegions(%d): bytes mismatch: "+ 1442 "got %x, want %x", i, gotRegionBytes, 1443 wantRegionBytes) 1444 return false 1445 } 1446 } 1447 1448 // Ensure the bulk determination of whether a set of block hashes are in 1449 // the database returns true for all loaded blocks. 1450 hasBlocks, err := tx.HasBlocks(allBlockHashes) 1451 if err != nil { 1452 tc.t.Errorf("HasBlocks: unexpected error: %v", err) 1453 return false 1454 } 1455 for i, hasBlock := range hasBlocks { 1456 if !hasBlock { 1457 tc.t.Errorf("HasBlocks(%d): should have block", i) 1458 return false 1459 } 1460 } 1461 1462 // ----------------------- 1463 // Invalid blocks/regions. 1464 // ----------------------- 1465 1466 // Ensure fetching blocks for which one doesn't exist returns the 1467 // expected error. 1468 testName := "FetchBlocks invalid hash" 1469 badBlockHashes := make([]wire.ShaHash, len(allBlockHashes)+1) 1470 copy(badBlockHashes, allBlockHashes) 1471 badBlockHashes[len(badBlockHashes)-1] = wire.ShaHash{} 1472 wantErrCode := database.ErrBlockNotFound 1473 _, err = tx.FetchBlocks(badBlockHashes) 1474 if !checkDbError(tc.t, testName, err, wantErrCode) { 1475 return false 1476 } 1477 1478 // Ensure fetching block headers for which one doesn't exist returns the 1479 // expected error. 1480 testName = "FetchBlockHeaders invalid hash" 1481 _, err = tx.FetchBlockHeaders(badBlockHashes) 1482 if !checkDbError(tc.t, testName, err, wantErrCode) { 1483 return false 1484 } 1485 1486 // Ensure fetching block regions for which one of blocks doesn't exist 1487 // returns expected error. 1488 testName = "FetchBlockRegions invalid hash" 1489 badBlockRegions := make([]database.BlockRegion, len(allBlockRegions)+1) 1490 copy(badBlockRegions, allBlockRegions) 1491 badBlockRegions[len(badBlockRegions)-1].Hash = &wire.ShaHash{} 1492 wantErrCode = database.ErrBlockNotFound 1493 _, err = tx.FetchBlockRegions(badBlockRegions) 1494 if !checkDbError(tc.t, testName, err, wantErrCode) { 1495 return false 1496 } 1497 1498 // Ensure fetching block regions that are out of bounds returns the 1499 // expected error. 1500 testName = "FetchBlockRegions invalid regions" 1501 badBlockRegions = badBlockRegions[:len(badBlockRegions)-1] 1502 for i := range badBlockRegions { 1503 badBlockRegions[i].Offset = ^uint32(0) 1504 } 1505 wantErrCode = database.ErrBlockRegionInvalid 1506 _, err = tx.FetchBlockRegions(badBlockRegions) 1507 if !checkDbError(tc.t, testName, err, wantErrCode) { 1508 return false 1509 } 1510 1511 return true 1512 } 1513 1514 // testBlockIOTxInterface ensures that the block IO interface works as expected 1515 // for both managed read/write and manual transactions. This function leaves 1516 // all of the stored blocks in the database. 1517 func testBlockIOTxInterface(tc *testContext) bool { 1518 // Ensure attempting to store a block with a read-only transaction fails 1519 // with the expected error. 1520 err := tc.db.View(func(tx database.Tx) error { 1521 wantErrCode := database.ErrTxNotWritable 1522 for i, block := range tc.blocks { 1523 testName := fmt.Sprintf("StoreBlock(%d) on ro tx", i) 1524 err := tx.StoreBlock(block) 1525 if !checkDbError(tc.t, testName, err, wantErrCode) { 1526 return errSubTestFail 1527 } 1528 } 1529 1530 return nil 1531 }) 1532 if err != nil { 1533 if err != errSubTestFail { 1534 tc.t.Errorf("%v", err) 1535 } 1536 return false 1537 } 1538 1539 // Populate the database with loaded blocks and ensure all of the data 1540 // fetching APIs work properly on them within the transaction before a 1541 // commit or rollback. Then, force a rollback so the code below can 1542 // ensure none of the data actually gets stored. 1543 forceRollbackError := fmt.Errorf("force rollback") 1544 err = tc.db.Update(func(tx database.Tx) error { 1545 // Store all blocks in the same transaction. 1546 for i, block := range tc.blocks { 1547 err := tx.StoreBlock(block) 1548 if err != nil { 1549 tc.t.Errorf("StoreBlock #%d: unexpected error: "+ 1550 "%v", i, err) 1551 return errSubTestFail 1552 } 1553 } 1554 1555 // Ensure attempting to store the same block again, before the 1556 // transaction has been committed, returns the expected error. 1557 wantErrCode := database.ErrBlockExists 1558 for i, block := range tc.blocks { 1559 testName := fmt.Sprintf("duplicate block entry #%d "+ 1560 "(before commit)", i) 1561 err := tx.StoreBlock(block) 1562 if !checkDbError(tc.t, testName, err, wantErrCode) { 1563 return errSubTestFail 1564 } 1565 } 1566 1567 // Ensure that all data fetches from the stored blocks before 1568 // the transaction has been committed work as expected. 1569 if !testFetchBlockIO(tc, tx) { 1570 return errSubTestFail 1571 } 1572 1573 return forceRollbackError 1574 }) 1575 if err != forceRollbackError { 1576 if err == errSubTestFail { 1577 return false 1578 } 1579 1580 tc.t.Errorf("Update: inner function error not returned - got "+ 1581 "%v, want %v", err, forceRollbackError) 1582 return false 1583 } 1584 1585 // Ensure rollback was successful 1586 err = tc.db.View(func(tx database.Tx) error { 1587 if !testFetchBlockIOMissing(tc, tx) { 1588 return errSubTestFail 1589 } 1590 return nil 1591 }) 1592 if err != nil { 1593 if err != errSubTestFail { 1594 tc.t.Errorf("%v", err) 1595 } 1596 return false 1597 } 1598 1599 // Populate the database with loaded blocks and ensure all of the data 1600 // fetching APIs work properly. 1601 err = tc.db.Update(func(tx database.Tx) error { 1602 // Store a bunch of blocks in the same transaction. 1603 for i, block := range tc.blocks { 1604 err := tx.StoreBlock(block) 1605 if err != nil { 1606 tc.t.Errorf("StoreBlock #%d: unexpected error: "+ 1607 "%v", i, err) 1608 return errSubTestFail 1609 } 1610 } 1611 1612 // Ensure attempting to store the same block again while in the 1613 // same transaction, but before it has been committed, returns 1614 // the expected error. 1615 for i, block := range tc.blocks { 1616 testName := fmt.Sprintf("duplicate block entry #%d "+ 1617 "(before commit)", i) 1618 wantErrCode := database.ErrBlockExists 1619 err := tx.StoreBlock(block) 1620 if !checkDbError(tc.t, testName, err, wantErrCode) { 1621 return errSubTestFail 1622 } 1623 } 1624 1625 // Ensure that all data fetches from the stored blocks before 1626 // the transaction has been committed work as expected. 1627 if !testFetchBlockIO(tc, tx) { 1628 return errSubTestFail 1629 } 1630 1631 return nil 1632 }) 1633 if err != nil { 1634 if err != errSubTestFail { 1635 tc.t.Errorf("%v", err) 1636 } 1637 return false 1638 } 1639 1640 // Ensure all data fetch tests work as expected using a managed 1641 // read-only transaction after the data was successfully committed 1642 // above. 1643 err = tc.db.View(func(tx database.Tx) error { 1644 if !testFetchBlockIO(tc, tx) { 1645 return errSubTestFail 1646 } 1647 1648 return nil 1649 }) 1650 if err != nil { 1651 if err != errSubTestFail { 1652 tc.t.Errorf("%v", err) 1653 } 1654 return false 1655 } 1656 1657 // Ensure all data fetch tests work as expected using a managed 1658 // read-write transaction after the data was successfully committed 1659 // above. 1660 err = tc.db.Update(func(tx database.Tx) error { 1661 if !testFetchBlockIO(tc, tx) { 1662 return errSubTestFail 1663 } 1664 1665 // Ensure attempting to store existing blocks again returns the 1666 // expected error. Note that this is different from the 1667 // previous version since this is a new transaction after the 1668 // blocks have been committed. 1669 wantErrCode := database.ErrBlockExists 1670 for i, block := range tc.blocks { 1671 testName := fmt.Sprintf("duplicate block entry #%d "+ 1672 "(before commit)", i) 1673 err := tx.StoreBlock(block) 1674 if !checkDbError(tc.t, testName, err, wantErrCode) { 1675 return errSubTestFail 1676 } 1677 } 1678 1679 return nil 1680 }) 1681 if err != nil { 1682 if err != errSubTestFail { 1683 tc.t.Errorf("%v", err) 1684 } 1685 return false 1686 } 1687 1688 return true 1689 } 1690 1691 // testClosedTxInterface ensures that both the metadata and block IO API 1692 // functions behave as expected when attempted against a closed transaction. 1693 func testClosedTxInterface(tc *testContext, tx database.Tx) bool { 1694 wantErrCode := database.ErrTxClosed 1695 bucket := tx.Metadata() 1696 cursor := tx.Metadata().Cursor() 1697 bucketName := []byte("closedtxbucket") 1698 keyName := []byte("closedtxkey") 1699 1700 // ------------ 1701 // Metadata API 1702 // ------------ 1703 1704 // Ensure that attempting to get an existing bucket returns nil when the 1705 // transaction is closed. 1706 if b := bucket.Bucket(bucketName); b != nil { 1707 tc.t.Errorf("Bucket: did not return nil on closed tx") 1708 return false 1709 } 1710 1711 // Ensure CreateBucket returns expected error. 1712 testName := "CreateBucket on closed tx" 1713 _, err := bucket.CreateBucket(bucketName) 1714 if !checkDbError(tc.t, testName, err, wantErrCode) { 1715 return false 1716 } 1717 1718 // Ensure CreateBucketIfNotExists returns expected error. 1719 testName = "CreateBucketIfNotExists on closed tx" 1720 _, err = bucket.CreateBucketIfNotExists(bucketName) 1721 if !checkDbError(tc.t, testName, err, wantErrCode) { 1722 return false 1723 } 1724 1725 // Ensure Delete returns expected error. 1726 testName = "Delete on closed tx" 1727 err = bucket.Delete(keyName) 1728 if !checkDbError(tc.t, testName, err, wantErrCode) { 1729 return false 1730 } 1731 1732 // Ensure DeleteBucket returns expected error. 1733 testName = "DeleteBucket on closed tx" 1734 err = bucket.DeleteBucket(bucketName) 1735 if !checkDbError(tc.t, testName, err, wantErrCode) { 1736 return false 1737 } 1738 1739 // Ensure ForEach returns expected error. 1740 testName = "ForEach on closed tx" 1741 err = bucket.ForEach(nil) 1742 if !checkDbError(tc.t, testName, err, wantErrCode) { 1743 return false 1744 } 1745 1746 // Ensure ForEachBucket returns expected error. 1747 testName = "ForEachBucket on closed tx" 1748 err = bucket.ForEachBucket(nil) 1749 if !checkDbError(tc.t, testName, err, wantErrCode) { 1750 return false 1751 } 1752 1753 // Ensure Get returns expected error. 1754 testName = "Get on closed tx" 1755 if k := bucket.Get(keyName); k != nil { 1756 tc.t.Errorf("Get: did not return nil on closed tx") 1757 return false 1758 } 1759 1760 // Ensure Put returns expected error. 1761 testName = "Put on closed tx" 1762 err = bucket.Put(keyName, []byte("test")) 1763 if !checkDbError(tc.t, testName, err, wantErrCode) { 1764 return false 1765 } 1766 1767 // ------------------- 1768 // Metadata Cursor API 1769 // ------------------- 1770 1771 // Ensure attempting to get a bucket from a cursor on a closed tx gives 1772 // back nil. 1773 if b := cursor.Bucket(); b != nil { 1774 tc.t.Error("Cursor.Bucket: returned non-nil on closed tx") 1775 return false 1776 } 1777 1778 // Ensure Cursor.Delete returns expected error. 1779 testName = "Cursor.Delete on closed tx" 1780 err = cursor.Delete() 1781 if !checkDbError(tc.t, testName, err, wantErrCode) { 1782 return false 1783 } 1784 1785 // Ensure Cursor.First on a closed tx returns false and nil key/value. 1786 if cursor.First() { 1787 tc.t.Error("Cursor.First: claims ok on closed tx") 1788 return false 1789 } 1790 if cursor.Key() != nil || cursor.Value() != nil { 1791 tc.t.Error("Cursor.First: key and/or value are not nil on " + 1792 "closed tx") 1793 return false 1794 } 1795 1796 // Ensure Cursor.Last on a closed tx returns false and nil key/value. 1797 if cursor.Last() { 1798 tc.t.Error("Cursor.Last: claims ok on closed tx") 1799 return false 1800 } 1801 if cursor.Key() != nil || cursor.Value() != nil { 1802 tc.t.Error("Cursor.Last: key and/or value are not nil on " + 1803 "closed tx") 1804 return false 1805 } 1806 1807 // Ensure Cursor.Next on a closed tx returns false and nil key/value. 1808 if cursor.Next() { 1809 tc.t.Error("Cursor.Next: claims ok on closed tx") 1810 return false 1811 } 1812 if cursor.Key() != nil || cursor.Value() != nil { 1813 tc.t.Error("Cursor.Next: key and/or value are not nil on " + 1814 "closed tx") 1815 return false 1816 } 1817 1818 // Ensure Cursor.Prev on a closed tx returns false and nil key/value. 1819 if cursor.Prev() { 1820 tc.t.Error("Cursor.Prev: claims ok on closed tx") 1821 return false 1822 } 1823 if cursor.Key() != nil || cursor.Value() != nil { 1824 tc.t.Error("Cursor.Prev: key and/or value are not nil on " + 1825 "closed tx") 1826 return false 1827 } 1828 1829 // Ensure Cursor.Seek on a closed tx returns false and nil key/value. 1830 if cursor.Seek([]byte{}) { 1831 tc.t.Error("Cursor.Seek: claims ok on closed tx") 1832 return false 1833 } 1834 if cursor.Key() != nil || cursor.Value() != nil { 1835 tc.t.Error("Cursor.Seek: key and/or value are not nil on " + 1836 "closed tx") 1837 return false 1838 } 1839 1840 // --------------------- 1841 // Non-bulk Block IO API 1842 // --------------------- 1843 1844 // Test the individual block APIs one block at a time to ensure they 1845 // return the expected error. Also, build the data needed to test the 1846 // bulk APIs below while looping. 1847 allBlockHashes := make([]wire.ShaHash, len(tc.blocks)) 1848 allBlockRegions := make([]database.BlockRegion, len(tc.blocks)) 1849 for i, block := range tc.blocks { 1850 blockHash := block.Sha() 1851 allBlockHashes[i] = *blockHash 1852 1853 txLocs, err := block.TxLoc() 1854 if err != nil { 1855 tc.t.Errorf("block.TxLoc(%d): unexpected error: %v", i, 1856 err) 1857 return false 1858 } 1859 1860 // Ensure StoreBlock returns expected error. 1861 testName = "StoreBlock on closed tx" 1862 err = tx.StoreBlock(block) 1863 if !checkDbError(tc.t, testName, err, wantErrCode) { 1864 return false 1865 } 1866 1867 // Ensure FetchBlock returns expected error. 1868 testName = fmt.Sprintf("FetchBlock #%d on closed tx", i) 1869 _, err = tx.FetchBlock(blockHash) 1870 if !checkDbError(tc.t, testName, err, wantErrCode) { 1871 return false 1872 } 1873 1874 // Ensure FetchBlockHeader returns expected error. 1875 testName = fmt.Sprintf("FetchBlockHeader #%d on closed tx", i) 1876 _, err = tx.FetchBlockHeader(blockHash) 1877 if !checkDbError(tc.t, testName, err, wantErrCode) { 1878 return false 1879 } 1880 1881 // Ensure the first transaction fetched as a block region from 1882 // the database returns the expected error. 1883 region := database.BlockRegion{ 1884 Hash: blockHash, 1885 Offset: uint32(txLocs[0].TxStart), 1886 Len: uint32(txLocs[0].TxLen), 1887 } 1888 allBlockRegions[i] = region 1889 _, err = tx.FetchBlockRegion(®ion) 1890 if !checkDbError(tc.t, testName, err, wantErrCode) { 1891 return false 1892 } 1893 1894 // Ensure HasBlock returns expected error. 1895 testName = fmt.Sprintf("HasBlock #%d on closed tx", i) 1896 _, err = tx.HasBlock(blockHash) 1897 if !checkDbError(tc.t, testName, err, wantErrCode) { 1898 return false 1899 } 1900 } 1901 1902 // ----------------- 1903 // Bulk Block IO API 1904 // ----------------- 1905 1906 // Ensure FetchBlocks returns expected error. 1907 testName = "FetchBlocks on closed tx" 1908 _, err = tx.FetchBlocks(allBlockHashes) 1909 if !checkDbError(tc.t, testName, err, wantErrCode) { 1910 return false 1911 } 1912 1913 // Ensure FetchBlockHeaders returns expected error. 1914 testName = "FetchBlockHeaders on closed tx" 1915 _, err = tx.FetchBlockHeaders(allBlockHashes) 1916 if !checkDbError(tc.t, testName, err, wantErrCode) { 1917 return false 1918 } 1919 1920 // Ensure FetchBlockRegions returns expected error. 1921 testName = "FetchBlockRegions on closed tx" 1922 _, err = tx.FetchBlockRegions(allBlockRegions) 1923 if !checkDbError(tc.t, testName, err, wantErrCode) { 1924 return false 1925 } 1926 1927 // Ensure HasBlocks returns expected error. 1928 testName = "HasBlocks on closed tx" 1929 _, err = tx.HasBlocks(allBlockHashes) 1930 if !checkDbError(tc.t, testName, err, wantErrCode) { 1931 return false 1932 } 1933 1934 // --------------- 1935 // Commit/Rollback 1936 // --------------- 1937 1938 // Ensure that attempting to rollback or commit a transaction that is 1939 // already closed returns the expected error. 1940 err = tx.Rollback() 1941 if !checkDbError(tc.t, "closed tx rollback", err, wantErrCode) { 1942 return false 1943 } 1944 err = tx.Commit() 1945 if !checkDbError(tc.t, "closed tx commit", err, wantErrCode) { 1946 return false 1947 } 1948 1949 return true 1950 } 1951 1952 // testTxClosed ensures that both the metadata and block IO API functions behave 1953 // as expected when attempted against both read-only and read-write 1954 // transactions. 1955 func testTxClosed(tc *testContext) bool { 1956 bucketName := []byte("closedtxbucket") 1957 keyName := []byte("closedtxkey") 1958 1959 // Start a transaction, create a bucket and key used for testing, and 1960 // immediately perform a commit on it so it is closed. 1961 tx, err := tc.db.Begin(true) 1962 if err != nil { 1963 tc.t.Errorf("Begin(true): unexpected error: %v", err) 1964 return false 1965 } 1966 defer rollbackOnPanic(tc.t, tx) 1967 if _, err := tx.Metadata().CreateBucket(bucketName); err != nil { 1968 tc.t.Errorf("CreateBucket: unexpected error: %v", err) 1969 return false 1970 } 1971 if err := tx.Metadata().Put(keyName, []byte("test")); err != nil { 1972 tc.t.Errorf("Put: unexpected error: %v", err) 1973 return false 1974 } 1975 if err := tx.Commit(); err != nil { 1976 tc.t.Errorf("Commit: unexpected error: %v", err) 1977 return false 1978 } 1979 1980 // Ensure invoking all of the functions on the closed read-write 1981 // transaction behave as expected. 1982 if !testClosedTxInterface(tc, tx) { 1983 return false 1984 } 1985 1986 // Repeat the tests with a rolled-back read-only transaction. 1987 tx, err = tc.db.Begin(false) 1988 if err != nil { 1989 tc.t.Errorf("Begin(false): unexpected error: %v", err) 1990 return false 1991 } 1992 defer rollbackOnPanic(tc.t, tx) 1993 if err := tx.Rollback(); err != nil { 1994 tc.t.Errorf("Rollback: unexpected error: %v", err) 1995 return false 1996 } 1997 1998 // Ensure invoking all of the functions on the closed read-only 1999 // transaction behave as expected. 2000 return testClosedTxInterface(tc, tx) 2001 } 2002 2003 // testConcurrecy ensure the database properly supports concurrent readers and 2004 // only a single writer. It also ensures views act as snapshots at the time 2005 // they are acquired. 2006 func testConcurrecy(tc *testContext) bool { 2007 // sleepTime is how long each of the concurrent readers should sleep to 2008 // aid in detection of whether or not the data is actually being read 2009 // concurrently. It starts with a sane lower bound. 2010 var sleepTime = time.Millisecond * 250 2011 2012 // Determine about how long it takes for a single block read. When it's 2013 // longer than the default minimum sleep time, adjust the sleep time to 2014 // help prevent durations that are too short which would cause erroneous 2015 // test failures on slower systems. 2016 startTime := time.Now() 2017 err := tc.db.View(func(tx database.Tx) error { 2018 _, err := tx.FetchBlock(tc.blocks[0].Sha()) 2019 if err != nil { 2020 return err 2021 } 2022 return nil 2023 }) 2024 if err != nil { 2025 tc.t.Errorf("Unexpected error in view: %v", err) 2026 return false 2027 } 2028 elapsed := time.Now().Sub(startTime) 2029 if sleepTime < elapsed { 2030 sleepTime = elapsed 2031 } 2032 tc.t.Logf("Time to load block 0: %v, using sleep time: %v", elapsed, 2033 sleepTime) 2034 2035 // reader takes a block number to load and channel to return the result 2036 // of the operation on. It is used below to launch multiple concurrent 2037 // readers. 2038 numReaders := len(tc.blocks) 2039 resultChan := make(chan bool, numReaders) 2040 reader := func(blockNum int) { 2041 err := tc.db.View(func(tx database.Tx) error { 2042 time.Sleep(sleepTime) 2043 _, err := tx.FetchBlock(tc.blocks[blockNum].Sha()) 2044 if err != nil { 2045 return err 2046 } 2047 return nil 2048 }) 2049 if err != nil { 2050 tc.t.Errorf("Unexpected error in concurrent view: %v", 2051 err) 2052 resultChan <- false 2053 } 2054 resultChan <- true 2055 } 2056 2057 // Start up several concurrent readers for the same block and wait for 2058 // the results. 2059 startTime = time.Now() 2060 for i := 0; i < numReaders; i++ { 2061 go reader(0) 2062 } 2063 for i := 0; i < numReaders; i++ { 2064 if result := <-resultChan; !result { 2065 return false 2066 } 2067 } 2068 elapsed = time.Now().Sub(startTime) 2069 tc.t.Logf("%d concurrent reads of same block elapsed: %v", numReaders, 2070 elapsed) 2071 2072 // Consider it a failure if it took longer than half the time it would 2073 // take with no concurrency. 2074 if elapsed > sleepTime*time.Duration(numReaders/2) { 2075 tc.t.Errorf("Concurrent views for same block did not appear to "+ 2076 "run simultaneously: elapsed %v", elapsed) 2077 return false 2078 } 2079 2080 // Start up several concurrent readers for different blocks and wait for 2081 // the results. 2082 startTime = time.Now() 2083 for i := 0; i < numReaders; i++ { 2084 go reader(i) 2085 } 2086 for i := 0; i < numReaders; i++ { 2087 if result := <-resultChan; !result { 2088 return false 2089 } 2090 } 2091 elapsed = time.Now().Sub(startTime) 2092 tc.t.Logf("%d concurrent reads of different blocks elapsed: %v", 2093 numReaders, elapsed) 2094 2095 // Consider it a failure if it took longer than half the time it would 2096 // take with no concurrency. 2097 if elapsed > sleepTime*time.Duration(numReaders/2) { 2098 tc.t.Errorf("Concurrent views for different blocks did not "+ 2099 "appear to run simultaneously: elapsed %v", elapsed) 2100 return false 2101 } 2102 2103 // Start up a few readers and wait for them to acquire views. Each 2104 // reader waits for a signal from the writer to be finished to ensure 2105 // that the data written by the writer is not seen by the view since it 2106 // was started before the data was set. 2107 concurrentKey := []byte("notthere") 2108 concurrentVal := []byte("someval") 2109 started := make(chan struct{}) 2110 writeComplete := make(chan struct{}) 2111 reader = func(blockNum int) { 2112 err := tc.db.View(func(tx database.Tx) error { 2113 started <- struct{}{} 2114 2115 // Wait for the writer to complete. 2116 <-writeComplete 2117 2118 // Since this reader was created before the write took 2119 // place, the data it added should not be visible. 2120 val := tx.Metadata().Get(concurrentKey) 2121 if val != nil { 2122 return fmt.Errorf("%s should not be visible", 2123 concurrentKey) 2124 } 2125 return nil 2126 }) 2127 if err != nil { 2128 tc.t.Errorf("Unexpected error in concurrent view: %v", 2129 err) 2130 resultChan <- false 2131 } 2132 resultChan <- true 2133 } 2134 for i := 0; i < numReaders; i++ { 2135 go reader(0) 2136 } 2137 for i := 0; i < numReaders; i++ { 2138 <-started 2139 } 2140 2141 // All readers are started and waiting for completion of the writer. 2142 // Set some data the readers are expecting to not find and signal the 2143 // readers the write is done by closing the writeComplete channel. 2144 err = tc.db.Update(func(tx database.Tx) error { 2145 err := tx.Metadata().Put(concurrentKey, concurrentVal) 2146 if err != nil { 2147 return err 2148 } 2149 return nil 2150 }) 2151 if err != nil { 2152 tc.t.Errorf("Unexpected error in update: %v", err) 2153 return false 2154 } 2155 close(writeComplete) 2156 2157 // Wait for reader results. 2158 for i := 0; i < numReaders; i++ { 2159 if result := <-resultChan; !result { 2160 return false 2161 } 2162 } 2163 2164 // Start a few writers and ensure the total time is at least the 2165 // writeSleepTime * numWriters. This ensures only one write transaction 2166 // can be active at a time. 2167 writeSleepTime := time.Millisecond * 250 2168 writer := func() { 2169 err := tc.db.Update(func(tx database.Tx) error { 2170 time.Sleep(writeSleepTime) 2171 return nil 2172 }) 2173 if err != nil { 2174 tc.t.Errorf("Unexpected error in concurrent view: %v", 2175 err) 2176 resultChan <- false 2177 } 2178 resultChan <- true 2179 } 2180 numWriters := 3 2181 startTime = time.Now() 2182 for i := 0; i < numWriters; i++ { 2183 go writer() 2184 } 2185 for i := 0; i < numWriters; i++ { 2186 if result := <-resultChan; !result { 2187 return false 2188 } 2189 } 2190 elapsed = time.Now().Sub(startTime) 2191 tc.t.Logf("%d concurrent writers elapsed using sleep time %v: %v", 2192 numWriters, writeSleepTime, elapsed) 2193 2194 // The total time must have been at least the sum of all sleeps if the 2195 // writes blocked properly. 2196 if elapsed < writeSleepTime*time.Duration(numWriters) { 2197 tc.t.Errorf("Concurrent writes appeared to run simultaneously: "+ 2198 "elapsed %v", elapsed) 2199 return false 2200 } 2201 2202 return true 2203 } 2204 2205 // testConcurrentClose ensures that closing the database with open transactions 2206 // blocks until the transactions are finished. 2207 // 2208 // The database will be closed upon returning from this function. 2209 func testConcurrentClose(tc *testContext) bool { 2210 // Start up a few readers and wait for them to acquire views. Each 2211 // reader waits for a signal to complete to ensure the transactions stay 2212 // open until they are explicitly signalled to be closed. 2213 var activeReaders int32 2214 numReaders := 3 2215 started := make(chan struct{}) 2216 finishReaders := make(chan struct{}) 2217 resultChan := make(chan bool, numReaders+1) 2218 reader := func() { 2219 err := tc.db.View(func(tx database.Tx) error { 2220 atomic.AddInt32(&activeReaders, 1) 2221 started <- struct{}{} 2222 <-finishReaders 2223 atomic.AddInt32(&activeReaders, -1) 2224 return nil 2225 }) 2226 if err != nil { 2227 tc.t.Errorf("Unexpected error in concurrent view: %v", 2228 err) 2229 resultChan <- false 2230 } 2231 resultChan <- true 2232 } 2233 for i := 0; i < numReaders; i++ { 2234 go reader() 2235 } 2236 for i := 0; i < numReaders; i++ { 2237 <-started 2238 } 2239 2240 // Close the database in a separate goroutine. This should block until 2241 // the transactions are finished. Once the close has taken place, the 2242 // dbClosed channel is closed to signal the main goroutine below. 2243 dbClosed := make(chan struct{}) 2244 go func() { 2245 started <- struct{}{} 2246 err := tc.db.Close() 2247 if err != nil { 2248 tc.t.Errorf("Unexpected error in concurrent view: %v", 2249 err) 2250 resultChan <- false 2251 } 2252 close(dbClosed) 2253 resultChan <- true 2254 }() 2255 <-started 2256 2257 // Wait a short period and then signal the reader transactions to 2258 // finish. When the db closed channel is received, ensure there are no 2259 // active readers open. 2260 time.AfterFunc(time.Millisecond*250, func() { close(finishReaders) }) 2261 <-dbClosed 2262 if nr := atomic.LoadInt32(&activeReaders); nr != 0 { 2263 tc.t.Errorf("Close did not appear to block with active "+ 2264 "readers: %d active", nr) 2265 return false 2266 } 2267 2268 // Wait for all results. 2269 for i := 0; i < numReaders+1; i++ { 2270 if result := <-resultChan; !result { 2271 return false 2272 } 2273 } 2274 2275 return true 2276 } 2277 2278 // testInterface tests performs tests for the various interfaces of the database 2279 // package which require state in the database for the given database type. 2280 func testInterface(t *testing.T, db database.DB) { 2281 // Create a test context to pass around. 2282 context := testContext{t: t, db: db} 2283 2284 // Load the test blocks and store in the test context for use throughout 2285 // the tests. 2286 blocks, err := loadBlocks(t, blockDataFile, blockDataNet) 2287 if err != nil { 2288 t.Errorf("loadBlocks: Unexpected error: %v", err) 2289 return 2290 } 2291 context.blocks = blocks 2292 2293 // Test the transaction metadata interface including managed and manual 2294 // transactions as well as buckets. 2295 if !testMetadataTxInterface(&context) { 2296 return 2297 } 2298 2299 // Test the transaction block IO interface using managed and manual 2300 // transactions. This function leaves all of the stored blocks in the 2301 // database since they're used later. 2302 if !testBlockIOTxInterface(&context) { 2303 return 2304 } 2305 2306 // Test all of the transaction interface functions against a closed 2307 // transaction work as expected. 2308 if !testTxClosed(&context) { 2309 return 2310 } 2311 2312 // Test the database properly supports concurrency. 2313 if !testConcurrecy(&context) { 2314 return 2315 } 2316 2317 // Test that closing the database with open transactions blocks until 2318 // the transactions are finished. 2319 // 2320 // The database will be closed upon returning from this function, so it 2321 // must be the last thing called. 2322 testConcurrentClose(&context) 2323 }