diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 749c8bfb97..dd8cabca96 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -793,12 +793,9 @@ func dbFetchSpendJournalEntry(dbTx database.Tx, block *dcrutil.Block) ([]spentTx // Ensure any deserialization errors are returned as database // corruption errors. if isDeserializeErr(err) { - return nil, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt spend "+ - "information for %v: %v", block.Hash(), - err), - } + str := fmt.Sprintf("corrupt spend information for %v: %v", + block.Hash(), err) + return nil, database.MakeError(database.ErrCorruption, str) } return nil, err @@ -1164,11 +1161,8 @@ func dbFetchUtxoEntry(dbTx database.Tx, hash *chainhash.Hash) (*UtxoEntry, error // Ensure any deserialization errors are returned as database // corruption errors. if isDeserializeErr(err) { - return nil, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt utxo entry "+ - "for %v: %v", hash, err), - } + str := fmt.Sprintf("corrupt utxo entry for %v: %v", hash, err) + return nil, database.MakeError(database.ErrCorruption, str) } return nil, err @@ -1211,11 +1205,8 @@ func dbFetchUtxoStats(dbTx database.Tx) (*UtxoStats, error) { // Ensure any deserialization errors are returned as database // corruption errors. if isDeserializeErr(err) { - return nil, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt utxo entry "+ - "for %v: %v", hash, err), - } + str := fmt.Sprintf("corrupt utxo entry for %v: %v", hash, err) + return nil, database.MakeError(database.ErrCorruption, str) } return nil, err @@ -1312,11 +1303,8 @@ func dbFetchGCSFilter(dbTx database.Tx, blockHash *chainhash.Hash) (*gcs.FilterV filter, err := gcs.FromBytesV2(blockcf2.B, blockcf2.M, serialized) if err != nil { - return nil, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt filter for %v: %v", blockHash, - err), - } + str := fmt.Sprintf("corrupt filter for %v: %v", blockHash, err) + return nil, database.MakeError(database.ErrCorruption, str) } return filter, nil @@ -1511,11 +1499,9 @@ func deserializeBestChainState(serializedData []byte) (bestChainState, error) { // and work sum length. expectedMinLen := chainhash.HashSize + 4 + 8 + 8 + 4 if len(serializedData) < expectedMinLen { - return bestChainState{}, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt best chain state size; min %v "+ - "got %v", expectedMinLen, len(serializedData)), - } + str := fmt.Sprintf("corrupt best chain state size; min %v got %v", + expectedMinLen, len(serializedData)) + return bestChainState{}, database.MakeError(database.ErrCorruption, str) } state := bestChainState{} @@ -1536,11 +1522,9 @@ func deserializeBestChainState(serializedData []byte) (bestChainState, error) { // Ensure the serialized data has enough bytes to deserialize the work // sum. if uint32(len(serializedData[offset:])) < workSumBytesLen { - return bestChainState{}, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt work sum size; want %v "+ - "got %v", workSumBytesLen, uint32(len(serializedData[offset:]))), - } + str := fmt.Sprintf("corrupt work sum size; want %v got %v", + workSumBytesLen, uint32(len(serializedData[offset:]))) + return bestChainState{}, database.MakeError(database.ErrCorruption, str) } workSumBytes := serializedData[offset : offset+workSumBytesLen] state.workSum = new(big.Int).SetBytes(workSumBytes) diff --git a/blockchain/chainio_test.go b/blockchain/chainio_test.go index b9e0dd3ba4..37022b7fdd 100644 --- a/blockchain/chainio_test.go +++ b/blockchain/chainio_test.go @@ -1470,44 +1470,39 @@ func TestBestChainStateDeserializeErrors(t *testing.T) { tests := []struct { name string serialized []byte - errType error + errKind database.ErrorKind }{ { name: "nothing serialized", serialized: hexToBytes(""), - errType: database.Error{ErrorCode: database.ErrCorruption}, + errKind: database.ErrCorruption, }, { name: "short data in hash", serialized: hexToBytes("0000"), - errType: database.Error{ErrorCode: database.ErrCorruption}, + errKind: database.ErrCorruption, }, { name: "short data in work sum", serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000001000000000000000500000001000100"), - errType: database.Error{ErrorCode: database.ErrCorruption}, + errKind: database.ErrCorruption, }, } for _, test := range tests { // Ensure the expected error type and code is returned. _, err := deserializeBestChainState(test.serialized) - if !errors.As(err, &test.errType) { + var derr database.Error + if !errors.As(err, &derr) { t.Errorf("deserializeBestChainState (%s): expected "+ "error type does not match - got %T, want %T", - test.name, err, test.errType) + test.name, err, test.errKind) continue } - var derr database.Error - if errors.As(err, &derr) { - var tderr database.Error - if !errors.As(test.errType, &tderr) || derr.ErrorCode != tderr.ErrorCode { - t.Errorf("deserializeBestChainState (%s): "+ - "wrong error code got: %v, want: %v", - test.name, derr.ErrorCode, - tderr.ErrorCode) - continue - } + if !errors.Is(derr, test.errKind) { + t.Errorf("deserializeBestChainState (%s): wrong error code "+ + "got: %v, want: %v", test.name, derr, test.errKind) + continue } } } diff --git a/blockchain/indexers/addrindex.go b/blockchain/indexers/addrindex.go index 65db2e82dc..e00367cae4 100644 --- a/blockchain/indexers/addrindex.go +++ b/blockchain/indexers/addrindex.go @@ -325,12 +325,9 @@ func dbFetchAddrIndexEntries(bucket internalBucket, addrKey [addrKeySize]byte, n // Ensure any deserialization errors are returned as // database corruption errors. if isDeserializeErr(err) { - err = database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("failed to "+ - "deserialized address index "+ - "for key %x: %v", addrKey, err), - } + str := fmt.Sprintf("failed to deserialized address index "+ + "for key %x: %v", addrKey, err) + err = database.MakeError(database.ErrCorruption, str) } return nil, 0, err diff --git a/blockchain/indexers/manager.go b/blockchain/indexers/manager.go index 5e1e22db8f..dd47f1bd60 100644 --- a/blockchain/indexers/manager.go +++ b/blockchain/indexers/manager.go @@ -7,6 +7,7 @@ package indexers import ( "context" + "errors" "fmt" "github.com/decred/dcrd/blockchain/v3/internal/progresslog" @@ -60,11 +61,9 @@ func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*chainhash.Hash, int32, indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) serialized := indexesBucket.Get(idxKey) if len(serialized) < chainhash.HashSize+4 { - return nil, 0, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("unexpected end of data for "+ - "index %q tip", string(idxKey)), - } + str := fmt.Sprintf("unexpected end of data for index %q tip", + string(idxKey)) + return nil, 0, database.MakeError(database.ErrCorruption, str) } var hash chainhash.Hash @@ -103,11 +102,9 @@ func dbFetchIndexerVersion(dbTx database.Tx, idxKey []byte) (uint32, error) { } if len(serialized) < 4 { - return 0, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("unexpected end of data for "+ - "index %q version", string(idxKey)), - } + str := fmt.Sprintf("unexpected end of data for index %q version", + string(idxKey)) + return 0, database.MakeError(database.ErrCorruption, str) } version := byteOrder.Uint32(serialized) @@ -760,7 +757,7 @@ func dropIndexMetadata(db database.DB, idxKey []byte, idxName string) error { } err = meta.DeleteBucket(idxKey) - if err != nil && !database.IsError(err, database.ErrBucketNotFound) { + if err != nil && !errors.Is(err, database.ErrBucketNotFound) { return err } diff --git a/blockchain/indexers/txindex.go b/blockchain/indexers/txindex.go index c0655cf43a..68c05d2153 100644 --- a/blockchain/indexers/txindex.go +++ b/blockchain/indexers/txindex.go @@ -217,21 +217,16 @@ func dbFetchTxIndexEntry(dbTx database.Tx, txHash *chainhash.Hash) (*TxIndexEntr // Ensure the serialized data has enough bytes to properly deserialize. if len(serializedData) < txEntrySize { - return nil, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt transaction index "+ - "entry for %s", txHash), - } + str := fmt.Sprintf("corrupt transaction index entry for %s", txHash) + return nil, database.MakeError(database.ErrCorruption, str) } // Load the block hash associated with the block ID. hash, err := dbFetchBlockHashBySerializedID(dbTx, serializedData[0:4]) if err != nil { - return nil, database.Error{ - ErrorCode: database.ErrCorruption, - Description: fmt.Sprintf("corrupt transaction index "+ - "entry for %s: %v", txHash, err), - } + str := fmt.Sprintf("corrupt transaction index entry for %s: %v", + txHash, err) + return nil, database.MakeError(database.ErrCorruption, str) } // Deserialize the final entry. diff --git a/blockmanager.go b/blockmanager.go index ec2ef778f5..a13e8f2cb0 100644 --- a/blockmanager.go +++ b/blockmanager.go @@ -1069,8 +1069,7 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) { blockHash, err) } var dbErr database.Error - if errors.As(err, &dbErr) && dbErr.ErrorCode == - database.ErrCorruption { + if errors.As(err, &dbErr) && errors.Is(dbErr, database.ErrCorruption) { bmgrLog.Errorf("Critical failure: %v", dbErr.Error()) } @@ -2586,9 +2585,7 @@ func loadBlockDB(params *chaincfg.Params) (database.DB, error) { // Return the error if it's not because the database doesn't // exist. var dbErr database.Error - if !errors.As(err, &dbErr) || dbErr.ErrorCode != - database.ErrDbDoesNotExist { - + if !errors.As(err, &dbErr) || !errors.Is(dbErr, database.ErrDbDoesNotExist) { return nil, err } diff --git a/cmd/addblock/addblock.go b/cmd/addblock/addblock.go index f78ed23082..17ff19ce31 100644 --- a/cmd/addblock/addblock.go +++ b/cmd/addblock/addblock.go @@ -6,6 +6,7 @@ package main import ( + "errors" "os" "path/filepath" "runtime" @@ -38,9 +39,8 @@ func loadBlockDB() (database.DB, error) { if err != nil { // Return the error if it's not because the database doesn't // exist. - if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode != - database.ErrDbDoesNotExist { - + var dbErr database.Error + if !errors.As(err, &dbErr) || !errors.Is(dbErr, database.ErrDbDoesNotExist) { return nil, err } diff --git a/database/cmd/dbtool/insecureimport.go b/database/cmd/dbtool/insecureimport.go index 85497e8539..f56e71b4be 100644 --- a/database/cmd/dbtool/insecureimport.go +++ b/database/cmd/dbtool/insecureimport.go @@ -7,6 +7,7 @@ package main import ( "encoding/binary" + "errors" "fmt" "io" "os" @@ -376,8 +377,8 @@ func (cmd *importCmd) Execute(args []string) error { resultsChan := importer.Import() results := <-resultsChan if results.err != nil { - dbErr, ok := results.err.(database.Error) - if !ok || ok && dbErr.ErrorCode != database.ErrDbNotOpen { + var dbErr database.Error + if !errors.As(results.err, &dbErr) || !errors.Is(dbErr, database.ErrDbNotOpen) { shutdownChannel <- results.err return } diff --git a/database/cmd/dbtool/main.go b/database/cmd/dbtool/main.go index 317a6bd319..a2fb931f14 100644 --- a/database/cmd/dbtool/main.go +++ b/database/cmd/dbtool/main.go @@ -6,6 +6,7 @@ package main import ( + "errors" "os" "path/filepath" "runtime" @@ -37,9 +38,8 @@ func loadBlockDB() (database.DB, error) { if err != nil { // Return the error if it's not because the database doesn't // exist. - if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode != - database.ErrDbDoesNotExist { - + var dbErr database.Error + if !errors.As(err, &dbErr) || !errors.Is(dbErr, database.ErrDbDoesNotExist) { return nil, err } diff --git a/database/driver.go b/database/driver.go index 1abd0dfbea..f1d7aa214e 100644 --- a/database/driver.go +++ b/database/driver.go @@ -42,7 +42,7 @@ func RegisterDriver(driver Driver) error { if _, exists := drivers[driver.DbType]; exists { str := fmt.Sprintf("driver %q is already registered", driver.DbType) - return makeError(ErrDbTypeRegistered, str, nil) + return MakeError(ErrDbTypeRegistered, str) } drivers[driver.DbType] = &driver @@ -68,7 +68,7 @@ func Create(dbType string, args ...interface{}) (DB, error) { drv, exists := drivers[dbType] if !exists { str := fmt.Sprintf("driver %q is not registered", dbType) - return nil, makeError(ErrDbUnknownType, str, nil) + return nil, MakeError(ErrDbUnknownType, str) } return drv.Create(args...) @@ -83,7 +83,7 @@ func Open(dbType string, args ...interface{}) (DB, error) { drv, exists := drivers[dbType] if !exists { str := fmt.Sprintf("driver %q is not registered", dbType) - return nil, makeError(ErrDbUnknownType, str, nil) + return nil, MakeError(ErrDbUnknownType, str) } return drv.Open(args...) diff --git a/database/driver_test.go b/database/driver_test.go index 6a4b47b370..d78e53cf39 100644 --- a/database/driver_test.go +++ b/database/driver_test.go @@ -6,6 +6,7 @@ package database_test import ( + "errors" "fmt" "testing" @@ -13,19 +14,18 @@ import ( _ "github.com/decred/dcrd/database/v2/ffldb" ) -// checkDbError ensures the passed error is a database.Error with an error code -// that matches the passed error code. -func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool { - dbErr, ok := gotErr.(database.Error) - if !ok { +// checkDbError ensures the passed error is a database.Error that matches +// the passed error kind. +func checkDbError(t *testing.T, testName string, gotErr error, wantErr database.ErrorKind) bool { + var dbErr database.Error + if !errors.As(gotErr, &dbErr) { t.Errorf("%s: unexpected error type - got %T, want %T", - testName, gotErr, database.Error{}) + testName, gotErr, dbErr) return false } - if dbErr.ErrorCode != wantErrCode { - t.Errorf("%s: unexpected error code - got %s (%s), want %s", - testName, dbErr.ErrorCode, dbErr.Description, - wantErrCode) + if !errors.Is(dbErr, wantErr) { + t.Errorf("%s: unexpected error - got %s (%s), want %s", + testName, dbErr, dbErr.Description, wantErr) return false } diff --git a/database/error.go b/database/error.go index c76c7fe41e..5f428c30db 100644 --- a/database/error.go +++ b/database/error.go @@ -1,204 +1,158 @@ // Copyright (c) 2015-2016 The btcsuite developers -// Copyright (c) 2016 The Decred developers +// Copyright (c) 2016-2020 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package database -import "fmt" - -// ErrorCode identifies a kind of error. -type ErrorCode int +// ErrorKind identifies a kind of error. It has full support for errors.Is and +// errors.As, so the caller can directly check against an error kind when +// determining the reason for an error. +type ErrorKind string // These constants are used to identify a specific database Error. const ( - // ************************************** + // ------------------------------------------ // Errors related to driver registration. - // ************************************** + // ------------------------------------------ // ErrDbTypeRegistered indicates two different database drivers // attempt to register with the name database type. - ErrDbTypeRegistered ErrorCode = iota + ErrDbTypeRegistered = ErrorKind("ErrDbTypeRegistered") - // ************************************* + // ------------------------------------------ // Errors related to database functions. - // ************************************* + // ------------------------------------------ // ErrDbUnknownType indicates there is no driver registered for // the specified database type. - ErrDbUnknownType + ErrDbUnknownType = ErrorKind("ErrDbUnknownType") // ErrDbDoesNotExist indicates open is called for a database that // does not exist. - ErrDbDoesNotExist + ErrDbDoesNotExist = ErrorKind("ErrDbDoesNotExist") // ErrDbExists indicates create is called for a database that // already exists. - ErrDbExists + ErrDbExists = ErrorKind("ErrDbExists") // ErrDbNotOpen indicates a database instance is accessed before // it is opened or after it is closed. - ErrDbNotOpen + ErrDbNotOpen = ErrorKind("ErrDbNotOpen") // ErrDbAlreadyOpen indicates open was called on a database that // is already open. - ErrDbAlreadyOpen + ErrDbAlreadyOpen = ErrorKind("ErrDbAlreadyOpen") // ErrInvalid indicates the specified database is not valid. - ErrInvalid + ErrInvalid = ErrorKind("ErrInvalid") // ErrCorruption indicates a checksum failure occurred which invariably // means the database is corrupt. - ErrCorruption + ErrCorruption = ErrorKind("ErrCorruption") - // **************************************** + // ------------------------------------------ // Errors related to database transactions. - // **************************************** + // ------------------------------------------ // ErrTxClosed indicates an attempt was made to commit or rollback a // transaction that has already had one of those operations performed. - ErrTxClosed + ErrTxClosed = ErrorKind("ErrTxClosed") // ErrTxNotWritable indicates an operation that requires write access to // the database was attempted against a read-only transaction. - ErrTxNotWritable + ErrTxNotWritable = ErrorKind("ErrTxNotWritable") - // ************************************** + // ------------------------------------------ // Errors related to metadata operations. - // ************************************** + // ------------------------------------------ // ErrBucketNotFound indicates an attempt to access a bucket that has // not been created yet. - ErrBucketNotFound + ErrBucketNotFound = ErrorKind("ErrBucketNotFound") // ErrBucketExists indicates an attempt to create a bucket that already // exists. - ErrBucketExists + ErrBucketExists = ErrorKind("ErrBucketExists") // ErrBucketNameRequired indicates an attempt to create a bucket with a // blank name. - ErrBucketNameRequired + ErrBucketNameRequired = ErrorKind("ErrBucketNameRequired") // ErrKeyRequired indicates at attempt to insert a zero-length key. - ErrKeyRequired + ErrKeyRequired = ErrorKind("ErrKeyRequired") // ErrKeyTooLarge indicates an attempt to insert a key that is larger // than the max allowed key size. The max key size depends on the // specific backend driver being used. As a general rule, key sizes // should be relatively, so this should rarely be an issue. - ErrKeyTooLarge + ErrKeyTooLarge = ErrorKind("ErrKeyTooLarge") // ErrValueTooLarge indicates an attempt to insert a value that is // larger than max allowed value size. The max key size depends on the // specific backend driver being used. - ErrValueTooLarge + ErrValueTooLarge = ErrorKind("ErrValueTooLarge") // ErrIncompatibleValue indicates the value in question is invalid for // the specific requested operation. For example, trying create or // delete a bucket with an existing non-bucket key, attempting to create // or delete a non-bucket key with an existing bucket key, or trying to // delete a value via a cursor when it points to a nested bucket. - ErrIncompatibleValue + ErrIncompatibleValue = ErrorKind("ErrIncompatibleValue") - // *************************************** + // ------------------------------------------ // Errors related to block I/O operations. - // *************************************** + // ------------------------------------------ // ErrBlockNotFound indicates a block with the provided hash does not // exist in the database. - ErrBlockNotFound + ErrBlockNotFound = ErrorKind("ErrBlockNotFound") // ErrBlockExists indicates a block with the provided hash already // exists in the database. - ErrBlockExists + ErrBlockExists = ErrorKind("ErrBlockExists") // ErrBlockRegionInvalid indicates a region that exceeds the bounds of // the specified block was requested. When the hash provided by the // region does not correspond to an existing block, the error will be // ErrBlockNotFound instead. - ErrBlockRegionInvalid + ErrBlockRegionInvalid = ErrorKind("ErrBlockRegionInvalid") - // *********************************** + // ------------------------------------------ // Support for driver-specific errors. - // *********************************** + // ------------------------------------------ // ErrDriverSpecific indicates the Err field is a driver-specific error. // This provides a mechanism for drivers to plug-in their own custom // errors for any situations which aren't already covered by the error // codes provided by this package. - ErrDriverSpecific - - // numErrorCodes is the maximum error code number used in tests. - numErrorCodes + ErrDriverSpecific = ErrorKind("ErrDriverSpecific") ) -// Map of ErrorCode values back to their constant names for pretty printing. -var errorCodeStrings = map[ErrorCode]string{ - ErrDbTypeRegistered: "ErrDbTypeRegistered", - ErrDbUnknownType: "ErrDbUnknownType", - ErrDbDoesNotExist: "ErrDbDoesNotExist", - ErrDbExists: "ErrDbExists", - ErrDbNotOpen: "ErrDbNotOpen", - ErrDbAlreadyOpen: "ErrDbAlreadyOpen", - ErrInvalid: "ErrInvalid", - ErrCorruption: "ErrCorruption", - ErrTxClosed: "ErrTxClosed", - ErrTxNotWritable: "ErrTxNotWritable", - ErrBucketNotFound: "ErrBucketNotFound", - ErrBucketExists: "ErrBucketExists", - ErrBucketNameRequired: "ErrBucketNameRequired", - ErrKeyRequired: "ErrKeyRequired", - ErrKeyTooLarge: "ErrKeyTooLarge", - ErrValueTooLarge: "ErrValueTooLarge", - ErrIncompatibleValue: "ErrIncompatibleValue", - ErrBlockNotFound: "ErrBlockNotFound", - ErrBlockExists: "ErrBlockExists", - ErrBlockRegionInvalid: "ErrBlockRegionInvalid", - ErrDriverSpecific: "ErrDriverSpecific", -} - -// String returns the ErrorCode as a human-readable name. -func (e ErrorCode) String() string { - if s := errorCodeStrings[e]; s != "" { - return s - } - return fmt.Sprintf("Unknown ErrorCode (%d)", int(e)) +// Error satisfies the error interface and prints human-readable errors. +func (e ErrorKind) Error() string { + return string(e) } -// Error provides a single type for errors that can happen during database -// operation. It is used to indicate several types of failures including errors -// with caller requests such as specifying invalid block regions or attempting -// to access data against closed database transactions, driver errors, errors -// retrieving data, and errors communicating with database servers. -// -// The caller can use type assertions to determine if an error is an Error and -// access the ErrorCode field to ascertain the specific reason for the failure. -// -// The ErrDriverSpecific error code will also have the Err field set with the -// underlying error. Depending on the backend driver, the Err field might be -// set to the underlying error for other error codes as well. +// Error identifies an error related to database operation. It has +// full support for errors.Is and errors.As, so the caller can ascertain the +// specific reason for the error by checking the underlying error. type Error struct { - ErrorCode ErrorCode // Describes the kind of error - Description string // Human readable description of the issue - Err error // Underlying error + Description string + Err error } // Error satisfies the error interface and prints human-readable errors. func (e Error) Error() string { - if e.Err != nil { - return e.Description + ": " + e.Err.Error() - } return e.Description } -// makeError creates an Error given a set of arguments. The error code must -// be one of the error codes provided by this package. -func makeError(c ErrorCode, desc string, err error) Error { - return Error{ErrorCode: c, Description: desc, Err: err} +// Unwrap returns the underlying wrapped error. +func (e Error) Unwrap() error { + return e.Err } -// IsError returns whether err is an Error with a matching error code. -func IsError(err error, code ErrorCode) bool { - e, ok := err.(Error) - return ok && e.ErrorCode == code +// MakeError creates an Error given a set of arguments. +func MakeError(kind ErrorKind, desc string) Error { + return Error{Err: kind, Description: desc} } diff --git a/database/error_test.go b/database/error_test.go index 34ffd0031b..482e156648 100644 --- a/database/error_test.go +++ b/database/error_test.go @@ -7,15 +7,16 @@ package database_test import ( "errors" + "io" "testing" "github.com/decred/dcrd/database/v2" ) // TestErrorCodeStringer tests the stringized output for the ErrorCode type. -func TestErrorCodeStringer(t *testing.T) { +func TestErrorKindStringer(t *testing.T) { tests := []struct { - in database.ErrorCode + in database.ErrorKind want string }{ {database.ErrDbTypeRegistered, "ErrDbTypeRegistered"}, @@ -39,22 +40,12 @@ func TestErrorCodeStringer(t *testing.T) { {database.ErrBlockExists, "ErrBlockExists"}, {database.ErrBlockRegionInvalid, "ErrBlockRegionInvalid"}, {database.ErrDriverSpecific, "ErrDriverSpecific"}, - - {0xffff, "Unknown ErrorCode (65535)"}, - } - - // Detect additional error codes that don't have the stringer added. - if len(tests)-1 != int(database.TstNumErrorCodes) { - t.Errorf("It appears an error code was added without adding " + - "an associated stringer test") } - t.Logf("Running %d tests", len(tests)) for i, test := range tests { - result := test.in.String() + result := test.in.Error() if result != test.want { - t.Errorf("String #%d\ngot: %s\nwant: %s", i, result, - test.want) + t.Errorf("#%d: got: %s want: %s", i, result, test.want) continue } } @@ -76,22 +67,95 @@ func TestError(t *testing.T) { database.Error{Description: "human-readable error"}, "human-readable error", }, - { - database.Error{ - ErrorCode: database.ErrDriverSpecific, - Description: "some error", - Err: errors.New("driver-specific error"), - }, - "some error: driver-specific error", - }, } - t.Logf("Running %d tests", len(tests)) for i, test := range tests { result := test.in.Error() if result != test.want { - t.Errorf("Error #%d\n got: %s want: %s", i, result, - test.want) + t.Errorf("#%d: got: %s want: %s", i, result, test.want) + continue + } + } +} + +// TestErrorKindIsAs ensures both ErrorKind and Error can be identified as being +// a specific error kind via errors.Is and unwrapped via errors.As. +func TestErrorKindIsAs(t *testing.T) { + tests := []struct { + name string + err error + target error + wantMatch bool + wantAs database.ErrorKind + }{{ + name: "ErrDbTypeRegistered == database.ErrDbTypeRegistered", + err: database.ErrDbTypeRegistered, + target: database.ErrDbTypeRegistered, + wantMatch: true, + wantAs: database.ErrDbTypeRegistered, + }, { + name: "Error.ErrDbTypeRegistered == ErrDbTypeRegistered", + err: database.MakeError(database.ErrDbTypeRegistered, ""), + target: database.ErrDbTypeRegistered, + wantMatch: true, + wantAs: database.ErrDbTypeRegistered, + }, { + name: "Error.ErrDbTypeRegistered == Error.ErrDbTypeRegistered", + err: database.MakeError(database.ErrDbTypeRegistered, ""), + target: database.MakeError(database.ErrDbTypeRegistered, ""), + wantMatch: true, + wantAs: database.ErrDbTypeRegistered, + }, { + name: "ErrBucketNotFound != ErrDbTypeRegistered", + err: database.ErrBucketNotFound, + target: database.ErrDbTypeRegistered, + wantMatch: false, + wantAs: database.ErrBucketNotFound, + }, { + name: "Error.ErrBucketNotFound != ErrDbTypeRegistered", + err: database.MakeError(database.ErrBucketNotFound, ""), + target: database.ErrDbTypeRegistered, + wantMatch: false, + wantAs: database.ErrBucketNotFound, + }, { + name: "ErrBucketNotFound != Error.ErrDbTypeRegistered", + err: database.ErrBucketNotFound, + target: database.MakeError(database.ErrDbTypeRegistered, ""), + wantMatch: false, + wantAs: database.ErrBucketNotFound, + }, { + name: "Error.ErrBucketNotFound != Error.ErrDbTypeRegistered", + err: database.MakeError(database.ErrBucketNotFound, ""), + target: database.MakeError(database.ErrDbTypeRegistered, ""), + wantMatch: false, + wantAs: database.ErrBucketNotFound, + }, { + name: "Error.ErrKeyRequired, != io.EOF", + err: database.MakeError(database.ErrKeyRequired, ""), + target: io.EOF, + wantMatch: false, + wantAs: database.ErrKeyRequired, + }} + + for _, test := range tests { + // Ensure the error matches or not depending on the expected result. + result := errors.Is(test.err, test.target) + if result != test.wantMatch { + t.Errorf("%s: incorrect error identification -- got %v, want %v", + test.name, result, test.wantMatch) + continue + } + + // Ensure the underlying error kind can be unwrapped is and is the + // expected kind. + var kind database.ErrorKind + if !errors.As(test.err, &kind) { + t.Errorf("%s: unable to unwrap to error kind", test.name) + continue + } + if kind != test.wantAs { + t.Errorf("%s: unexpected unwrapped error kind -- got %v, want %v", + test.name, kind, test.wantAs) continue } } diff --git a/database/export_test.go b/database/export_test.go deleted file mode 100644 index 9a6b8080d4..0000000000 --- a/database/export_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2015-2016 The btcsuite developers -// Copyright (c) 2016 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -/* -This test file is part of the database package rather than the -database_test package so it can bridge access to the internals to properly test -cases which are either not possible or can't reliably be tested via the public -interface. The functions, constants, and variables are only exported while the -tests are being run. -*/ - -package database - -// TstNumErrorCodes makes the internal numErrorCodes parameter available to the -// test package. -const TstNumErrorCodes = numErrorCodes diff --git a/database/ffldb/blockio.go b/database/ffldb/blockio.go index 85d5364019..977abf311a 100644 --- a/database/ffldb/blockio.go +++ b/database/ffldb/blockio.go @@ -240,7 +240,7 @@ func (s *blockStore) openWriteFile(fileNum uint32) (filer, error) { file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666) if err != nil { str := fmt.Sprintf("failed to open file %q: %v", filePath, err) - return nil, makeDbErr(database.ErrDriverSpecific, str, err) + return nil, database.MakeError(database.ErrDriverSpecific, str) } return file, nil @@ -258,8 +258,9 @@ func (s *blockStore) openFile(fileNum uint32) (*lockableFile, error) { filePath := blockFilePath(s.basePath, fileNum) file, err := os.Open(filePath) if err != nil { - return nil, makeDbErr(database.ErrDriverSpecific, err.Error(), - err) + str := fmt.Sprintf("failed to open read-only file %q: %v", + filePath, err) + return nil, database.MakeError(database.ErrDriverSpecific, str) } blockFile := &lockableFile{file: file} @@ -305,7 +306,8 @@ func (s *blockStore) openFile(fileNum uint32) (*lockableFile, error) { func (s *blockStore) deleteFile(fileNum uint32) error { filePath := blockFilePath(s.basePath, fileNum) if err := os.Remove(filePath); err != nil { - return makeDbErr(database.ErrDriverSpecific, err.Error(), err) + str := fmt.Sprintf("failed to delete file %q: %v", filePath, err) + return database.MakeError(database.ErrDriverSpecific, str) } return nil @@ -387,7 +389,7 @@ func (s *blockStore) writeData(data []byte, fieldName string) error { str := fmt.Sprintf("failed to write %s to file %d at "+ "offset %d: %v", fieldName, wc.curFileNum, wc.curOffset-uint32(n), err) - return makeDbErr(database.ErrDriverSpecific, str, err) + return database.MakeError(database.ErrDriverSpecific, str) } return nil @@ -525,7 +527,7 @@ func (s *blockStore) readBlock(hash *chainhash.Hash, loc blockLocation) ([]byte, str := fmt.Sprintf("failed to read block %s from file %d, "+ "offset %d: %v", hash, loc.blockFileNum, loc.fileOffset, err) - return nil, makeDbErr(database.ErrDriverSpecific, str, err) + return nil, database.MakeError(database.ErrDriverSpecific, str) } // Calculate the checksum of the read data and ensure it matches the @@ -538,7 +540,7 @@ func (s *blockStore) readBlock(hash *chainhash.Hash, loc blockLocation) ([]byte, str := fmt.Sprintf("block data for block %s checksum "+ "does not match - got %x, want %x", hash, calculatedChecksum, serializedChecksum) - return nil, makeDbErr(database.ErrCorruption, str, nil) + return nil, database.MakeError(database.ErrCorruption, str) } // The network associated with the block must match the current active @@ -549,7 +551,7 @@ func (s *blockStore) readBlock(hash *chainhash.Hash, loc blockLocation) ([]byte, str := fmt.Sprintf("block data for block %s is for the "+ "wrong network - got %d, want %d", hash, serializedNet, uint32(s.network)) - return nil, makeDbErr(database.ErrDriverSpecific, str, nil) + return nil, database.MakeError(database.ErrDriverSpecific, str) } // The raw block excludes the network, length of the block, and @@ -585,7 +587,7 @@ func (s *blockStore) readBlockRegion(loc blockLocation, offset, numBytes uint32) str := fmt.Sprintf("failed to read region from block file %d, "+ "offset %d, len %d: %v", loc.blockFileNum, readOffset, numBytes, err) - return nil, makeDbErr(database.ErrDriverSpecific, str, err) + return nil, database.MakeError(database.ErrDriverSpecific, str) } return serializedData, nil @@ -615,7 +617,7 @@ func (s *blockStore) syncBlocks() error { if err := wc.curFile.file.Sync(); err != nil { str := fmt.Sprintf("failed to sync file %d: %v", wc.curFileNum, err) - return makeDbErr(database.ErrDriverSpecific, str, err) + return database.MakeError(database.ErrDriverSpecific, str) } return nil diff --git a/database/ffldb/db.go b/database/ffldb/db.go index 4b2ab37b7a..f86be5292c 100644 --- a/database/ffldb/db.go +++ b/database/ffldb/db.go @@ -126,36 +126,31 @@ func (s bulkFetchDataSorter) Less(i, j int) bool { return s[i].fileOffset < s[j].fileOffset } -// makeDbErr creates a database.Error given a set of arguments. -func makeDbErr(c database.ErrorCode, desc string, err error) database.Error { - return database.Error{ErrorCode: c, Description: desc, Err: err} -} - // convertErr converts the passed leveldb error into a database error with an -// equivalent error code and the passed description. It also sets the passed +// equivalent error kind and the passed description. It also sets the passed // error as the underlying error. func convertErr(desc string, ldbErr error) database.Error { // Use the driver-specific error code by default. The code below will // update this with the converted error if it's recognized. - var code = database.ErrDriverSpecific + var kind = database.ErrDriverSpecific switch { // Database corruption errors. case ldberrors.IsCorrupted(ldbErr): - code = database.ErrCorruption + kind = database.ErrCorruption // Database open/create errors. case ldbErr == leveldb.ErrClosed: - code = database.ErrDbNotOpen + kind = database.ErrDbNotOpen // Transaction errors. case ldbErr == leveldb.ErrSnapshotReleased: - code = database.ErrTxClosed + kind = database.ErrTxClosed case ldbErr == leveldb.ErrIterReleased: - code = database.ErrTxClosed + kind = database.ErrTxClosed } - return database.Error{ErrorCode: code, Description: desc, Err: ldbErr} + return database.MakeError(kind, ldbErr.Error()) } // copySlice returns a copy of the passed slice. This is mostly used to copy @@ -210,14 +205,14 @@ func (c *cursor) Delete() error { // Error if the cursor is exhausted. if c.currentIter == nil { str := "cursor is exhausted" - return makeDbErr(database.ErrIncompatibleValue, str, nil) + return database.MakeError(database.ErrIncompatibleValue, str) } // Do not allow buckets to be deleted via the cursor. key := c.currentIter.Key() if bytes.HasPrefix(key, bucketIndexPrefix) { str := "buckets may not be deleted from a cursor" - return makeDbErr(database.ErrIncompatibleValue, str, nil) + return database.MakeError(database.ErrIncompatibleValue, str) } c.bucket.tx.deleteKey(copySlice(key), true) @@ -626,20 +621,20 @@ func (b *bucket) CreateBucket(key []byte) (database.Bucket, error) { // Ensure the transaction is writable. if !b.tx.writable { str := "create bucket requires a writable database transaction" - return nil, makeDbErr(database.ErrTxNotWritable, str, nil) + return nil, database.MakeError(database.ErrTxNotWritable, str) } // Ensure a key was provided. if len(key) == 0 { str := "create bucket requires a key" - return nil, makeDbErr(database.ErrBucketNameRequired, str, nil) + return nil, database.MakeError(database.ErrBucketNameRequired, str) } // Ensure bucket does not already exist. bidxKey := bucketIndexKey(b.id, key) if b.tx.hasKey(bidxKey) { str := "bucket already exists" - return nil, makeDbErr(database.ErrBucketExists, str, nil) + return nil, database.MakeError(database.ErrBucketExists, str) } // Find the appropriate next bucket ID to use for the new bucket. In @@ -683,7 +678,7 @@ func (b *bucket) CreateBucketIfNotExists(key []byte) (database.Bucket, error) { // Ensure the transaction is writable. if !b.tx.writable { str := "create bucket requires a writable database transaction" - return nil, makeDbErr(database.ErrTxNotWritable, str, nil) + return nil, database.MakeError(database.ErrTxNotWritable, str) } // Return existing bucket if it already exists, otherwise create it. @@ -710,7 +705,7 @@ func (b *bucket) DeleteBucket(key []byte) error { // Ensure the transaction is writable. if !b.tx.writable { str := "delete bucket requires a writable database transaction" - return makeDbErr(database.ErrTxNotWritable, str, nil) + return database.MakeError(database.ErrTxNotWritable, str) } // Attempt to fetch the ID for the child bucket. The bucket does not @@ -720,7 +715,7 @@ func (b *bucket) DeleteBucket(key []byte) error { childID := b.tx.fetchKey(bidxKey) if childID == nil { str := fmt.Sprintf("bucket %q does not exist", key) - return makeDbErr(database.ErrBucketNotFound, str, nil) + return database.MakeError(database.ErrBucketNotFound, str) } // Remove all nested buckets and their keys. @@ -876,13 +871,13 @@ func (b *bucket) Put(key, value []byte) error { // Ensure the transaction is writable. if !b.tx.writable { str := "setting a key requires a writable database transaction" - return makeDbErr(database.ErrTxNotWritable, str, nil) + return database.MakeError(database.ErrTxNotWritable, str) } // Ensure a key was provided. if len(key) == 0 { str := "put requires a key" - return makeDbErr(database.ErrKeyRequired, str, nil) + return database.MakeError(database.ErrKeyRequired, str) } return b.tx.putKey(bucketizedKey(b.id, key), value) @@ -930,7 +925,7 @@ func (b *bucket) Delete(key []byte) error { // Ensure the transaction is writable. if !b.tx.writable { str := "deleting a value requires a writable database transaction" - return makeDbErr(database.ErrTxNotWritable, str, nil) + return database.MakeError(database.ErrTxNotWritable, str) } // Nothing to do if there is no key. @@ -1019,7 +1014,7 @@ func (tx *transaction) notifyActiveIters() { func (tx *transaction) checkClosed() error { // The transaction is no longer valid if it has been closed. if tx.closed { - return makeDbErr(database.ErrTxClosed, errTxClosedStr, nil) + return database.MakeError(database.ErrTxClosed, errTxClosedStr) } return nil @@ -1155,21 +1150,21 @@ func (tx *transaction) StoreBlock(block database.BlockSerializer) error { // Ensure the transaction is writable. if !tx.writable { str := "store block requires a writable database transaction" - return makeDbErr(database.ErrTxNotWritable, str, nil) + return database.MakeError(database.ErrTxNotWritable, str) } // Reject the block if it already exists. blockHash := block.Hash() if tx.hasBlock(blockHash) { str := fmt.Sprintf("block %s already exists", blockHash) - return makeDbErr(database.ErrBlockExists, str, nil) + return database.MakeError(database.ErrBlockExists, str) } blockBytes, err := block.Bytes() if err != nil { str := fmt.Sprintf("failed to get serialized bytes for block %s", blockHash) - return makeDbErr(database.ErrDriverSpecific, str, err) + return database.MakeError(database.ErrDriverSpecific, str) } // Add the block to be stored to the list of pending blocks to store @@ -1232,7 +1227,7 @@ func (tx *transaction) fetchBlockRow(hash *chainhash.Hash) ([]byte, error) { blockRow := tx.blockIdxBucket.Get(hash[:]) if blockRow == nil { str := fmt.Sprintf("block %s does not exist", hash) - return nil, makeDbErr(database.ErrBlockNotFound, str, nil) + return nil, database.MakeError(database.ErrBlockNotFound, str) } return blockRow, nil @@ -1440,7 +1435,7 @@ func (tx *transaction) fetchPendingRegion(region *database.BlockRegion) ([]byte, str := fmt.Sprintf("block %s region offset %d, length %d "+ "exceeds block length of %d", region.Hash, region.Offset, region.Len, blockLen) - return nil, makeDbErr(database.ErrBlockRegionInvalid, str, nil) + return nil, database.MakeError(database.ErrBlockRegionInvalid, str) } // Return the bytes from the pending block. @@ -1505,7 +1500,7 @@ func (tx *transaction) FetchBlockRegion(region *database.BlockRegion) ([]byte, e str := fmt.Sprintf("block %s region offset %d, length %d "+ "exceeds block length of %d", region.Hash, region.Offset, region.Len, location.blockLen) - return nil, makeDbErr(database.ErrBlockRegionInvalid, str, nil) + return nil, database.MakeError(database.ErrBlockRegionInvalid, str) } // Read the region from the appropriate disk block file. @@ -1602,7 +1597,7 @@ func (tx *transaction) FetchBlockRegions(regions []database.BlockRegion) ([][]by str := fmt.Sprintf("block %s region offset %d, length "+ "%d exceeds block length of %d", region.Hash, region.Offset, region.Len, location.blockLen) - return nil, makeDbErr(database.ErrBlockRegionInvalid, str, nil) + return nil, database.MakeError(database.ErrBlockRegionInvalid, str) } fetchList = append(fetchList, bulkFetchData{&location, i}) @@ -1753,7 +1748,7 @@ func (tx *transaction) Commit() error { // Ensure the transaction is writable. if !tx.writable { str := "Commit requires a writable database transaction" - return makeDbErr(database.ErrTxNotWritable, str, nil) + return database.MakeError(database.ErrTxNotWritable, str) } // Write pending data. The function will rollback if any errors occur. @@ -1827,8 +1822,7 @@ func (db *db) begin(writable bool) (*transaction, error) { if writable { db.writeLock.Unlock() } - return nil, makeDbErr(database.ErrDbNotOpen, errDbNotOpenStr, - nil) + return nil, database.MakeError(database.ErrDbNotOpen, errDbNotOpenStr) } // Grab a snapshot of the database cache (which in turn also handles the @@ -1967,7 +1961,7 @@ func (db *db) Close() error { defer db.closeLock.Unlock() if db.closed { - return makeDbErr(database.ErrDbNotOpen, errDbNotOpenStr, nil) + return database.MakeError(database.ErrDbNotOpen, errDbNotOpenStr) } db.closed = true @@ -2045,7 +2039,7 @@ func openDB(dbPath string, network wire.CurrencyNet, create bool) (database.DB, dbExists := fileExists(metadataDbPath) if !create && !dbExists { str := fmt.Sprintf("database %q does not exist", metadataDbPath) - return nil, makeDbErr(database.ErrDbDoesNotExist, str, nil) + return nil, database.MakeError(database.ErrDbDoesNotExist, str) } // Ensure the full path to the database exists. diff --git a/database/ffldb/driver_test.go b/database/ffldb/driver_test.go index 0c30c759bc..bf7df409ac 100644 --- a/database/ffldb/driver_test.go +++ b/database/ffldb/driver_test.go @@ -29,9 +29,9 @@ func TestCreateOpenFail(t *testing.T) { // Ensure that attempting to open a database that doesn't exist returns // the expected error. - wantErrCode := database.ErrDbDoesNotExist + wantErrKind := database.ErrDbDoesNotExist _, err := database.Open(dbType, "noexist", blockDataNet) - if !checkDbError(t, "Open", err, wantErrCode) { + if !checkDbError(t, "Open", err, wantErrKind) { return } @@ -113,37 +113,37 @@ func TestCreateOpenFail(t *testing.T) { defer os.RemoveAll(dbPath) db.Close() - wantErrCode = database.ErrDbNotOpen + wantErrKind = database.ErrDbNotOpen err = db.View(func(tx database.Tx) error { return nil }) - if !checkDbError(t, "View", err, wantErrCode) { + if !checkDbError(t, "View", err, wantErrKind) { return } - wantErrCode = database.ErrDbNotOpen + wantErrKind = database.ErrDbNotOpen err = db.Update(func(tx database.Tx) error { return nil }) - if !checkDbError(t, "Update", err, wantErrCode) { + if !checkDbError(t, "Update", err, wantErrKind) { return } - wantErrCode = database.ErrDbNotOpen + wantErrKind = database.ErrDbNotOpen _, err = db.Begin(false) - if !checkDbError(t, "Begin(false)", err, wantErrCode) { + if !checkDbError(t, "Begin(false)", err, wantErrKind) { return } - wantErrCode = database.ErrDbNotOpen + wantErrKind = database.ErrDbNotOpen _, err = db.Begin(true) - if !checkDbError(t, "Begin(true)", err, wantErrCode) { + if !checkDbError(t, "Begin(true)", err, wantErrKind) { return } - wantErrCode = database.ErrDbNotOpen + wantErrKind = database.ErrDbNotOpen err = db.Close() - if !checkDbError(t, "Close", err, wantErrCode) { + if !checkDbError(t, "Close", err, wantErrKind) { return } } diff --git a/database/ffldb/interface_test.go b/database/ffldb/interface_test.go index 272906848a..11ce3ac236 100644 --- a/database/ffldb/interface_test.go +++ b/database/ffldb/interface_test.go @@ -17,6 +17,7 @@ import ( "bytes" "compress/bzip2" "encoding/gob" + "errors" "fmt" "os" "path/filepath" @@ -88,19 +89,18 @@ func loadBlocks(t *testing.T, dataFile string, network wire.CurrencyNet) ([]*dcr return blocks, nil } -// checkDbError ensures the passed error is a database.Error with an error code -// that matches the passed error code. -func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool { - dbErr, ok := gotErr.(database.Error) - if !ok { +// checkDbError ensures the passed error is a database.Error that matches the +// passed error kind. +func checkDbError(t *testing.T, testName string, gotErr error, wantErr database.ErrorKind) bool { + var dbErr database.Error + if !errors.As(gotErr, &dbErr) { t.Errorf("%s: unexpected error type - got %T, want %T", - testName, gotErr, database.Error{}) + testName, gotErr, dbErr) return false } - if dbErr.ErrorCode != wantErrCode { + if !errors.Is(dbErr, wantErr) { t.Errorf("%s: unexpected error code - got %s (%s), want %s", - testName, dbErr.ErrorCode, dbErr.Description, - wantErrCode) + testName, dbErr, dbErr.Description, wantErr) return false } @@ -478,9 +478,9 @@ func testBucketInterface(tc *testContext, bucket database.Bucket) bool { // Ensure creating a bucket that already exists fails with the // expected error. - wantErrCode := database.ErrBucketExists + wantErrKind := database.ErrBucketExists _, err = bucket.CreateBucket(testBucketName) - if !checkDbError(tc.t, "CreateBucket", err, wantErrCode) { + if !checkDbError(tc.t, "CreateBucket", err, wantErrKind) { return false } @@ -514,9 +514,9 @@ func testBucketInterface(tc *testContext, bucket database.Bucket) bool { // Ensure deleting a bucket that doesn't exist returns the // expected error. - wantErrCode = database.ErrBucketNotFound + wantErrKind = database.ErrBucketNotFound err = bucket.DeleteBucket(testBucketName) - if !checkDbError(tc.t, "DeleteBucket", err, wantErrCode) { + if !checkDbError(tc.t, "DeleteBucket", err, wantErrKind) { return false } @@ -551,24 +551,24 @@ func testBucketInterface(tc *testContext, bucket database.Bucket) bool { } else { // Put should fail with bucket that is not writable. testName := "unwritable tx put" - wantErrCode := database.ErrTxNotWritable + wantErrKind := database.ErrTxNotWritable failBytes := []byte("fail") err := bucket.Put(failBytes, failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Delete should fail with bucket that is not writable. testName = "unwritable tx delete" err = bucket.Delete(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // CreateBucket should fail with bucket that is not writable. testName = "unwritable tx create bucket" _, err = bucket.CreateBucket(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -576,14 +576,14 @@ func testBucketInterface(tc *testContext, bucket database.Bucket) bool { // writable. testName = "unwritable tx create bucket if not exists" _, err = bucket.CreateBucketIfNotExists(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // DeleteBucket should fail with bucket that is not writable. testName = "unwritable tx delete bucket" err = bucket.DeleteBucket(failBytes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -655,9 +655,9 @@ func testMetadataManualTxInterface(tc *testContext) bool { // The transaction is not writable, so it should fail // the commit. testName := "unwritable tx commit" - wantErrCode := database.ErrTxNotWritable + wantErrKind := database.ErrTxNotWritable err := tx.Commit() - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { _ = tx.Rollback() return false } @@ -1085,7 +1085,7 @@ func testMetadataTxInterface(tc *testContext) bool { // testFetchBlockIOMissing ensures that all of the block retrieval API functions // work as expected when requesting blocks that don't exist. func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool { - wantErrCode := database.ErrBlockNotFound + wantErrKind := database.ErrBlockNotFound // --------------------- // Non-bulk Block IO API @@ -1110,7 +1110,7 @@ func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool { // Ensure FetchBlock returns expected error. testName := fmt.Sprintf("FetchBlock #%d on missing block", i) _, err = tx.FetchBlock(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1118,7 +1118,7 @@ func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool { testName = fmt.Sprintf("FetchBlockHeader #%d on missing block", i) _, err = tx.FetchBlockHeader(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1131,7 +1131,7 @@ func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool { } allBlockRegions[i] = region _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1154,21 +1154,21 @@ func testFetchBlockIOMissing(tc *testContext, tx database.Tx) bool { // Ensure FetchBlocks returns expected error. testName := "FetchBlocks on missing blocks" _, err := tx.FetchBlocks(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure FetchBlockHeaders returns expected error. testName = "FetchBlockHeaders on missing blocks" _, err = tx.FetchBlockHeaders(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure FetchBlockRegions returns expected error. testName = "FetchBlockRegions on missing blocks" _, err = tx.FetchBlockRegions(allBlockRegions) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1299,9 +1299,9 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { badBlockHash := &chainhash.Hash{} testName := fmt.Sprintf("FetchBlock(%s) invalid block", badBlockHash) - wantErrCode := database.ErrBlockNotFound + wantErrKind := database.ErrBlockNotFound _, err = tx.FetchBlock(badBlockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1310,7 +1310,7 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { testName = fmt.Sprintf("FetchBlockHeader(%s) invalid block", badBlockHash) _, err = tx.FetchBlockHeader(badBlockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1318,11 +1318,11 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { // return the expected error. testName = fmt.Sprintf("FetchBlockRegion(%s) invalid hash", badBlockHash) - wantErrCode = database.ErrBlockNotFound + wantErrKind = database.ErrBlockNotFound region.Hash = badBlockHash region.Offset = ^uint32(0) _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1330,11 +1330,11 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { // the expected error. testName = fmt.Sprintf("FetchBlockRegion(%s) invalid region", blockHash) - wantErrCode = database.ErrBlockRegionInvalid + wantErrKind = database.ErrBlockRegionInvalid region.Hash = blockHash region.Offset = ^uint32(0) _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } } @@ -1441,9 +1441,9 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { badBlockHashes := make([]chainhash.Hash, len(allBlockHashes)+1) copy(badBlockHashes, allBlockHashes) badBlockHashes[len(badBlockHashes)-1] = chainhash.Hash{} - wantErrCode := database.ErrBlockNotFound + wantErrKind := database.ErrBlockNotFound _, err = tx.FetchBlocks(badBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1451,7 +1451,7 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { // expected error. testName = "FetchBlockHeaders invalid hash" _, err = tx.FetchBlockHeaders(badBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1461,9 +1461,9 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { badBlockRegions := make([]database.BlockRegion, len(allBlockRegions)+1) copy(badBlockRegions, allBlockRegions) badBlockRegions[len(badBlockRegions)-1].Hash = &chainhash.Hash{} - wantErrCode = database.ErrBlockNotFound + wantErrKind = database.ErrBlockNotFound _, err = tx.FetchBlockRegions(badBlockRegions) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1474,9 +1474,9 @@ func testFetchBlockIO(tc *testContext, tx database.Tx) bool { for i := range badBlockRegions { badBlockRegions[i].Offset = ^uint32(0) } - wantErrCode = database.ErrBlockRegionInvalid + wantErrKind = database.ErrBlockRegionInvalid _, err = tx.FetchBlockRegions(badBlockRegions) - return checkDbError(tc.t, testName, err, wantErrCode) + return checkDbError(tc.t, testName, err, wantErrKind) } // testBlockIOTxInterface ensures that the block IO interface works as expected @@ -1486,11 +1486,11 @@ func testBlockIOTxInterface(tc *testContext) bool { // Ensure attempting to store a block with a read-only transaction fails // with the expected error. err := tc.db.View(func(tx database.Tx) error { - wantErrCode := database.ErrTxNotWritable + wantErrKind := database.ErrTxNotWritable for i, block := range tc.blocks { testName := fmt.Sprintf("StoreBlock(%d) on ro tx", i) err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return errSubTestFail } } @@ -1522,12 +1522,12 @@ func testBlockIOTxInterface(tc *testContext) bool { // Ensure attempting to store the same block again, before the // transaction has been committed, returns the expected error. - wantErrCode := database.ErrBlockExists + wantErrKind := database.ErrBlockExists for i, block := range tc.blocks { testName := fmt.Sprintf("duplicate block entry #%d "+ "(before commit)", i) err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return errSubTestFail } } @@ -1583,9 +1583,9 @@ func testBlockIOTxInterface(tc *testContext) bool { for i, block := range tc.blocks { testName := fmt.Sprintf("duplicate block entry #%d "+ "(before commit)", i) - wantErrCode := database.ErrBlockExists + wantErrKind := database.ErrBlockExists err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return errSubTestFail } } @@ -1634,12 +1634,12 @@ func testBlockIOTxInterface(tc *testContext) bool { // expected error. Note that this is different from the // previous version since this is a new transaction after the // blocks have been committed. - wantErrCode := database.ErrBlockExists + wantErrKind := database.ErrBlockExists for i, block := range tc.blocks { testName := fmt.Sprintf("duplicate block entry #%d "+ "(before commit)", i) err := tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return errSubTestFail } } @@ -1659,7 +1659,7 @@ func testBlockIOTxInterface(tc *testContext) bool { // testClosedTxInterface ensures that both the metadata and block IO API // functions behave as expected when attempted against a closed transaction. func testClosedTxInterface(tc *testContext, tx database.Tx) bool { - wantErrCode := database.ErrTxClosed + wantErrKind := database.ErrTxClosed bucket := tx.Metadata() cursor := tx.Metadata().Cursor() bucketName := []byte("closedtxbucket") @@ -1679,42 +1679,42 @@ func testClosedTxInterface(tc *testContext, tx database.Tx) bool { // Ensure CreateBucket returns expected error. testName := "CreateBucket on closed tx" _, err := bucket.CreateBucket(bucketName) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure CreateBucketIfNotExists returns expected error. testName = "CreateBucketIfNotExists on closed tx" _, err = bucket.CreateBucketIfNotExists(bucketName) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure Delete returns expected error. testName = "Delete on closed tx" err = bucket.Delete(keyName) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure DeleteBucket returns expected error. testName = "DeleteBucket on closed tx" err = bucket.DeleteBucket(bucketName) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure ForEach returns expected error. testName = "ForEach on closed tx" err = bucket.ForEach(nil) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure ForEachBucket returns expected error. testName = "ForEachBucket on closed tx" err = bucket.ForEachBucket(nil) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1727,7 +1727,7 @@ func testClosedTxInterface(tc *testContext, tx database.Tx) bool { // Ensure Put returns expected error. testName = "Put on closed tx" err = bucket.Put(keyName, []byte("test")) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1745,7 +1745,7 @@ func testClosedTxInterface(tc *testContext, tx database.Tx) bool { // Ensure Cursor.Delete returns expected error. testName = "Cursor.Delete on closed tx" err = cursor.Delete() - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1827,21 +1827,21 @@ func testClosedTxInterface(tc *testContext, tx database.Tx) bool { // Ensure StoreBlock returns expected error. testName = "StoreBlock on closed tx" err = tx.StoreBlock(block) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure FetchBlock returns expected error. testName = fmt.Sprintf("FetchBlock #%d on closed tx", i) _, err = tx.FetchBlock(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure FetchBlockHeader returns expected error. testName = fmt.Sprintf("FetchBlockHeader #%d on closed tx", i) _, err = tx.FetchBlockHeader(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1854,14 +1854,14 @@ func testClosedTxInterface(tc *testContext, tx database.Tx) bool { } allBlockRegions[i] = region _, err = tx.FetchBlockRegion(®ion) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure HasBlock returns expected error. testName = fmt.Sprintf("HasBlock #%d on closed tx", i) _, err = tx.HasBlock(blockHash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } } @@ -1873,28 +1873,28 @@ func testClosedTxInterface(tc *testContext, tx database.Tx) bool { // Ensure FetchBlocks returns expected error. testName = "FetchBlocks on closed tx" _, err = tx.FetchBlocks(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure FetchBlockHeaders returns expected error. testName = "FetchBlockHeaders on closed tx" _, err = tx.FetchBlockHeaders(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure FetchBlockRegions returns expected error. testName = "FetchBlockRegions on closed tx" _, err = tx.FetchBlockRegions(allBlockRegions) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } // Ensure HasBlocks returns expected error. testName = "HasBlocks on closed tx" _, err = tx.HasBlocks(allBlockHashes) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return false } @@ -1905,11 +1905,11 @@ func testClosedTxInterface(tc *testContext, tx database.Tx) bool { // Ensure that attempting to rollback or commit a transaction that is // already closed returns the expected error. err = tx.Rollback() - if !checkDbError(tc.t, "closed tx rollback", err, wantErrCode) { + if !checkDbError(tc.t, "closed tx rollback", err, wantErrKind) { return false } err = tx.Commit() - return checkDbError(tc.t, "closed tx commit", err, wantErrCode) + return checkDbError(tc.t, "closed tx commit", err, wantErrKind) } // testTxClosed ensures that both the metadata and block IO API functions behave diff --git a/database/ffldb/reconcile.go b/database/ffldb/reconcile.go index b6299c64d5..53d25c61ac 100644 --- a/database/ffldb/reconcile.go +++ b/database/ffldb/reconcile.go @@ -40,7 +40,7 @@ func deserializeWriteRow(writeRow []byte) (uint32, uint32, error) { str := fmt.Sprintf("metadata for write cursor does not match "+ "the expected checksum - got %d, want %d", gotChecksum, wantChecksum) - return 0, 0, makeDbErr(database.ErrCorruption, str, nil) + return 0, 0, database.MakeError(database.ErrCorruption, str) } fileNum := byteOrder.Uint32(writeRow[0:4]) @@ -65,7 +65,7 @@ func reconcileDB(pdb *db, create bool) (database.DB, error) { writeRow := tx.Metadata().Get(writeLocKeyName) if writeRow == nil { str := "write cursor does not exist" - return makeDbErr(database.ErrCorruption, str, nil) + return database.MakeError(database.ErrCorruption, str) } var err error @@ -111,7 +111,7 @@ func reconcileDB(pdb *db, create bool) (database.DB, error) { "block data is at file %d, offset %d", curFileNum, curOffset, wc.curFileNum, wc.curOffset) log.Warnf("***Database corruption detected***: %v", str) - return nil, makeDbErr(database.ErrCorruption, str, nil) + return nil, database.MakeError(database.ErrCorruption, str) } return pdb, nil diff --git a/database/ffldb/whitebox_test.go b/database/ffldb/whitebox_test.go index 27b274b1e7..c846040ab4 100644 --- a/database/ffldb/whitebox_test.go +++ b/database/ffldb/whitebox_test.go @@ -13,6 +13,7 @@ import ( "compress/bzip2" "encoding/binary" "encoding/gob" + "errors" "fmt" "hash/crc32" "os" @@ -83,19 +84,19 @@ func loadBlocks(t *testing.T, dataFile string, network wire.CurrencyNet) ([]*dcr return blocks, nil } -// checkDbError ensures the passed error is a database.Error with an error code -// that matches the passed error code. -func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool { - dbErr, ok := gotErr.(database.Error) - if !ok { +// checkDbError ensures the passed error is a database.Error that matches the +// passed error kind. +func checkDbError(t *testing.T, testName string, gotErr error, wantErr database.ErrorKind) bool { + var dbErr database.Error + if !errors.As(gotErr, &dbErr) { t.Errorf("%s: unexpected error type - got %T, want %T", - testName, gotErr, database.Error{}) + testName, gotErr, dbErr) return false } - if dbErr.ErrorCode != wantErrCode { + + if !errors.Is(dbErr, wantErr) { t.Errorf("%s: unexpected error code - got %s (%s), want %s", - testName, dbErr.ErrorCode, dbErr.Description, - wantErrCode) + testName, dbErr, dbErr.Description, wantErr) return false } @@ -119,9 +120,9 @@ func TestConvertErr(t *testing.T) { tests := []struct { err error - wantErrCode database.ErrorCode + wantErrKind database.ErrorKind }{ - {&ldberrors.ErrCorrupted{}, database.ErrCorruption}, + {&ldberrors.ErrCorrupted{Err: errors.New("corrupted ldb")}, database.ErrCorruption}, {leveldb.ErrClosed, database.ErrDbNotOpen}, {leveldb.ErrSnapshotReleased, database.ErrTxClosed}, {leveldb.ErrIterReleased, database.ErrTxClosed}, @@ -129,9 +130,9 @@ func TestConvertErr(t *testing.T) { for i, test := range tests { gotErr := convertErr("test", test.err) - if gotErr.ErrorCode != test.wantErrCode { + if !errors.Is(gotErr, test.wantErrKind) { t.Errorf("convertErr #%d unexpected error - got %v, "+ - "want %v", i, gotErr.ErrorCode, test.wantErrCode) + "want %v", i, gotErr, test.wantErrKind) continue } } @@ -155,9 +156,9 @@ func TestCornerCases(t *testing.T) { // Ensure creating a new database fails when a file exists where a // directory is needed. testName := "openDB: fail due to file at target location" - wantErrCode := database.ErrDriverSpecific + wantErrKind := database.ErrDriverSpecific idb, err := openDB(dbPath, blockDataNet, true) - if !checkDbError(t, testName, err, wantErrCode) { + if !checkDbError(t, testName, err, wantErrKind) { if err == nil { idb.Close() } @@ -198,20 +199,20 @@ func TestCornerCases(t *testing.T) { // Ensure initialization errors in the underlying database work as // expected. testName = "initDB: reinitialization" - wantErrCode = database.ErrDbNotOpen + wantErrKind = database.ErrDbNotOpen err = initDB(ldb) - if !checkDbError(t, testName, err, wantErrCode) { + if !checkDbError(t, testName, err, wantErrKind) { return } // Ensure the View handles errors in the underlying leveldb database // properly. testName = "View: underlying leveldb error" - wantErrCode = database.ErrDbNotOpen + wantErrKind = database.ErrDbNotOpen err = idb.View(func(tx database.Tx) error { return nil }) - if !checkDbError(t, testName, err, wantErrCode) { + if !checkDbError(t, testName, err, wantErrKind) { return } @@ -221,7 +222,7 @@ func TestCornerCases(t *testing.T) { err = idb.Update(func(tx database.Tx) error { return nil }) - if !checkDbError(t, testName, err, wantErrCode) { + if !checkDbError(t, testName, err, wantErrKind) { return } } @@ -443,9 +444,9 @@ func testBlockFileErrors(tc *testContext) bool { // underlying file they need to read from has been closed. err = tc.db.View(func(tx database.Tx) error { testName = "FetchBlock closed file" - wantErrCode := database.ErrDriverSpecific + wantErrKind := database.ErrDriverSpecific _, err := tx.FetchBlock(block0Hash) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return errSubTestFail } @@ -458,13 +459,13 @@ func testBlockFileErrors(tc *testContext) bool { }, } _, err = tx.FetchBlockRegion(®ions[0]) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return errSubTestFail } testName = "FetchBlockRegions closed file" _, err = tx.FetchBlockRegions(regions) - if !checkDbError(tc.t, testName, err, wantErrCode) { + if !checkDbError(tc.t, testName, err, wantErrKind) { return errSubTestFail } @@ -511,7 +512,7 @@ func testCorruption(tc *testContext) bool { tests := []struct { offset uint32 fixChecksum bool - wantErrCode database.ErrorCode + wantErrKind database.ErrorKind }{ // One of the network bytes. The checksum needs to be fixed so // the invalid network is detected. @@ -552,7 +553,7 @@ func testCorruption(tc *testContext) bool { testName := fmt.Sprintf("FetchBlock (test #%d): "+ "corruption", i) _, err := tx.FetchBlock(block0Hash) - if !checkDbError(tc.t, testName, err, test.wantErrCode) { + if !checkDbError(tc.t, testName, err, test.wantErrKind) { return errSubTestFail } @@ -629,8 +630,7 @@ func TestFailureScenarios(t *testing.T) { store.openFileFunc = func(fileNum uint32) (*lockableFile, error) { // Force error when trying to open max file num. if fileNum == ^uint32(0) { - return nil, makeDbErr(database.ErrDriverSpecific, - "test", nil) + return nil, database.MakeError(database.ErrDriverSpecific, "test") } if file, ok := tc.files[fileNum]; ok { // "Reopen" the file. @@ -656,7 +656,7 @@ func TestFailureScenarios(t *testing.T) { } str := fmt.Sprintf("file %d does not exist", fileNum) - return makeDbErr(database.ErrDriverSpecific, str, nil) + return database.MakeError(database.ErrDriverSpecific, str) } // Load the test blocks and save in the test context for use throughout