github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/database/ffldb/whitebox_test.go (about)

     1  // Copyright (c) 2015-2016 The btcsuite developers
     2  // Copyright (c) 2016 The Dash 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 is part of the ffldb package rather than the ffldb_test package as
     7  // it provides whitebox testing.
     8  
     9  package ffldb
    10  
    11  import (
    12  	"compress/bzip2"
    13  	"encoding/binary"
    14  	"fmt"
    15  	"hash/crc32"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  	"testing"
    20  
    21  	"github.com/btcsuite/goleveldb/leveldb"
    22  	ldberrors "github.com/btcsuite/goleveldb/leveldb/errors"
    23  	"github.com/dashpay/godash/chaincfg"
    24  	"github.com/dashpay/godash/database"
    25  	"github.com/dashpay/godash/wire"
    26  	"github.com/dashpay/godashutil"
    27  )
    28  
    29  var (
    30  	// blockDataNet is the expected network in the test block data.
    31  	blockDataNet = wire.MainNet
    32  
    33  	// blockDataFile is the path to a file containing the first 256 blocks
    34  	// of the block chain.
    35  	blockDataFile = filepath.Join("..", "testdata", "blocks1-256.bz2")
    36  
    37  	// errSubTestFail is used to signal that a sub test returned false.
    38  	errSubTestFail = fmt.Errorf("sub test failure")
    39  )
    40  
    41  // loadBlocks loads the blocks contained in the testdata directory and returns
    42  // a slice of them.
    43  func loadBlocks(t *testing.T, dataFile string, network wire.BitcoinNet) ([]*godashutil.Block, error) {
    44  	// Open the file that contains the blocks for reading.
    45  	fi, err := os.Open(dataFile)
    46  	if err != nil {
    47  		t.Errorf("failed to open file %v, err %v", dataFile, err)
    48  		return nil, err
    49  	}
    50  	defer func() {
    51  		if err := fi.Close(); err != nil {
    52  			t.Errorf("failed to close file %v %v", dataFile,
    53  				err)
    54  		}
    55  	}()
    56  	dr := bzip2.NewReader(fi)
    57  
    58  	// Set the first block as the genesis block.
    59  	blocks := make([]*godashutil.Block, 0, 256)
    60  	genesis := godashutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
    61  	blocks = append(blocks, genesis)
    62  
    63  	// Load the remaining blocks.
    64  	for height := 1; ; height++ {
    65  		var net uint32
    66  		err := binary.Read(dr, binary.LittleEndian, &net)
    67  		if err == io.EOF {
    68  			// Hit end of file at the expected offset.  No error.
    69  			break
    70  		}
    71  		if err != nil {
    72  			t.Errorf("Failed to load network type for block %d: %v",
    73  				height, err)
    74  			return nil, err
    75  		}
    76  		if net != uint32(network) {
    77  			t.Errorf("Block doesn't match network: %v expects %v",
    78  				net, network)
    79  			return nil, err
    80  		}
    81  
    82  		var blockLen uint32
    83  		err = binary.Read(dr, binary.LittleEndian, &blockLen)
    84  		if err != nil {
    85  			t.Errorf("Failed to load block size for block %d: %v",
    86  				height, err)
    87  			return nil, err
    88  		}
    89  
    90  		// Read the block.
    91  		blockBytes := make([]byte, blockLen)
    92  		_, err = io.ReadFull(dr, blockBytes)
    93  		if err != nil {
    94  			t.Errorf("Failed to load block %d: %v", height, err)
    95  			return nil, err
    96  		}
    97  
    98  		// Deserialize and store the block.
    99  		block, err := godashutil.NewBlockFromBytes(blockBytes)
   100  		if err != nil {
   101  			t.Errorf("Failed to parse block %v: %v", height, err)
   102  			return nil, err
   103  		}
   104  		blocks = append(blocks, block)
   105  	}
   106  
   107  	return blocks, nil
   108  }
   109  
   110  // checkDbError ensures the passed error is a database.Error with an error code
   111  // that matches the passed  error code.
   112  func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool {
   113  	dbErr, ok := gotErr.(database.Error)
   114  	if !ok {
   115  		t.Errorf("%s: unexpected error type - got %T, want %T",
   116  			testName, gotErr, database.Error{})
   117  		return false
   118  	}
   119  	if dbErr.ErrorCode != wantErrCode {
   120  		t.Errorf("%s: unexpected error code - got %s (%s), want %s",
   121  			testName, dbErr.ErrorCode, dbErr.Description,
   122  			wantErrCode)
   123  		return false
   124  	}
   125  
   126  	return true
   127  }
   128  
   129  // testContext is used to store context information about a running test which
   130  // is passed into helper functions.
   131  type testContext struct {
   132  	t            *testing.T
   133  	db           database.DB
   134  	files        map[uint32]*lockableFile
   135  	maxFileSizes map[uint32]int64
   136  	blocks       []*godashutil.Block
   137  }
   138  
   139  // TestConvertErr ensures the leveldb error to database error conversion works
   140  // as expected.
   141  func TestConvertErr(t *testing.T) {
   142  	t.Parallel()
   143  
   144  	tests := []struct {
   145  		err         error
   146  		wantErrCode database.ErrorCode
   147  	}{
   148  		{&ldberrors.ErrCorrupted{}, database.ErrCorruption},
   149  		{leveldb.ErrClosed, database.ErrDbNotOpen},
   150  		{leveldb.ErrSnapshotReleased, database.ErrTxClosed},
   151  		{leveldb.ErrIterReleased, database.ErrTxClosed},
   152  	}
   153  
   154  	for i, test := range tests {
   155  		gotErr := convertErr("test", test.err)
   156  		if gotErr.ErrorCode != test.wantErrCode {
   157  			t.Errorf("convertErr #%d unexpected error - got %v, "+
   158  				"want %v", i, gotErr.ErrorCode, test.wantErrCode)
   159  			continue
   160  		}
   161  	}
   162  }
   163  
   164  // TestCornerCases ensures several corner cases which can happen when opening
   165  // a database and/or block files work as expected.
   166  func TestCornerCases(t *testing.T) {
   167  	t.Parallel()
   168  
   169  	// Create a file at the datapase path to force the open below to fail.
   170  	dbPath := filepath.Join(os.TempDir(), "ffldb-errors")
   171  	_ = os.RemoveAll(dbPath)
   172  	fi, err := os.Create(dbPath)
   173  	if err != nil {
   174  		t.Errorf("os.Create: unexpected error: %v", err)
   175  		return
   176  	}
   177  	fi.Close()
   178  
   179  	// Ensure creating a new database fails when a file exists where a
   180  	// directory is needed.
   181  	testName := "openDB: fail due to file at target location"
   182  	wantErrCode := database.ErrDriverSpecific
   183  	idb, err := openDB(dbPath, blockDataNet, true)
   184  	if !checkDbError(t, testName, err, wantErrCode) {
   185  		if err == nil {
   186  			idb.Close()
   187  		}
   188  		_ = os.RemoveAll(dbPath)
   189  		return
   190  	}
   191  
   192  	// Remove the file and create the database to run tests against.  It
   193  	// should be successful this time.
   194  	_ = os.RemoveAll(dbPath)
   195  	idb, err = openDB(dbPath, blockDataNet, true)
   196  	if err != nil {
   197  		t.Errorf("openDB: unexpected error: %v", err)
   198  		return
   199  	}
   200  	defer os.RemoveAll(dbPath)
   201  	defer idb.Close()
   202  
   203  	// Ensure attempting to write to a file that can't be created returns
   204  	// the expected error.
   205  	testName = "writeBlock: open file failure"
   206  	filePath := blockFilePath(dbPath, 0)
   207  	if err := os.Mkdir(filePath, 0755); err != nil {
   208  		t.Errorf("os.Mkdir: unexpected error: %v", err)
   209  		return
   210  	}
   211  	store := idb.(*db).store
   212  	_, err = store.writeBlock([]byte{0x00})
   213  	if !checkDbError(t, testName, err, database.ErrDriverSpecific) {
   214  		return
   215  	}
   216  	_ = os.RemoveAll(filePath)
   217  
   218  	// Close the underlying leveldb database out from under the database.
   219  	ldb := idb.(*db).cache.ldb
   220  	ldb.Close()
   221  
   222  	// Ensure initilization errors in the underlying database work as
   223  	// expected.
   224  	testName = "initDB: reinitialization"
   225  	wantErrCode = database.ErrDbNotOpen
   226  	err = initDB(ldb)
   227  	if !checkDbError(t, testName, err, wantErrCode) {
   228  		return
   229  	}
   230  
   231  	// Ensure the View handles errors in the underlying leveldb database
   232  	// properly.
   233  	testName = "View: underlying leveldb error"
   234  	wantErrCode = database.ErrDbNotOpen
   235  	err = idb.View(func(tx database.Tx) error {
   236  		return nil
   237  	})
   238  	if !checkDbError(t, testName, err, wantErrCode) {
   239  		return
   240  	}
   241  
   242  	// Ensure the Update handles errors in the underlying leveldb database
   243  	// properly.
   244  	testName = "Update: underlying leveldb error"
   245  	err = idb.Update(func(tx database.Tx) error {
   246  		return nil
   247  	})
   248  	if !checkDbError(t, testName, err, wantErrCode) {
   249  		return
   250  	}
   251  }
   252  
   253  // resetDatabase removes everything from the opened database associated with the
   254  // test context including all metadata and the mock files.
   255  func resetDatabase(tc *testContext) bool {
   256  	// Reset the metadata.
   257  	err := tc.db.Update(func(tx database.Tx) error {
   258  		// Remove all the keys using a cursor while also generating a
   259  		// list of buckets.  It's not safe to remove keys during ForEach
   260  		// iteration nor is it safe to remove buckets during cursor
   261  		// iteration, so this dual approach is needed.
   262  		var bucketNames [][]byte
   263  		cursor := tx.Metadata().Cursor()
   264  		for ok := cursor.First(); ok; ok = cursor.Next() {
   265  			if cursor.Value() != nil {
   266  				if err := cursor.Delete(); err != nil {
   267  					return err
   268  				}
   269  			} else {
   270  				bucketNames = append(bucketNames, cursor.Key())
   271  			}
   272  		}
   273  
   274  		// Remove the buckets.
   275  		for _, k := range bucketNames {
   276  			if err := tx.Metadata().DeleteBucket(k); err != nil {
   277  				return err
   278  			}
   279  		}
   280  
   281  		_, err := tx.Metadata().CreateBucket(blockIdxBucketName)
   282  		return err
   283  	})
   284  	if err != nil {
   285  		tc.t.Errorf("Update: unexpected error: %v", err)
   286  		return false
   287  	}
   288  
   289  	// Reset the mock files.
   290  	store := tc.db.(*db).store
   291  	wc := store.writeCursor
   292  	wc.curFile.Lock()
   293  	if wc.curFile.file != nil {
   294  		wc.curFile.file.Close()
   295  		wc.curFile.file = nil
   296  	}
   297  	wc.curFile.Unlock()
   298  	wc.Lock()
   299  	wc.curFileNum = 0
   300  	wc.curOffset = 0
   301  	wc.Unlock()
   302  	tc.files = make(map[uint32]*lockableFile)
   303  	tc.maxFileSizes = make(map[uint32]int64)
   304  	return true
   305  }
   306  
   307  // testWriteFailures tests various failures paths when writing to the block
   308  // files.
   309  func testWriteFailures(tc *testContext) bool {
   310  	if !resetDatabase(tc) {
   311  		return false
   312  	}
   313  
   314  	// Ensure file sync errors during flush return the expected error.
   315  	store := tc.db.(*db).store
   316  	testName := "flush: file sync failure"
   317  	store.writeCursor.Lock()
   318  	oldFile := store.writeCursor.curFile
   319  	store.writeCursor.curFile = &lockableFile{
   320  		file: &mockFile{forceSyncErr: true, maxSize: -1},
   321  	}
   322  	store.writeCursor.Unlock()
   323  	err := tc.db.(*db).cache.flush()
   324  	if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) {
   325  		return false
   326  	}
   327  	store.writeCursor.Lock()
   328  	store.writeCursor.curFile = oldFile
   329  	store.writeCursor.Unlock()
   330  
   331  	// Force errors in the various error paths when writing data by using
   332  	// mock files with a limited max size.
   333  	block0Bytes, _ := tc.blocks[0].Bytes()
   334  	tests := []struct {
   335  		fileNum uint32
   336  		maxSize int64
   337  	}{
   338  		// Force an error when writing the network bytes.
   339  		{fileNum: 0, maxSize: 2},
   340  
   341  		// Force an error when writing the block size.
   342  		{fileNum: 0, maxSize: 6},
   343  
   344  		// Force an error when writing the block.
   345  		{fileNum: 0, maxSize: 17},
   346  
   347  		// Force an error when writing the checksum.
   348  		{fileNum: 0, maxSize: int64(len(block0Bytes)) + 10},
   349  
   350  		// Force an error after writing enough blocks for force multiple
   351  		// files.
   352  		{fileNum: 15, maxSize: 1},
   353  	}
   354  
   355  	for i, test := range tests {
   356  		if !resetDatabase(tc) {
   357  			return false
   358  		}
   359  
   360  		// Ensure storing the specified number of blocks using a mock
   361  		// file that fails the write fails when the transaction is
   362  		// committed, not when the block is stored.
   363  		tc.maxFileSizes = map[uint32]int64{test.fileNum: test.maxSize}
   364  		err := tc.db.Update(func(tx database.Tx) error {
   365  			for i, block := range tc.blocks {
   366  				err := tx.StoreBlock(block)
   367  				if err != nil {
   368  					tc.t.Errorf("StoreBlock (%d): unexpected "+
   369  						"error: %v", i, err)
   370  					return errSubTestFail
   371  				}
   372  			}
   373  
   374  			return nil
   375  		})
   376  		testName := fmt.Sprintf("Force update commit failure - test "+
   377  			"%d, fileNum %d, maxsize %d", i, test.fileNum,
   378  			test.maxSize)
   379  		if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) {
   380  			tc.t.Errorf("%v", err)
   381  			return false
   382  		}
   383  
   384  		// Ensure the commit rollback removed all extra files and data.
   385  		if len(tc.files) != 1 {
   386  			tc.t.Errorf("Update rollback: new not removed - want "+
   387  				"1 file, got %d", len(tc.files))
   388  			return false
   389  		}
   390  		if _, ok := tc.files[0]; !ok {
   391  			tc.t.Error("Update rollback: file 0 does not exist")
   392  			return false
   393  		}
   394  		file := tc.files[0].file.(*mockFile)
   395  		if len(file.data) != 0 {
   396  			tc.t.Errorf("Update rollback: file did not truncate - "+
   397  				"want len 0, got len %d", len(file.data))
   398  			return false
   399  		}
   400  	}
   401  
   402  	return true
   403  }
   404  
   405  // testBlockFileErrors ensures the database returns expected errors with various
   406  // file-related issues such as closed and missing files.
   407  func testBlockFileErrors(tc *testContext) bool {
   408  	if !resetDatabase(tc) {
   409  		return false
   410  	}
   411  
   412  	// Ensure errors in blockFile and openFile when requesting invalid file
   413  	// numbers.
   414  	store := tc.db.(*db).store
   415  	testName := "blockFile invalid file open"
   416  	_, err := store.blockFile(^uint32(0))
   417  	if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) {
   418  		return false
   419  	}
   420  	testName = "openFile invalid file open"
   421  	_, err = store.openFile(^uint32(0))
   422  	if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) {
   423  		return false
   424  	}
   425  
   426  	// Insert the first block into the mock file.
   427  	err = tc.db.Update(func(tx database.Tx) error {
   428  		err := tx.StoreBlock(tc.blocks[0])
   429  		if err != nil {
   430  			tc.t.Errorf("StoreBlock: unexpected error: %v", err)
   431  			return errSubTestFail
   432  		}
   433  
   434  		return nil
   435  	})
   436  	if err != nil {
   437  		if err != errSubTestFail {
   438  			tc.t.Errorf("Update: unexpected error: %v", err)
   439  		}
   440  		return false
   441  	}
   442  
   443  	// Ensure errors in readBlock and readBlockRegion when requesting a file
   444  	// number that doesn't exist.
   445  	block0Hash := tc.blocks[0].Sha()
   446  	testName = "readBlock invalid file number"
   447  	invalidLoc := blockLocation{
   448  		blockFileNum: ^uint32(0),
   449  		blockLen:     80,
   450  	}
   451  	_, err = store.readBlock(block0Hash, invalidLoc)
   452  	if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) {
   453  		return false
   454  	}
   455  	testName = "readBlockRegion invalid file number"
   456  	_, err = store.readBlockRegion(invalidLoc, 0, 80)
   457  	if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) {
   458  		return false
   459  	}
   460  
   461  	// Close the block file out from under the database.
   462  	store.writeCursor.curFile.Lock()
   463  	store.writeCursor.curFile.file.Close()
   464  	store.writeCursor.curFile.Unlock()
   465  
   466  	// Ensure failures in FetchBlock and FetchBlockRegion(s) since the
   467  	// underlying file they need to read from has been closed.
   468  	err = tc.db.View(func(tx database.Tx) error {
   469  		testName = "FetchBlock closed file"
   470  		wantErrCode := database.ErrDriverSpecific
   471  		_, err := tx.FetchBlock(block0Hash)
   472  		if !checkDbError(tc.t, testName, err, wantErrCode) {
   473  			return errSubTestFail
   474  		}
   475  
   476  		testName = "FetchBlockRegion closed file"
   477  		regions := []database.BlockRegion{
   478  			{
   479  				Hash:   block0Hash,
   480  				Len:    80,
   481  				Offset: 0,
   482  			},
   483  		}
   484  		_, err = tx.FetchBlockRegion(&regions[0])
   485  		if !checkDbError(tc.t, testName, err, wantErrCode) {
   486  			return errSubTestFail
   487  		}
   488  
   489  		testName = "FetchBlockRegions closed file"
   490  		_, err = tx.FetchBlockRegions(regions)
   491  		if !checkDbError(tc.t, testName, err, wantErrCode) {
   492  			return errSubTestFail
   493  		}
   494  
   495  		return nil
   496  	})
   497  	if err != nil {
   498  		if err != errSubTestFail {
   499  			tc.t.Errorf("View: unexpected error: %v", err)
   500  		}
   501  		return false
   502  	}
   503  
   504  	return true
   505  }
   506  
   507  // testCorruption ensures the database returns expected errors under various
   508  // corruption scenarios.
   509  func testCorruption(tc *testContext) bool {
   510  	if !resetDatabase(tc) {
   511  		return false
   512  	}
   513  
   514  	// Insert the first block into the mock file.
   515  	err := tc.db.Update(func(tx database.Tx) error {
   516  		err := tx.StoreBlock(tc.blocks[0])
   517  		if err != nil {
   518  			tc.t.Errorf("StoreBlock: unexpected error: %v", err)
   519  			return errSubTestFail
   520  		}
   521  
   522  		return nil
   523  	})
   524  	if err != nil {
   525  		if err != errSubTestFail {
   526  			tc.t.Errorf("Update: unexpected error: %v", err)
   527  		}
   528  		return false
   529  	}
   530  
   531  	// Ensure corruption is detected by intentionally modifying the bytes
   532  	// stored to the mock file and reading the block.
   533  	block0Bytes, _ := tc.blocks[0].Bytes()
   534  	block0Hash := tc.blocks[0].Sha()
   535  	tests := []struct {
   536  		offset      uint32
   537  		fixChecksum bool
   538  		wantErrCode database.ErrorCode
   539  	}{
   540  		// One of the network bytes.  The checksum needs to be fixed so
   541  		// the invalid network is detected.
   542  		{2, true, database.ErrDriverSpecific},
   543  
   544  		// The same network byte, but this time don't fix the checksum
   545  		// to ensure the corruption is detected.
   546  		{2, false, database.ErrCorruption},
   547  
   548  		// One of the block length bytes.
   549  		{6, false, database.ErrCorruption},
   550  
   551  		// Random header byte.
   552  		{17, false, database.ErrCorruption},
   553  
   554  		// Random transaction byte.
   555  		{90, false, database.ErrCorruption},
   556  
   557  		// Random checksum byte.
   558  		{uint32(len(block0Bytes)) + 10, false, database.ErrCorruption},
   559  	}
   560  	err = tc.db.View(func(tx database.Tx) error {
   561  		data := tc.files[0].file.(*mockFile).data
   562  		for i, test := range tests {
   563  			// Corrupt the byte at the offset by a single bit.
   564  			data[test.offset] ^= 0x10
   565  
   566  			// Fix the checksum if requested to force other errors.
   567  			fileLen := len(data)
   568  			var oldChecksumBytes [4]byte
   569  			copy(oldChecksumBytes[:], data[fileLen-4:])
   570  			if test.fixChecksum {
   571  				toSum := data[:fileLen-4]
   572  				cksum := crc32.Checksum(toSum, castagnoli)
   573  				binary.BigEndian.PutUint32(data[fileLen-4:], cksum)
   574  			}
   575  
   576  			testName := fmt.Sprintf("FetchBlock (test #%d): "+
   577  				"corruption", i)
   578  			_, err := tx.FetchBlock(block0Hash)
   579  			if !checkDbError(tc.t, testName, err, test.wantErrCode) {
   580  				return errSubTestFail
   581  			}
   582  
   583  			// Reset the corrupted data back to the original.
   584  			data[test.offset] ^= 0x10
   585  			if test.fixChecksum {
   586  				copy(data[fileLen-4:], oldChecksumBytes[:])
   587  			}
   588  		}
   589  
   590  		return nil
   591  	})
   592  	if err != nil {
   593  		if err != errSubTestFail {
   594  			tc.t.Errorf("View: unexpected error: %v", err)
   595  		}
   596  		return false
   597  	}
   598  
   599  	return true
   600  }
   601  
   602  // TestFailureScenarios ensures several failure scenarios such as database
   603  // corruption, block file write failures, and rollback failures are handled
   604  // correctly.
   605  func TestFailureScenarios(t *testing.T) {
   606  	// Create a new database to run tests against.
   607  	dbPath := filepath.Join(os.TempDir(), "ffldb-failurescenarios")
   608  	_ = os.RemoveAll(dbPath)
   609  	idb, err := database.Create(dbType, dbPath, blockDataNet)
   610  	if err != nil {
   611  		t.Errorf("Failed to create test database (%s) %v", dbType, err)
   612  		return
   613  	}
   614  	defer os.RemoveAll(dbPath)
   615  	defer idb.Close()
   616  
   617  	// Create a test context to pass around.
   618  	tc := &testContext{
   619  		t:            t,
   620  		db:           idb,
   621  		files:        make(map[uint32]*lockableFile),
   622  		maxFileSizes: make(map[uint32]int64),
   623  	}
   624  
   625  	// Change the maximum file size to a small value to force multiple flat
   626  	// files with the test data set and replace the file-related functions
   627  	// to make use of mock files in memory.  This allows injection of
   628  	// various file-related errors.
   629  	store := idb.(*db).store
   630  	store.maxBlockFileSize = 1024 // 1KiB
   631  	store.openWriteFileFunc = func(fileNum uint32) (filer, error) {
   632  		if file, ok := tc.files[fileNum]; ok {
   633  			// "Reopen" the file.
   634  			file.Lock()
   635  			mock := file.file.(*mockFile)
   636  			mock.Lock()
   637  			mock.closed = false
   638  			mock.Unlock()
   639  			file.Unlock()
   640  			return mock, nil
   641  		}
   642  
   643  		// Limit the max size of the mock file as specified in the test
   644  		// context.
   645  		maxSize := int64(-1)
   646  		if maxFileSize, ok := tc.maxFileSizes[fileNum]; ok {
   647  			maxSize = int64(maxFileSize)
   648  		}
   649  		file := &mockFile{maxSize: int64(maxSize)}
   650  		tc.files[fileNum] = &lockableFile{file: file}
   651  		return file, nil
   652  	}
   653  	store.openFileFunc = func(fileNum uint32) (*lockableFile, error) {
   654  		// Force error when trying to open max file num.
   655  		if fileNum == ^uint32(0) {
   656  			return nil, makeDbErr(database.ErrDriverSpecific,
   657  				"test", nil)
   658  		}
   659  		if file, ok := tc.files[fileNum]; ok {
   660  			// "Reopen" the file.
   661  			file.Lock()
   662  			mock := file.file.(*mockFile)
   663  			mock.Lock()
   664  			mock.closed = false
   665  			mock.Unlock()
   666  			file.Unlock()
   667  			return file, nil
   668  		}
   669  		file := &lockableFile{file: &mockFile{}}
   670  		tc.files[fileNum] = file
   671  		return file, nil
   672  	}
   673  	store.deleteFileFunc = func(fileNum uint32) error {
   674  		if file, ok := tc.files[fileNum]; ok {
   675  			file.Lock()
   676  			file.file.Close()
   677  			file.Unlock()
   678  			delete(tc.files, fileNum)
   679  			return nil
   680  		}
   681  
   682  		str := fmt.Sprintf("file %d does not exist", fileNum)
   683  		return makeDbErr(database.ErrDriverSpecific, str, nil)
   684  	}
   685  
   686  	// Load the test blocks and save in the test context for use throughout
   687  	// the tests.
   688  	blocks, err := loadBlocks(t, blockDataFile, blockDataNet)
   689  	if err != nil {
   690  		t.Errorf("loadBlocks: Unexpected error: %v", err)
   691  		return
   692  	}
   693  	tc.blocks = blocks
   694  
   695  	// Test various failures paths when writing to the block files.
   696  	if !testWriteFailures(tc) {
   697  		return
   698  	}
   699  
   700  	// Test various file-related issues such as closed and missing files.
   701  	if !testBlockFileErrors(tc) {
   702  		return
   703  	}
   704  
   705  	// Test various corruption scenarios.
   706  	testCorruption(tc)
   707  }