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  }