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  }