decred.org/dcrwallet/v3@v3.1.0/wallet/internal/bdb/interface_test.go (about) 1 // Copyright (c) 2014 The btcsuite developers 2 // Copyright (c) 2015 The Decred 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. See the bdb backend driver for a working example. 10 // 11 // NOTE: When copying this file into the backend driver folder, the package name 12 // will need to be changed accordingly. 13 14 // Test must be updated for API changes. 15 16 package bdb_test 17 18 import ( 19 "bytes" 20 "context" 21 "fmt" 22 "os" 23 "testing" 24 25 "decred.org/dcrwallet/v3/errors" 26 "decred.org/dcrwallet/v3/wallet/walletdb" 27 ) 28 29 // errSubTestFail is used to signal that a sub test returned false. 30 var errSubTestFail = errors.Errorf("sub test failure") 31 32 // testContext is used to store context information about a running test which 33 // is passed into helper functions. 34 type testContext struct { 35 t *testing.T 36 db walletdb.DB 37 bucketDepth int 38 isWritable bool 39 } 40 41 // rollbackValues returns a copy of the provided map with all values set to an 42 // empty string. This is used to test that values are properly rolled back. 43 func rollbackValues(values map[string]string) map[string]string { 44 retMap := make(map[string]string, len(values)) 45 for k := range values { 46 retMap[k] = "" 47 } 48 return retMap 49 } 50 51 // testGetValues checks that all of the provided key/value pairs can be 52 // retrieved from the database and the retrieved values match the provided 53 // values. 54 func testGetValues(tc *testContext, bucket walletdb.ReadBucket, values map[string]string) bool { 55 for k, v := range values { 56 var vBytes []byte 57 if v != "" { 58 vBytes = []byte(v) 59 } 60 61 gotValue := bucket.Get([]byte(k)) 62 if !bytes.Equal(gotValue, vBytes) { 63 tc.t.Errorf("Get: unexpected value - got %s, want %s", 64 gotValue, vBytes) 65 return false 66 } 67 } 68 69 return true 70 } 71 72 // testPutValues stores all of the provided key/value pairs in the provided 73 // bucket while checking for errors. 74 func testPutValues(tc *testContext, bucket walletdb.ReadWriteBucket, values map[string]string) bool { 75 for k, v := range values { 76 var vBytes []byte 77 if v != "" { 78 vBytes = []byte(v) 79 } 80 if err := bucket.Put([]byte(k), vBytes); err != nil { 81 tc.t.Errorf("Put: unexpected error: %v", err) 82 return false 83 } 84 } 85 86 return true 87 } 88 89 // testDeleteValues removes all of the provided key/value pairs from the 90 // provided bucket. 91 func testDeleteValues(tc *testContext, bucket walletdb.ReadWriteBucket, values map[string]string) bool { 92 for k := range values { 93 if err := bucket.Delete([]byte(k)); err != nil { 94 tc.t.Errorf("Delete: unexpected error: %v", err) 95 return false 96 } 97 } 98 99 return true 100 } 101 102 // testNestedReadWriteBucket reruns the testReadWriteBucketInterface against a 103 // nested bucket along with a counter to only test a couple of level deep. 104 func testNestedReadWriteBucket(tc *testContext, testBucket walletdb.ReadWriteBucket) bool { 105 // Don't go more than 2 nested level deep. 106 if tc.bucketDepth > 1 { 107 return true 108 } 109 110 tc.bucketDepth++ 111 defer func() { 112 tc.bucketDepth-- 113 }() 114 115 return testReadWriteBucketInterface(tc, testBucket) 116 } 117 118 // testReadWriteBucketInterface ensures the bucket interface is working 119 // properly by exercising all of its functions. 120 func testReadWriteBucketInterface(tc *testContext, bucket walletdb.ReadWriteBucket) bool { 121 // keyValues holds the keys and values to use when putting 122 // values into the bucket. 123 var keyValues = map[string]string{ 124 "bucketkey1": "foo1", 125 "bucketkey2": "foo2", 126 "bucketkey3": "foo3", 127 } 128 if !testPutValues(tc, bucket, keyValues) { 129 return false 130 } 131 132 if !testGetValues(tc, bucket, keyValues) { 133 return false 134 } 135 136 // Iterate all of the keys using ForEach while making sure the 137 // stored values are the expected values. 138 keysFound := make(map[string]struct{}, len(keyValues)) 139 err := bucket.ForEach(func(k, v []byte) error { 140 kString := string(k) 141 wantV, ok := keyValues[kString] 142 if !ok { 143 return errors.Errorf("ForEach: key '%s' should "+ 144 "exist", kString) 145 } 146 147 if !bytes.Equal(v, []byte(wantV)) { 148 return errors.Errorf("ForEach: value for key '%s' "+ 149 "does not match - got %s, want %s", 150 kString, v, wantV) 151 } 152 153 keysFound[kString] = struct{}{} 154 return nil 155 }) 156 if err != nil { 157 tc.t.Errorf("%v", err) 158 return false 159 } 160 161 // Ensure all keys were iterated. 162 for k := range keyValues { 163 if _, ok := keysFound[k]; !ok { 164 tc.t.Errorf("ForEach: key '%s' was not iterated "+ 165 "when it should have been", k) 166 return false 167 } 168 } 169 170 // Delete the keys and ensure they were deleted. 171 if !testDeleteValues(tc, bucket, keyValues) { 172 return false 173 } 174 if !testGetValues(tc, bucket, rollbackValues(keyValues)) { 175 return false 176 } 177 178 // Ensure creating a new bucket works as expected. 179 testBucketName := []byte("testbucket") 180 testBucket, err := bucket.CreateBucket(testBucketName) 181 if err != nil { 182 tc.t.Errorf("CreateBucket: unexpected error: %v", err) 183 return false 184 } 185 if !testNestedReadWriteBucket(tc, testBucket) { 186 return false 187 } 188 189 // Ensure creating a bucket that already exists fails with the 190 // expected error. 191 if _, err := bucket.CreateBucket(testBucketName); !errors.Is(err, errors.Exist) { 192 tc.t.Errorf("CreateBucket: unexpected error: %v", err) 193 return false 194 } 195 196 // Ensure CreateBucketIfNotExists returns an existing bucket. 197 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) 198 if err != nil { 199 tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ 200 "error: %v", err) 201 return false 202 } 203 if !testNestedReadWriteBucket(tc, testBucket) { 204 return false 205 } 206 207 // Ensure retrieving and existing bucket works as expected. 208 testBucket = bucket.NestedReadWriteBucket(testBucketName) 209 if !testNestedReadWriteBucket(tc, testBucket) { 210 return false 211 } 212 213 // Ensure deleting a bucket works as intended. 214 if err := bucket.DeleteNestedBucket(testBucketName); err != nil { 215 tc.t.Errorf("DeleteBucket: unexpected error: %v", err) 216 return false 217 } 218 if b := bucket.NestedReadWriteBucket(testBucketName); b != nil { 219 tc.t.Errorf("DeleteBucket: bucket '%s' still exists", 220 testBucketName) 221 return false 222 } 223 224 // Ensure deleting a bucket that doesn't exist returns the 225 // expected error. 226 if err := bucket.DeleteNestedBucket(testBucketName); !errors.Is(err, errors.NotExist) { 227 tc.t.Errorf("DeleteBucket: unexpected error: %v", err) 228 return false 229 } 230 231 // Ensure CreateBucketIfNotExists creates a new bucket when 232 // it doesn't already exist. 233 testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) 234 if err != nil { 235 tc.t.Errorf("CreateBucketIfNotExists: unexpected error: %v", err) 236 return false 237 } 238 if !testNestedReadWriteBucket(tc, testBucket) { 239 return false 240 } 241 242 // Delete the test bucket to avoid leaving it around for future 243 // calls. 244 if err := bucket.DeleteNestedBucket(testBucketName); err != nil { 245 tc.t.Errorf("DeleteBucket: unexpected error: %v", err) 246 return false 247 } 248 if b := bucket.NestedReadWriteBucket(testBucketName); b != nil { 249 tc.t.Errorf("DeleteBucket: bucket '%s' still exists", 250 testBucketName) 251 return false 252 } 253 254 return true 255 } 256 257 // testManualTxInterface ensures that manual transactions work as expected. 258 func testManualTxInterface(tc *testContext, bucketKey []byte) bool { 259 db := tc.db 260 261 // populateValues tests that populating values works as expected. 262 // 263 // When the writable flag is false, a read-only tranasction is created, 264 // standard bucket tests for read-only transactions are performed, and 265 // the Commit function is checked to ensure it fails as expected. 266 // 267 // Otherwise, a read-write transaction is created, the values are 268 // written, standard bucket tests for read-write transactions are 269 // performed, and then the transaction is either committed or rolled 270 // back depending on the flag. 271 populateValues := func(writable, rollback bool, putValues map[string]string) bool { 272 var dbtx walletdb.ReadTx 273 var rootBucket walletdb.ReadBucket 274 var err error 275 if writable { 276 dbtx, err = db.BeginReadWriteTx() 277 if err != nil { 278 tc.t.Errorf("BeginReadWriteTx: unexpected error %v", err) 279 return false 280 } 281 rootBucket = dbtx.(walletdb.ReadWriteTx).ReadWriteBucket(bucketKey) 282 } else { 283 dbtx, err = db.BeginReadTx() 284 if err != nil { 285 tc.t.Errorf("BeginReadTx: unexpected error %v", err) 286 return false 287 } 288 rootBucket = dbtx.ReadBucket(bucketKey) 289 } 290 if rootBucket == nil { 291 tc.t.Errorf("ReadWriteBucket/ReadBucket: unexpected nil root bucket") 292 _ = dbtx.Rollback() 293 return false 294 } 295 296 if writable { 297 tc.isWritable = writable 298 if !testReadWriteBucketInterface(tc, rootBucket.(walletdb.ReadWriteBucket)) { 299 _ = dbtx.Rollback() 300 return false 301 } 302 } 303 304 if !writable { 305 // Rollback the transaction. 306 if err := dbtx.Rollback(); err != nil { 307 tc.t.Errorf("Commit: unexpected error %v", err) 308 return false 309 } 310 } else { 311 rootBucket := rootBucket.(walletdb.ReadWriteBucket) 312 if !testPutValues(tc, rootBucket, putValues) { 313 return false 314 } 315 316 if rollback { 317 // Rollback the transaction. 318 if err := dbtx.Rollback(); err != nil { 319 tc.t.Errorf("Rollback: unexpected "+ 320 "error %v", err) 321 return false 322 } 323 } else { 324 // The commit should succeed. 325 if err := dbtx.(walletdb.ReadWriteTx).Commit(); err != nil { 326 tc.t.Errorf("Commit: unexpected error "+ 327 "%v", err) 328 return false 329 } 330 } 331 } 332 333 return true 334 } 335 336 // checkValues starts a read-only transaction and checks that all of 337 // the key/value pairs specified in the expectedValues parameter match 338 // what's in the database. 339 checkValues := func(expectedValues map[string]string) bool { 340 // Begin another read-only transaction to ensure... 341 dbtx, err := db.BeginReadTx() 342 if err != nil { 343 tc.t.Errorf("BeginReadTx: unexpected error %v", err) 344 return false 345 } 346 347 rootBucket := dbtx.ReadBucket(bucketKey) 348 if rootBucket == nil { 349 tc.t.Errorf("ReadBucket: unexpected nil root bucket") 350 _ = dbtx.Rollback() 351 return false 352 } 353 354 if !testGetValues(tc, rootBucket, expectedValues) { 355 _ = dbtx.Rollback() 356 return false 357 } 358 359 // Rollback the read-only transaction. 360 if err := dbtx.Rollback(); err != nil { 361 tc.t.Errorf("Commit: unexpected error %v", err) 362 return false 363 } 364 365 return true 366 } 367 368 // deleteValues starts a read-write transaction and deletes the keys 369 // in the passed key/value pairs. 370 deleteValues := func(values map[string]string) bool { 371 dbtx, err := db.BeginReadWriteTx() 372 if err != nil { 373 tc.t.Errorf("BeginReadWriteTx: unexpected error %v", err) 374 _ = dbtx.Rollback() 375 return false 376 } 377 378 rootBucket := dbtx.ReadWriteBucket(bucketKey) 379 if rootBucket == nil { 380 tc.t.Errorf("RootBucket: unexpected nil root bucket") 381 _ = dbtx.Rollback() 382 return false 383 } 384 385 // Delete the keys and ensure they were deleted. 386 if !testDeleteValues(tc, rootBucket, values) { 387 _ = dbtx.Rollback() 388 return false 389 } 390 if !testGetValues(tc, rootBucket, rollbackValues(values)) { 391 _ = dbtx.Rollback() 392 return false 393 } 394 395 // Commit the changes and ensure it was successful. 396 if err := dbtx.Commit(); err != nil { 397 tc.t.Errorf("Commit: unexpected error %v", err) 398 return false 399 } 400 401 return true 402 } 403 404 // keyValues holds the keys and values to use when putting values 405 // into a bucket. 406 var keyValues = map[string]string{ 407 "umtxkey1": "foo1", 408 "umtxkey2": "foo2", 409 "umtxkey3": "foo3", 410 } 411 412 // Ensure that attempting populating the values using a read-only 413 // transaction fails as expected. 414 if !populateValues(false, true, keyValues) { 415 return false 416 } 417 if !checkValues(rollbackValues(keyValues)) { 418 return false 419 } 420 421 // Ensure that attempting populating the values using a read-write 422 // transaction and then rolling it back yields the expected values. 423 if !populateValues(true, true, keyValues) { 424 return false 425 } 426 if !checkValues(rollbackValues(keyValues)) { 427 return false 428 } 429 430 // Ensure that attempting populating the values using a read-write 431 // transaction and then committing it stores the expected values. 432 if !populateValues(true, false, keyValues) { 433 return false 434 } 435 if !checkValues(keyValues) { 436 return false 437 } 438 439 // Clean up the keys. 440 if !deleteValues(keyValues) { 441 return false 442 } 443 444 return true 445 } 446 447 // testNamespaceAndTxInterfaces creates a namespace using the provided key and 448 // tests all facets of it interface as well as transaction and bucket 449 // interfaces under it. 450 func testNamespaceAndTxInterfaces(tc *testContext, namespaceKey string) bool { 451 ctx := context.Background() 452 namespaceKeyBytes := []byte(namespaceKey) 453 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 454 _, err := tx.CreateTopLevelBucket(namespaceKeyBytes) 455 return err 456 }) 457 if err != nil { 458 tc.t.Errorf("CreateTopLevelBucket: unexpected error: %v", err) 459 return false 460 } 461 defer func() { 462 // Remove the namespace now that the tests are done for it. 463 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 464 return tx.DeleteTopLevelBucket(namespaceKeyBytes) 465 }) 466 if err != nil { 467 tc.t.Errorf("DeleteTopLevelBucket: unexpected error: %v", err) 468 return 469 } 470 }() 471 472 if !testManualTxInterface(tc, namespaceKeyBytes) { 473 return false 474 } 475 476 // keyValues holds the keys and values to use when putting values 477 // into a bucket. 478 var keyValues = map[string]string{ 479 "mtxkey1": "foo1", 480 "mtxkey2": "foo2", 481 "mtxkey3": "foo3", 482 } 483 484 // Test the bucket interface via a managed read-only transaction. 485 err = walletdb.View(ctx, tc.db, func(tx walletdb.ReadTx) error { 486 rootBucket := tx.ReadBucket(namespaceKeyBytes) 487 if rootBucket == nil { 488 return fmt.Errorf("ReadBucket: unexpected nil root bucket") 489 } 490 491 return nil 492 }) 493 if err != nil { 494 if !errors.Is(err, errSubTestFail) { 495 tc.t.Errorf("%v", err) 496 } 497 return false 498 } 499 500 // Test the bucket interface via a managed read-write transaction. 501 // Also, put a series of values and force a rollback so the following 502 // code can ensure the values were not stored. 503 forceRollbackError := fmt.Errorf("force rollback") 504 err = walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 505 rootBucket := tx.ReadWriteBucket(namespaceKeyBytes) 506 if rootBucket == nil { 507 return fmt.Errorf("ReadWriteBucket: unexpected nil root bucket") 508 } 509 510 tc.isWritable = true 511 if !testReadWriteBucketInterface(tc, rootBucket) { 512 return errSubTestFail 513 } 514 515 if !testPutValues(tc, rootBucket, keyValues) { 516 return errSubTestFail 517 } 518 519 // Return an error to force a rollback. 520 return forceRollbackError 521 }) 522 if !errors.Is(err, forceRollbackError) { 523 if errors.Is(err, errSubTestFail) { 524 return false 525 } 526 527 tc.t.Errorf("Update: inner function error not returned - got "+ 528 "%v, want %v", err, forceRollbackError) 529 return false 530 } 531 532 // Ensure the values that should have not been stored due to the forced 533 // rollback above were not actually stored. 534 err = walletdb.View(ctx, tc.db, func(tx walletdb.ReadTx) error { 535 rootBucket := tx.ReadBucket(namespaceKeyBytes) 536 if rootBucket == nil { 537 return fmt.Errorf("ReadBucket: unexpected nil root bucket") 538 } 539 540 if !testGetValues(tc, rootBucket, rollbackValues(keyValues)) { 541 return errSubTestFail 542 } 543 544 return nil 545 }) 546 if err != nil { 547 if !errors.Is(err, errSubTestFail) { 548 tc.t.Errorf("%v", err) 549 } 550 return false 551 } 552 553 // Store a series of values via a managed read-write transaction. 554 err = walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 555 rootBucket := tx.ReadWriteBucket(namespaceKeyBytes) 556 if rootBucket == nil { 557 return fmt.Errorf("ReadWriteBucket: unexpected nil root bucket") 558 } 559 560 if !testPutValues(tc, rootBucket, keyValues) { 561 return errSubTestFail 562 } 563 564 return nil 565 }) 566 if err != nil { 567 if !errors.Is(err, errSubTestFail) { 568 tc.t.Errorf("%v", err) 569 } 570 return false 571 } 572 573 // Ensure the values stored above were committed as expected. 574 err = walletdb.View(ctx, tc.db, func(tx walletdb.ReadTx) error { 575 rootBucket := tx.ReadBucket(namespaceKeyBytes) 576 if rootBucket == nil { 577 return fmt.Errorf("ReadBucket: unexpected nil root bucket") 578 } 579 580 if !testGetValues(tc, rootBucket, keyValues) { 581 return errSubTestFail 582 } 583 584 return nil 585 }) 586 if err != nil { 587 if !errors.Is(err, errSubTestFail) { 588 tc.t.Errorf("%v", err) 589 } 590 return false 591 } 592 593 // Clean up the values stored above in a managed read-write transaction. 594 err = walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 595 rootBucket := tx.ReadWriteBucket(namespaceKeyBytes) 596 if rootBucket == nil { 597 return fmt.Errorf("ReadWriteBucket: unexpected nil root bucket") 598 } 599 600 if !testDeleteValues(tc, rootBucket, keyValues) { 601 return errSubTestFail 602 } 603 604 return nil 605 }) 606 if err != nil { 607 if !errors.Is(err, errSubTestFail) { 608 tc.t.Errorf("%v", err) 609 } 610 return false 611 } 612 613 return true 614 } 615 616 // testAdditionalErrors performs some tests for error cases not covered 617 // elsewhere in the tests and therefore improves negative test coverage. 618 func testAdditionalErrors(tc *testContext) bool { 619 ctx := context.Background() 620 ns3Key := []byte("ns3") 621 622 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 623 // Create a new namespace 624 rootBucket, err := tx.CreateTopLevelBucket(ns3Key) 625 if err != nil { 626 return fmt.Errorf("CreateTopLevelBucket: unexpected error: %v", err) 627 } 628 629 // Ensure CreateBucket returns the expected error when no bucket 630 // key is specified. 631 if _, err := rootBucket.CreateBucket(nil); !errors.Is(err, errors.Invalid) { 632 return fmt.Errorf("CreateBucket: unexpected error - "+ 633 "got %v, want %v", err, errors.Invalid) 634 } 635 636 // Ensure DeleteNestedBucket returns the expected error when no bucket 637 // key is specified. 638 if err := rootBucket.DeleteNestedBucket(nil); !errors.Is(err, errors.Invalid) { 639 return fmt.Errorf("DeleteNestedBucket: unexpected error - "+ 640 "got %v, want %v", err, errors.Invalid) 641 } 642 643 // Ensure Put returns the expected error when no key is 644 // specified. 645 if err := rootBucket.Put(nil, nil); !errors.Is(err, errors.Invalid) { 646 return fmt.Errorf("Put: unexpected error - got %v, "+ 647 "want %v", err, errors.Invalid) 648 } 649 650 return nil 651 }) 652 if err != nil { 653 if !errors.Is(err, errSubTestFail) { 654 tc.t.Errorf("%v", err) 655 } 656 return false 657 } 658 659 // Ensure that attempting to rollback or commit a transaction that is 660 // already closed returns the expected error. 661 tx, err := tc.db.BeginReadWriteTx() 662 if err != nil { 663 tc.t.Errorf("Begin: unexpected error: %v", err) 664 return false 665 } 666 if err := tx.Rollback(); err != nil { 667 tc.t.Errorf("Rollback: unexpected error: %v", err) 668 return false 669 } 670 if err := tx.Rollback(); !errors.Is(err, errors.Invalid) { 671 tc.t.Errorf("Rollback: unexpected error - got %v, want %v", err, 672 errors.Invalid) 673 return false 674 } 675 if err := tx.Commit(); !errors.Is(err, errors.Invalid) { 676 tc.t.Errorf("Commit: unexpected error - got %v, want %v", err, 677 errors.Invalid) 678 return false 679 } 680 681 return true 682 } 683 684 // testInterface tests performs tests for the various interfaces of walletdb 685 // which require state in the database for the given database type. 686 func testInterface(t *testing.T, db walletdb.DB) { 687 // Create a test context to pass around. 688 context := testContext{t: t, db: db} 689 690 // Create a namespace and test the interface for it. 691 if !testNamespaceAndTxInterfaces(&context, "ns1") { 692 return 693 } 694 695 // Create a second namespace and test the interface for it. 696 if !testNamespaceAndTxInterfaces(&context, "ns2") { 697 return 698 } 699 700 // Check a few more error conditions not covered elsewhere. 701 if !testAdditionalErrors(&context) { 702 return 703 } 704 } 705 706 // TestInterface performs all interfaces tests for this database driver. 707 func TestInterface(t *testing.T) { 708 // Create a new database to run tests against. 709 dbPath := "interfacetest.db" 710 db, err := walletdb.Create(dbType, dbPath) 711 if err != nil { 712 t.Errorf("Failed to create test database (%s) %v", dbType, err) 713 return 714 } 715 defer os.Remove(dbPath) 716 defer db.Close() 717 718 // Run all of the interface tests against the database. 719 testInterface(t, db) 720 }