github.com/btcsuite/btcwallet/walletdb@v1.4.2/walletdbtest/interface.go (about) 1 // Copyright (c) 2014-2017 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package walletdbtest 6 7 import ( 8 "bytes" 9 "fmt" 10 "reflect" 11 "sync" 12 13 "github.com/btcsuite/btcwallet/walletdb" 14 ) 15 16 // errSubTestFail is used to signal that a sub test returned false. 17 var errSubTestFail = fmt.Errorf("sub test failure") 18 19 // testContext is used to store context information about a running test which 20 // is passed into helper functions. 21 type testContext struct { 22 t Tester 23 db walletdb.DB 24 bucketDepth int 25 isWritable bool 26 } 27 28 // rollbackValues returns a copy of the provided map with all values set to an 29 // empty string. This is used to test that values are properly rolled back. 30 func rollbackValues(values map[string]string) map[string]string { 31 retMap := make(map[string]string, len(values)) 32 for k := range values { 33 retMap[k] = "" 34 } 35 return retMap 36 } 37 38 // testGetValues checks that all of the provided key/value pairs can be 39 // retrieved from the database and the retrieved values match the provided 40 // values. 41 func testGetValues(tc *testContext, bucket walletdb.ReadBucket, values map[string]string) bool { 42 for k, v := range values { 43 var vBytes []byte 44 if v != "" { 45 vBytes = []byte(v) 46 } 47 48 gotValue := bucket.Get([]byte(k)) 49 if !reflect.DeepEqual(gotValue, vBytes) { 50 tc.t.Errorf("Get: unexpected value - got %s, want %s", 51 gotValue, vBytes) 52 return false 53 } 54 } 55 56 return true 57 } 58 59 // testPutValues stores all of the provided key/value pairs in the provided 60 // bucket while checking for errors. 61 func testPutValues(tc *testContext, bucket walletdb.ReadWriteBucket, values map[string]string) bool { 62 for k, v := range values { 63 var vBytes []byte 64 if v != "" { 65 vBytes = []byte(v) 66 } 67 if err := bucket.Put([]byte(k), vBytes); err != nil { 68 tc.t.Errorf("Put: unexpected error: %v", err) 69 return false 70 } 71 } 72 73 return true 74 } 75 76 // testDeleteValues removes all of the provided key/value pairs from the 77 // provided bucket. 78 func testDeleteValues(tc *testContext, bucket walletdb.ReadWriteBucket, values map[string]string) bool { 79 for k := range values { 80 if err := bucket.Delete([]byte(k)); err != nil { 81 tc.t.Errorf("Delete: unexpected error: %v", err) 82 return false 83 } 84 } 85 86 return true 87 } 88 89 // testNestedReadWriteBucket reruns the testBucketInterface against a nested bucket along 90 // with a counter to only test a couple of level deep. 91 func testNestedReadWriteBucket(tc *testContext, testBucket walletdb.ReadWriteBucket) bool { 92 // Don't go more than 2 nested level deep. 93 if tc.bucketDepth > 1 { 94 return true 95 } 96 97 tc.bucketDepth++ 98 defer func() { 99 tc.bucketDepth-- 100 }() 101 if !testReadWriteBucketInterface(tc, testBucket) { 102 return false 103 } 104 105 return true 106 } 107 108 // testSequence tests that the sequence related methods work as expected. 109 func testSequence(tc *testContext, testBucket walletdb.ReadWriteBucket) bool { 110 // Obtaining the current sequence twice should give us the same value. 111 seqNo1 := testBucket.Sequence() 112 seqNo2 := testBucket.Sequence() 113 if seqNo1 != seqNo2 { 114 tc.t.Errorf("Sequence: seq has incremented") 115 return false 116 } 117 118 // Incrementing to the next sequence should give us a value one larger 119 // than the prior number. 120 seqNo3, err := testBucket.NextSequence() 121 if err != nil { 122 tc.t.Errorf("Sequence: unexpected error: %v", err) 123 return false 124 } 125 if seqNo3 != seqNo2+1 { 126 tc.t.Errorf("Sequence: expected seq no of %v, instead got %v", 127 seqNo2+1, seqNo3) 128 return false 129 } 130 131 // We should be able to modify the sequence base number. 132 newBase := uint64(100) 133 if err := testBucket.SetSequence(newBase); err != nil { 134 tc.t.Errorf("Sequence: unexpected error: %v", err) 135 return false 136 } 137 138 // Any offset from this new sequence should now be properly reflected. 139 seqNo4, err := testBucket.NextSequence() 140 if err != nil { 141 tc.t.Errorf("Sequence: unexpected error: %v", err) 142 return false 143 } 144 if seqNo4 != newBase+1 { 145 tc.t.Errorf("Sequence: expected seq no of %v, instead got %v", 146 newBase+1, seqNo4) 147 return false 148 } 149 150 return true 151 } 152 153 // testReadWriteBucketInterface ensures the bucket interface is working properly by 154 // exercising all of its functions. 155 func testReadWriteBucketInterface(tc *testContext, bucket walletdb.ReadWriteBucket) bool { 156 // keyValues holds the keys and values to use when putting 157 // values into the bucket. 158 var keyValues = map[string]string{ 159 "bucketkey1": "foo1", 160 "bucketkey2": "foo2", 161 "bucketkey3": "foo3", 162 } 163 if !testPutValues(tc, bucket, keyValues) { 164 return false 165 } 166 167 if !testGetValues(tc, bucket, keyValues) { 168 return false 169 } 170 171 // Iterate all of the keys using ForEach while making sure the 172 // stored values are the expected values. 173 keysFound := make(map[string]struct{}, len(keyValues)) 174 err := bucket.ForEach(func(k, v []byte) error { 175 ks := string(k) 176 wantV, ok := keyValues[ks] 177 if !ok { 178 return fmt.Errorf("ForEach: key '%s' should "+ 179 "exist", ks) 180 } 181 182 if !reflect.DeepEqual(v, []byte(wantV)) { 183 return fmt.Errorf("ForEach: value for key '%s' "+ 184 "does not match - got %s, want %s", 185 ks, v, wantV) 186 } 187 188 keysFound[ks] = struct{}{} 189 return nil 190 }) 191 if err != nil { 192 tc.t.Errorf("%v", err) 193 return false 194 } 195 196 // Ensure all keys were iterated. 197 for k := range keyValues { 198 if _, ok := keysFound[k]; !ok { 199 tc.t.Errorf("ForEach: key '%s' was not iterated "+ 200 "when it should have been", k) 201 return false 202 } 203 } 204 205 // Delete the keys and ensure they were deleted. 206 if !testDeleteValues(tc, bucket, keyValues) { 207 return false 208 } 209 if !testGetValues(tc, bucket, rollbackValues(keyValues)) { 210 return false 211 } 212 213 // Test that the sequence methods work as expected. 214 if !testSequence(tc, bucket) { 215 return false 216 } 217 218 // Ensure creating a new bucket works as expected. 219 testBucketName := []byte("testbucket") 220 testBucket, err := bucket.CreateBucket(testBucketName) 221 if err != nil { 222 tc.t.Errorf("CreateBucket: unexpected error: %v", err) 223 return false 224 } 225 if !testNestedReadWriteBucket(tc, testBucket) { 226 return false 227 } 228 229 // Ensure creating a bucket that already exists fails with the 230 // expected error. 231 wantErr := walletdb.ErrBucketExists 232 if _, err := bucket.CreateBucket(testBucketName); err != wantErr { 233 tc.t.Errorf("CreateBucket: unexpected error - got %v, "+ 234 "want %v", err, wantErr) 235 return false 236 } 237 238 // Ensure CreateBucketIfNotExists returns an existing bucket. 239 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) 240 if err != nil { 241 tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ 242 "error: %v", err) 243 return false 244 } 245 if !testNestedReadWriteBucket(tc, testBucket) { 246 return false 247 } 248 249 // Ensure retrieving and existing bucket works as expected. 250 testBucket = bucket.NestedReadWriteBucket(testBucketName) 251 if !testNestedReadWriteBucket(tc, testBucket) { 252 return false 253 } 254 255 // Ensure deleting a bucket works as intended. 256 if err := bucket.DeleteNestedBucket(testBucketName); err != nil { 257 tc.t.Errorf("DeleteNestedBucket: unexpected error: %v", err) 258 return false 259 } 260 if b := bucket.NestedReadWriteBucket(testBucketName); b != nil { 261 tc.t.Errorf("DeleteNestedBucket: bucket '%s' still exists", 262 testBucketName) 263 return false 264 } 265 266 // Ensure deleting a bucket that doesn't exist returns the 267 // expected error. 268 wantErr = walletdb.ErrBucketNotFound 269 if err := bucket.DeleteNestedBucket(testBucketName); err != wantErr { 270 tc.t.Errorf("DeleteNestedBucket: unexpected error - got %v, "+ 271 "want %v", err, wantErr) 272 return false 273 } 274 275 // Ensure CreateBucketIfNotExists creates a new bucket when 276 // it doesn't already exist. 277 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) 278 if err != nil { 279 tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ 280 "error: %v", err) 281 return false 282 } 283 if !testNestedReadWriteBucket(tc, testBucket) { 284 return false 285 } 286 287 // Delete the test bucket to avoid leaving it around for future 288 // calls. 289 if err := bucket.DeleteNestedBucket(testBucketName); err != nil { 290 tc.t.Errorf("DeleteNestedBucket: unexpected error: %v", err) 291 return false 292 } 293 if b := bucket.NestedReadWriteBucket(testBucketName); b != nil { 294 tc.t.Errorf("DeleteNestedBucket: bucket '%s' still exists", 295 testBucketName) 296 return false 297 } 298 return true 299 } 300 301 // testManualTxInterface ensures that manual transactions work as expected. 302 func testManualTxInterface(tc *testContext, bucketKey []byte) bool { 303 db := tc.db 304 305 // populateValues tests that populating values works as expected. 306 // 307 // When the writable flag is false, a read-only tranasction is created, 308 // standard bucket tests for read-only transactions are performed, and 309 // the Commit function is checked to ensure it fails as expected. 310 // 311 // Otherwise, a read-write transaction is created, the values are 312 // written, standard bucket tests for read-write transactions are 313 // performed, and then the transaction is either commited or rolled 314 // back depending on the flag. 315 populateValues := func(writable, rollback bool, putValues map[string]string) bool { 316 var dbtx walletdb.ReadTx 317 var rootBucket walletdb.ReadBucket 318 var err error 319 if writable { 320 dbtx, err = db.BeginReadWriteTx() 321 if err != nil { 322 tc.t.Errorf("BeginReadWriteTx: unexpected error %v", err) 323 return false 324 } 325 rootBucket = dbtx.(walletdb.ReadWriteTx).ReadWriteBucket(bucketKey) 326 } else { 327 dbtx, err = db.BeginReadTx() 328 if err != nil { 329 tc.t.Errorf("BeginReadTx: unexpected error %v", err) 330 return false 331 } 332 rootBucket = dbtx.ReadBucket(bucketKey) 333 } 334 if rootBucket == nil { 335 tc.t.Errorf("ReadWriteBucket/ReadBucket: unexpected nil root bucket") 336 _ = dbtx.Rollback() 337 return false 338 } 339 340 if writable { 341 tc.isWritable = writable 342 if !testReadWriteBucketInterface(tc, rootBucket.(walletdb.ReadWriteBucket)) { 343 _ = dbtx.Rollback() 344 return false 345 } 346 } 347 348 if !writable { 349 // Rollback the transaction. 350 if err := dbtx.Rollback(); err != nil { 351 tc.t.Errorf("Commit: unexpected error %v", err) 352 return false 353 } 354 } else { 355 rootBucket := rootBucket.(walletdb.ReadWriteBucket) 356 if !testPutValues(tc, rootBucket, putValues) { 357 return false 358 } 359 360 if rollback { 361 // Rollback the transaction. 362 if err := dbtx.Rollback(); err != nil { 363 tc.t.Errorf("Rollback: unexpected "+ 364 "error %v", err) 365 return false 366 } 367 } else { 368 // The commit should succeed. 369 if err := dbtx.(walletdb.ReadWriteTx).Commit(); err != nil { 370 tc.t.Errorf("Commit: unexpected error "+ 371 "%v", err) 372 return false 373 } 374 } 375 } 376 377 return true 378 } 379 380 // checkValues starts a read-only transaction and checks that all of 381 // the key/value pairs specified in the expectedValues parameter match 382 // what's in the database. 383 checkValues := func(expectedValues map[string]string) bool { 384 // Begin another read-only transaction to ensure... 385 dbtx, err := db.BeginReadTx() 386 if err != nil { 387 tc.t.Errorf("BeginReadTx: unexpected error %v", err) 388 return false 389 } 390 391 rootBucket := dbtx.ReadBucket(bucketKey) 392 if rootBucket == nil { 393 tc.t.Errorf("ReadBucket: unexpected nil root bucket") 394 _ = dbtx.Rollback() 395 return false 396 } 397 398 if !testGetValues(tc, rootBucket, expectedValues) { 399 _ = dbtx.Rollback() 400 return false 401 } 402 403 // Rollback the read-only transaction. 404 if err := dbtx.Rollback(); err != nil { 405 tc.t.Errorf("Commit: unexpected error %v", err) 406 return false 407 } 408 409 return true 410 } 411 412 // deleteValues starts a read-write transaction and deletes the keys 413 // in the passed key/value pairs. 414 deleteValues := func(values map[string]string) bool { 415 dbtx, err := db.BeginReadWriteTx() 416 if err != nil { 417 tc.t.Errorf("BeginReadWriteTx: unexpected error %v", err) 418 _ = dbtx.Rollback() 419 return false 420 } 421 422 rootBucket := dbtx.ReadWriteBucket(bucketKey) 423 if rootBucket == nil { 424 tc.t.Errorf("RootBucket: unexpected nil root bucket") 425 _ = dbtx.Rollback() 426 return false 427 } 428 429 // Delete the keys and ensure they were deleted. 430 if !testDeleteValues(tc, rootBucket, values) { 431 _ = dbtx.Rollback() 432 return false 433 } 434 if !testGetValues(tc, rootBucket, rollbackValues(values)) { 435 _ = dbtx.Rollback() 436 return false 437 } 438 439 // Commit the changes and ensure it was successful. 440 if err := dbtx.Commit(); err != nil { 441 tc.t.Errorf("Commit: unexpected error %v", err) 442 return false 443 } 444 445 return true 446 } 447 448 // keyValues holds the keys and values to use when putting values 449 // into a bucket. 450 var keyValues = map[string]string{ 451 "umtxkey1": "foo1", 452 "umtxkey2": "foo2", 453 "umtxkey3": "foo3", 454 } 455 456 // Ensure that attempting populating the values using a read-only 457 // transaction fails as expected. 458 if !populateValues(false, true, keyValues) { 459 return false 460 } 461 if !checkValues(rollbackValues(keyValues)) { 462 return false 463 } 464 465 // Ensure that attempting populating the values using a read-write 466 // transaction and then rolling it back yields the expected values. 467 if !populateValues(true, true, keyValues) { 468 return false 469 } 470 if !checkValues(rollbackValues(keyValues)) { 471 return false 472 } 473 474 // Ensure that attempting populating the values using a read-write 475 // transaction and then committing it stores the expected values. 476 if !populateValues(true, false, keyValues) { 477 return false 478 } 479 if !checkValues(keyValues) { 480 return false 481 } 482 483 // Clean up the keys. 484 if !deleteValues(keyValues) { 485 return false 486 } 487 488 return true 489 } 490 491 // testNamespaceAndTxInterfaces creates a namespace using the provided key and 492 // tests all facets of it interface as well as transaction and bucket 493 // interfaces under it. 494 func testNamespaceAndTxInterfaces(tc *testContext, namespaceKey string) bool { 495 namespaceKeyBytes := []byte(namespaceKey) 496 err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { 497 _, err := tx.CreateTopLevelBucket(namespaceKeyBytes) 498 return err 499 }) 500 if err != nil { 501 tc.t.Errorf("CreateTopLevelBucket: unexpected error: %v", err) 502 return false 503 } 504 defer func() { 505 // Remove the namespace now that the tests are done for it. 506 err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { 507 return tx.DeleteTopLevelBucket(namespaceKeyBytes) 508 }) 509 if err != nil { 510 tc.t.Errorf("DeleteTopLevelBucket: unexpected error: %v", err) 511 return 512 } 513 }() 514 515 if !testManualTxInterface(tc, namespaceKeyBytes) { 516 return false 517 } 518 519 // keyValues holds the keys and values to use when putting values 520 // into a bucket. 521 var keyValues = map[string]string{ 522 "mtxkey1": "foo1", 523 "mtxkey2": "foo2", 524 "mtxkey3": "foo3", 525 } 526 527 // Test the bucket interface via a managed read-only transaction. 528 err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error { 529 rootBucket := tx.ReadBucket(namespaceKeyBytes) 530 if rootBucket == nil { 531 return fmt.Errorf("ReadBucket: unexpected nil root bucket") 532 } 533 534 return nil 535 }) 536 if err != nil { 537 if err != errSubTestFail { 538 tc.t.Errorf("%v", err) 539 } 540 return false 541 } 542 543 // Test that we can read the top level buckets. 544 var topLevelBuckets []string 545 walletdb.View(tc.db, func(tx walletdb.ReadTx) error { 546 return tx.ForEachBucket(func(key []byte) error { 547 topLevelBuckets = append(topLevelBuckets, string(key)) 548 return nil 549 }) 550 }) 551 if err != nil { 552 if err != errSubTestFail { 553 tc.t.Errorf("%v", err) 554 } 555 return false 556 } 557 558 if len(topLevelBuckets) != 1 { 559 tc.t.Errorf("ForEachBucket: expected only one top level bucket") 560 return false 561 } 562 if topLevelBuckets[0] != namespaceKey { 563 tc.t.Errorf("ForEachBucket: expected %v, got %v", namespaceKey, 564 topLevelBuckets[0]) 565 return false 566 } 567 568 // Test the bucket interface via a managed read-write transaction. 569 // Also, put a series of values and force a rollback so the following 570 // code can ensure the values were not stored. 571 forceRollbackError := fmt.Errorf("force rollback") 572 err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { 573 rootBucket := tx.ReadWriteBucket(namespaceKeyBytes) 574 if rootBucket == nil { 575 return fmt.Errorf("ReadWriteBucket: unexpected nil root bucket") 576 } 577 578 tc.isWritable = true 579 if !testReadWriteBucketInterface(tc, rootBucket) { 580 return errSubTestFail 581 } 582 583 if !testPutValues(tc, rootBucket, keyValues) { 584 return errSubTestFail 585 } 586 587 // Return an error to force a rollback. 588 return forceRollbackError 589 }) 590 if err != forceRollbackError { 591 if err == errSubTestFail { 592 return false 593 } 594 595 tc.t.Errorf("Update: inner function error not returned - got "+ 596 "%v, want %v", err, forceRollbackError) 597 return false 598 } 599 600 // Ensure the values that should have not been stored due to the forced 601 // rollback above were not actually stored. 602 err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error { 603 rootBucket := tx.ReadBucket(namespaceKeyBytes) 604 if rootBucket == nil { 605 return fmt.Errorf("ReadBucket: unexpected nil root bucket") 606 } 607 608 if !testGetValues(tc, rootBucket, rollbackValues(keyValues)) { 609 return errSubTestFail 610 } 611 612 return nil 613 }) 614 if err != nil { 615 if err != errSubTestFail { 616 tc.t.Errorf("%v", err) 617 } 618 return false 619 } 620 621 // Store a series of values via a managed read-write transaction. 622 err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { 623 rootBucket := tx.ReadWriteBucket(namespaceKeyBytes) 624 if rootBucket == nil { 625 return fmt.Errorf("ReadWriteBucket: unexpected nil root bucket") 626 } 627 628 if !testPutValues(tc, rootBucket, keyValues) { 629 return errSubTestFail 630 } 631 632 return nil 633 }) 634 if err != nil { 635 if err != errSubTestFail { 636 tc.t.Errorf("%v", err) 637 } 638 return false 639 } 640 641 // Ensure the values stored above were committed as expected. 642 err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error { 643 rootBucket := tx.ReadBucket(namespaceKeyBytes) 644 if rootBucket == nil { 645 return fmt.Errorf("ReadBucket: unexpected nil root bucket") 646 } 647 648 if !testGetValues(tc, rootBucket, keyValues) { 649 return errSubTestFail 650 } 651 652 return nil 653 }) 654 if err != nil { 655 if err != errSubTestFail { 656 tc.t.Errorf("%v", err) 657 } 658 return false 659 } 660 661 // Clean up the values stored above in a managed read-write transaction. 662 err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { 663 rootBucket := tx.ReadWriteBucket(namespaceKeyBytes) 664 if rootBucket == nil { 665 return fmt.Errorf("ReadWriteBucket: unexpected nil root bucket") 666 } 667 668 if !testDeleteValues(tc, rootBucket, keyValues) { 669 return errSubTestFail 670 } 671 672 return nil 673 }) 674 if err != nil { 675 if err != errSubTestFail { 676 tc.t.Errorf("%v", err) 677 } 678 return false 679 } 680 681 return true 682 } 683 684 // testAdditionalErrors performs some tests for error cases not covered 685 // elsewhere in the tests and therefore improves negative test coverage. 686 func testAdditionalErrors(tc *testContext) bool { 687 ns3Key := []byte("ns3") 688 689 err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { 690 // Create a new namespace 691 rootBucket, err := tx.CreateTopLevelBucket(ns3Key) 692 if err != nil { 693 return fmt.Errorf("CreateTopLevelBucket: unexpected error: %w", err) 694 } 695 696 // Ensure CreateBucket returns the expected error when no bucket 697 // key is specified. 698 wantErr := walletdb.ErrBucketNameRequired 699 if _, err := rootBucket.CreateBucket(nil); err != wantErr { 700 return fmt.Errorf("CreateBucket: unexpected error - "+ 701 "got %v, want %v", err, wantErr) 702 } 703 704 // Ensure DeleteNestedBucket returns the expected error when no bucket 705 // key is specified. 706 wantErr = walletdb.ErrIncompatibleValue 707 if err := rootBucket.DeleteNestedBucket(nil); err != wantErr { 708 return fmt.Errorf("DeleteNestedBucket: unexpected error - "+ 709 "got %v, want %v", err, wantErr) 710 } 711 712 // Ensure Put returns the expected error when no key is 713 // specified. 714 wantErr = walletdb.ErrKeyRequired 715 if err := rootBucket.Put(nil, nil); err != wantErr { 716 return fmt.Errorf("Put: unexpected error - got %v, "+ 717 "want %v", err, wantErr) 718 } 719 720 return nil 721 }) 722 if err != nil { 723 if err != errSubTestFail { 724 tc.t.Errorf("%v", err) 725 } 726 return false 727 } 728 729 // Ensure that attempting to rollback or commit a transaction that is 730 // already closed returns the expected error. 731 tx, err := tc.db.BeginReadWriteTx() 732 if err != nil { 733 tc.t.Errorf("Begin: unexpected error: %v", err) 734 return false 735 } 736 if err := tx.Rollback(); err != nil { 737 tc.t.Errorf("Rollback: unexpected error: %v", err) 738 return false 739 } 740 wantErr := walletdb.ErrTxClosed 741 if err := tx.Rollback(); err != wantErr { 742 tc.t.Errorf("Rollback: unexpected error - got %v, want %v", err, 743 wantErr) 744 return false 745 } 746 if err := tx.Commit(); err != wantErr { 747 tc.t.Errorf("Commit: unexpected error - got %v, want %v", err, 748 wantErr) 749 return false 750 } 751 752 return true 753 } 754 755 // testBatchInterface tests that if the target database implements the batch 756 // method, then the method functions as expected. 757 func testBatchInterface(tc *testContext) bool { 758 // If the database doesn't support the batch super-set of the 759 // interface, then we're done here. 760 batchDB, ok := tc.db.(walletdb.BatchDB) 761 if !ok { 762 return true 763 } 764 765 const numGoroutines = 5 766 errChan := make(chan error, numGoroutines) 767 768 var wg sync.WaitGroup 769 for i := 0; i < numGoroutines; i++ { 770 wg.Add(1) 771 go func(i int) { 772 defer wg.Done() 773 err := walletdb.Batch(batchDB, func(tx walletdb.ReadWriteTx) error { 774 b, err := tx.CreateTopLevelBucket([]byte("test")) 775 if err != nil { 776 return err 777 } 778 779 byteI := []byte{byte(i)} 780 return b.Put(byteI, byteI) 781 }) 782 errChan <- err 783 }(i) 784 } 785 786 wg.Wait() 787 close(errChan) 788 789 for err := range errChan { 790 if err != nil { 791 tc.t.Errorf("Batch: unexpected error: %v", err) 792 return false 793 } 794 } 795 796 err := walletdb.View(batchDB, func(tx walletdb.ReadTx) error { 797 b := tx.ReadBucket([]byte("test")) 798 799 for i := 0; i < numGoroutines; i++ { 800 byteI := []byte{byte(i)} 801 if v := b.Get(byteI); v == nil { 802 return fmt.Errorf("key %v not present", byteI) 803 } else if !bytes.Equal(v, byteI) { 804 return fmt.Errorf("key %v not equal to value: "+ 805 "%v", byteI, v) 806 } 807 } 808 809 return nil 810 }) 811 if err != nil { 812 tc.t.Errorf("Batch: unexpected error: %v", err) 813 return false 814 } 815 816 return true 817 } 818 819 // TestInterface performs all interfaces tests for this database driver. 820 func TestInterface(t Tester, dbType string, args ...interface{}) { 821 db, err := walletdb.Create(dbType, args...) 822 if err != nil { 823 t.Errorf("Failed to create test database (%s) %v", dbType, err) 824 return 825 } 826 defer db.Close() 827 828 // Run all of the interface tests against the database. 829 // Create a test context to pass around. 830 context := testContext{t: t, db: db} 831 832 // Create a namespace and test the interface for it. 833 if !testNamespaceAndTxInterfaces(&context, "ns1") { 834 return 835 } 836 837 // Create a second namespace and test the interface for it. 838 if !testNamespaceAndTxInterfaces(&context, "ns2") { 839 return 840 } 841 842 // Check a few more error conditions not covered elsewhere. 843 if !testAdditionalErrors(&context) { 844 return 845 } 846 847 // If applicable, also test the behavior of the Batch call. 848 if !testBatchInterface(&context) { 849 return 850 } 851 }