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