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  }