chore: errors main (#23707)
This commit is contained in:
parent
eafa0e2c9d
commit
a5661db02a
@ -31,6 +31,21 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v2.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/errors/%2Fv2.0.0)
|
||||
|
||||
### API Breaking Changes
|
||||
|
||||
* [#20402](https://github.com/cosmos/cosmos-sdk/pull/20402) Remove Grpc error codes from the error package. This is done in order to keep the dependency graph of errors minimal
|
||||
* [#20539](https://github.com/cosmos/cosmos-sdk/pull/20539) Removes `IsOf`, `Recover`, `WithType` and wrapped error. The errors package uses the go std library errors. It provides a `Wrap` and `Wrapf` to help in the migration from v1 to v2.
|
||||
|
||||
## [v1.0.1](https://github.com/cosmos/cosmos-sdk/releases/tag/errors%2Fv1.0.1)
|
||||
|
||||
### Improvements
|
||||
|
||||
* [#18918](https://github.com/cosmos/cosmos-sdk/pull/18918) Improve `IsOf` by returning earlier when the checked error is nil.
|
||||
|
||||
## [v1.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/errors%2Fv1.0.0)
|
||||
|
||||
### Features
|
||||
|
||||
* [#15989](https://github.com/cosmos/cosmos-sdk/pull/15989) Add `ErrStopIterating` for modules to use for breaking out of iteration.
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
@ -34,7 +35,8 @@ func ABCIInfo(err error, debug bool) (codespace string, code uint32, log string)
|
||||
encode = debugErrEncoder
|
||||
}
|
||||
|
||||
return abciCodespace(err), abciCode(err), encode(err)
|
||||
code, space := abciInfo(err)
|
||||
return space, code, encode(err)
|
||||
}
|
||||
|
||||
// The debugErrEncoder encodes the error with a stacktrace.
|
||||
@ -46,54 +48,25 @@ func defaultErrEncoder(err error) string {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
type coder interface {
|
||||
ABCICode() uint32
|
||||
}
|
||||
|
||||
// abciCode tests if given error contains an ABCI code and returns the value of
|
||||
// abciInfo tests if given error contains an ABCI code and returns the value of
|
||||
// it if available. This function is testing for the causer interface as well
|
||||
// and unwraps the error.
|
||||
func abciCode(err error) uint32 {
|
||||
func abciInfo(err error) (code uint32, codespace string) {
|
||||
if errIsNil(err) {
|
||||
return SuccessABCICode
|
||||
return SuccessABCICode, ""
|
||||
}
|
||||
|
||||
for {
|
||||
if c, ok := err.(coder); ok {
|
||||
return c.ABCICode()
|
||||
}
|
||||
var customErr *Error
|
||||
|
||||
if c, ok := err.(causer); ok {
|
||||
err = c.Cause()
|
||||
} else {
|
||||
return internalABCICode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type codespacer interface {
|
||||
Codespace() string
|
||||
}
|
||||
|
||||
// abciCodespace tests if given error contains a codespace and returns the value of
|
||||
// it if available. This function is testing for the causer interface as well
|
||||
// and unwraps the error.
|
||||
func abciCodespace(err error) string {
|
||||
if errIsNil(err) {
|
||||
return ""
|
||||
if errors.As(err, &customErr) {
|
||||
code = customErr.ABCICode()
|
||||
codespace = customErr.Codespace()
|
||||
} else {
|
||||
code = internalABCICode
|
||||
codespace = internalABCICodespace
|
||||
}
|
||||
|
||||
for {
|
||||
if c, ok := err.(codespacer); ok {
|
||||
return c.Codespace()
|
||||
}
|
||||
|
||||
if c, ok := err.(causer); ok {
|
||||
err = c.Cause()
|
||||
} else {
|
||||
return internalABCICodespace
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// errIsNil returns true if value represented by the given error is nil.
|
||||
|
||||
@ -3,25 +3,10 @@ package errors
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type abciTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestABCITestSuite(t *testing.T) {
|
||||
suite.Run(t, new(abciTestSuite))
|
||||
}
|
||||
|
||||
func (s *abciTestSuite) SetupSuite() {
|
||||
s.T().Parallel()
|
||||
}
|
||||
|
||||
func (s *abciTestSuite) TestABCInfo() {
|
||||
func TestABCInfo(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
err error
|
||||
debug bool
|
||||
@ -37,7 +22,7 @@ func (s *abciTestSuite) TestABCInfo() {
|
||||
wantSpace: testCodespace,
|
||||
},
|
||||
"wrapped SDK error": {
|
||||
err: Wrap(Wrap(ErrUnauthorized, "foo"), "bar"),
|
||||
err: fmt.Errorf("bar: %w", fmt.Errorf("foo: %w", ErrUnauthorized)),
|
||||
debug: false,
|
||||
wantLog: "bar: foo: unauthorized",
|
||||
wantCode: ErrUnauthorized.code,
|
||||
@ -64,93 +49,29 @@ func (s *abciTestSuite) TestABCInfo() {
|
||||
wantCode: 1,
|
||||
wantSpace: UndefinedCodespace,
|
||||
},
|
||||
// This is hard to test because of attached stacktrace. This
|
||||
// case is tested in an another test.
|
||||
// "wrapped stdlib is a full message in debug mode": {
|
||||
// err: Wrap(io.EOF, "cannot read file"),
|
||||
// debug: true,
|
||||
// wantLog: "cannot read file: EOF",
|
||||
// wantCode: 1,
|
||||
// },
|
||||
"custom error": {
|
||||
err: customErr{},
|
||||
debug: false,
|
||||
wantLog: "custom",
|
||||
wantCode: 999,
|
||||
wantSpace: "extern",
|
||||
},
|
||||
"custom error in debug mode": {
|
||||
err: customErr{},
|
||||
debug: true,
|
||||
wantLog: "custom",
|
||||
wantCode: 999,
|
||||
wantSpace: "extern",
|
||||
},
|
||||
}
|
||||
|
||||
for testName, tc := range cases {
|
||||
s.T().Run(testName, func(t *testing.T) {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
space, code, log := ABCIInfo(tc.err, tc.debug)
|
||||
s.Require().Equal(tc.wantSpace, space, testName)
|
||||
s.Require().Equal(tc.wantCode, code, testName)
|
||||
s.Require().Equal(tc.wantLog, log, testName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *abciTestSuite) TestABCIInfoStacktrace() {
|
||||
cases := map[string]struct {
|
||||
err error
|
||||
debug bool
|
||||
wantStacktrace bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
"wrapped SDK error in debug mode provides stacktrace": {
|
||||
err: Wrap(ErrUnauthorized, "wrapped"),
|
||||
debug: true,
|
||||
wantStacktrace: true,
|
||||
wantErrMsg: "wrapped: unauthorized",
|
||||
},
|
||||
"wrapped SDK error in non-debug mode does not have stacktrace": {
|
||||
err: Wrap(ErrUnauthorized, "wrapped"),
|
||||
debug: false,
|
||||
wantStacktrace: false,
|
||||
wantErrMsg: "wrapped: unauthorized",
|
||||
},
|
||||
"wrapped stdlib error in debug mode provides stacktrace": {
|
||||
err: Wrap(fmt.Errorf("stdlib"), "wrapped"),
|
||||
debug: true,
|
||||
wantStacktrace: true,
|
||||
wantErrMsg: "wrapped: stdlib",
|
||||
},
|
||||
}
|
||||
|
||||
const thisTestSrc = "cosmossdk.io/errors.(*abciTestSuite).TestABCIInfoStacktrace"
|
||||
|
||||
for testName, tc := range cases {
|
||||
s.T().Run(testName, func(t *testing.T) {
|
||||
_, _, log := ABCIInfo(tc.err, tc.debug)
|
||||
if !tc.wantStacktrace {
|
||||
s.Require().Equal(tc.wantErrMsg, log, testName)
|
||||
} else {
|
||||
s.Require().True(strings.Contains(log, thisTestSrc), testName)
|
||||
s.Require().True(strings.Contains(log, tc.wantErrMsg), testName)
|
||||
if space != tc.wantSpace {
|
||||
t.Errorf("%s: expected space %s, got %s", testName, tc.wantSpace, space)
|
||||
}
|
||||
if code != tc.wantCode {
|
||||
t.Errorf("%s: expected code %d, got %d", testName, tc.wantCode, code)
|
||||
}
|
||||
if log != tc.wantLog {
|
||||
t.Errorf("%s: expected log %s, got %s", testName, tc.wantLog, log)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *abciTestSuite) TestABCIInfoHidesStacktrace() {
|
||||
err := Wrap(ErrUnauthorized, "wrapped")
|
||||
_, _, log := ABCIInfo(err, false)
|
||||
s.Require().Equal("wrapped: unauthorized", log)
|
||||
}
|
||||
|
||||
func (s *abciTestSuite) TestABCIInfoSerializeErr() {
|
||||
func TestABCIInfoSerializeErr(t *testing.T) {
|
||||
var (
|
||||
// Create errors with stacktrace for equal comparison.
|
||||
myErrDecode = Wrap(ErrTxDecode, "test")
|
||||
myErrAddr = Wrap(ErrInvalidAddress, "tester")
|
||||
// Create errors for equal comparison.
|
||||
myErrDecode = fmt.Errorf("test: %w", ErrTxDecode)
|
||||
myErrAddr = fmt.Errorf("tester: %w", ErrInvalidAddress)
|
||||
myPanic = ErrPanic
|
||||
)
|
||||
|
||||
@ -181,18 +102,10 @@ func (s *abciTestSuite) TestABCIInfoSerializeErr() {
|
||||
},
|
||||
}
|
||||
for msg, spec := range specs {
|
||||
spec := spec
|
||||
|
||||
_, _, log := ABCIInfo(spec.src, spec.debug)
|
||||
s.Require().Equal(spec.exp, log, msg)
|
||||
if log != spec.exp {
|
||||
t.Errorf("%s: expected log %s, got %s", msg, spec.exp, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// customErr is a custom implementation of an error that provides an ABCICode
|
||||
// method.
|
||||
type customErr struct{}
|
||||
|
||||
func (customErr) Codespace() string { return "extern" }
|
||||
|
||||
func (customErr) ABCICode() uint32 { return 999 }
|
||||
|
||||
func (customErr) Error() string { return "custom" }
|
||||
|
||||
@ -1,33 +1,31 @@
|
||||
/*
|
||||
Package errors implements custom error interfaces for cosmos-sdk.
|
||||
|
||||
Error declarations should be generic and cover broad range of cases. Each
|
||||
returned error instance can wrap a generic error declaration to provide more
|
||||
details.
|
||||
|
||||
This package provides a broad range of errors declared that fits all common
|
||||
cases. If an error is very specific for an extension it can be registered outside
|
||||
of the errors package. If it will be needed my many extensions, please consider
|
||||
registering it in the errors package. To create a new error instance use Register
|
||||
function. You must provide a unique, non zero error code and a short description, for example:
|
||||
|
||||
var ErrZeroDivision = errors.Register(9241, "zero division")
|
||||
|
||||
When returning an error, you can attach to it an additional context
|
||||
information by using Wrap function, for example:
|
||||
|
||||
func safeDiv(val, div int) (int, err) {
|
||||
if div == 0 {
|
||||
return 0, errors.Wrapf(ErrZeroDivision, "cannot divide %d", val)
|
||||
}
|
||||
return val / div, nil
|
||||
}
|
||||
|
||||
The first time an error instance is wrapped a stacktrace is attached as well.
|
||||
Stacktrace information can be printed using %+v and %v formats.
|
||||
|
||||
%s is just the error message
|
||||
%+v is the full stack trace
|
||||
%v appends a compressed [filename:line] where the error was created
|
||||
*/
|
||||
// Package errors implements custom error interfaces for cosmos-sdk.
|
||||
//
|
||||
// Error declarations should be generic and cover broad range of cases. Each
|
||||
// returned error instance can wrap a generic error declaration to provide more
|
||||
// details.
|
||||
//
|
||||
// This package provides a broad range of errors declared that fits all common
|
||||
// cases. If an error is very specific for an extension it can be registered outside
|
||||
// of the errors package. If it will be needed my many extensions, please consider
|
||||
// registering it in the errors package. To create a new error instance use Register
|
||||
// function. You must provide a unique, non-zero error code and a short description, for example:
|
||||
//
|
||||
// var ErrZeroDivision = errors.Register(9241, "zero division")
|
||||
//
|
||||
// When returning an error, you can attach to it an additional context
|
||||
// information by using Wrap function, for example:
|
||||
//
|
||||
// func safeDiv(val, div int) (int, err) {
|
||||
// if div == 0 {
|
||||
// return 0, errors.Wrapf(ErrZeroDivision, "cannot divide %d", val)
|
||||
// }
|
||||
// return val / div, nil
|
||||
// }
|
||||
//
|
||||
// The first time an error instance is wrapped a stacktrace is attached as well.
|
||||
// Stacktrace information can be printed using %+v and %v formats.
|
||||
//
|
||||
// %s is just the error message
|
||||
// %+v is the full stack trace
|
||||
// %v appends a compressed [filename:line] where the error was created
|
||||
package errors
|
||||
|
||||
197
errors/errors.go
197
errors/errors.go
@ -2,24 +2,17 @@ package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
grpccodes "google.golang.org/grpc/codes"
|
||||
grpcstatus "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// UndefinedCodespace when we explicitly declare no codespace
|
||||
const UndefinedCodespace = "undefined"
|
||||
|
||||
var (
|
||||
// errInternal should never be exposed, but we reserve this code for non-specified errors
|
||||
errInternal = Register(UndefinedCodespace, 1, "internal")
|
||||
|
||||
// ErrStopIterating is used to break out of an iteration
|
||||
ErrStopIterating = Register(UndefinedCodespace, 2, "stop iterating")
|
||||
|
||||
// ErrPanic should only be set when we recovering from a panic
|
||||
// ErrPanic should only be set when we recover from a panic
|
||||
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
|
||||
)
|
||||
|
||||
@ -32,18 +25,11 @@ var (
|
||||
//
|
||||
// Use this function only during a program startup phase.
|
||||
func Register(codespace string, code uint32, description string) *Error {
|
||||
return RegisterWithGRPCCode(codespace, code, grpccodes.Unknown, description)
|
||||
}
|
||||
|
||||
// RegisterWithGRPCCode is a version of Register that associates a gRPC error
|
||||
// code with a registered error.
|
||||
func RegisterWithGRPCCode(codespace string, code uint32, grpcCode grpccodes.Code, description string) *Error {
|
||||
// TODO - uniqueness is (codespace, code) combo
|
||||
if e := getUsed(codespace, code); e != nil {
|
||||
panic(fmt.Sprintf("error with code %d is already registered: %q", code, e.desc))
|
||||
}
|
||||
|
||||
err := &Error{codespace: codespace, code: code, desc: description, grpcCode: grpcCode}
|
||||
err := &Error{codespace: codespace, code: code, desc: description}
|
||||
setUsed(err)
|
||||
|
||||
return err
|
||||
@ -74,11 +60,11 @@ func setUsed(err *Error) {
|
||||
// The server (abci app / blockchain) should only refer to registered errors
|
||||
func ABCIError(codespace string, code uint32, log string) error {
|
||||
if e := getUsed(codespace, code); e != nil {
|
||||
return Wrap(e, log)
|
||||
return fmt.Errorf("%s: %w", log, e)
|
||||
}
|
||||
// This is a unique error, will never match on .Is()
|
||||
// Use Wrap here to get a stack trace
|
||||
return Wrap(&Error{codespace: codespace, code: code, desc: "unknown"}, log)
|
||||
return fmt.Errorf("%s: %w", log, &Error{codespace: codespace, code: code, desc: "unknown"})
|
||||
}
|
||||
|
||||
// Error represents a root error.
|
||||
@ -94,7 +80,6 @@ type Error struct {
|
||||
codespace string
|
||||
code uint32
|
||||
desc string
|
||||
grpcCode grpccodes.Code
|
||||
}
|
||||
|
||||
// New is an alias for Register.
|
||||
@ -114,88 +99,22 @@ func (e Error) Codespace() string {
|
||||
return e.codespace
|
||||
}
|
||||
|
||||
// Is check if given error instance is of a given kind/type. This involves
|
||||
// unwrapping given error using the Cause method if available.
|
||||
func (e *Error) Is(err error) bool {
|
||||
// Reflect usage is necessary to correctly compare with
|
||||
// a nil implementation of an error.
|
||||
if e == nil {
|
||||
return isNilErr(err)
|
||||
}
|
||||
|
||||
for {
|
||||
if err == e {
|
||||
return true
|
||||
}
|
||||
|
||||
// If this is a collection of errors, this function must return
|
||||
// true if at least one from the group match.
|
||||
if u, ok := err.(unpacker); ok {
|
||||
for _, er := range u.Unpack() {
|
||||
if e.Is(er) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c, ok := err.(causer); ok {
|
||||
err = c.Cause()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap extends this error with an additional information.
|
||||
// It's a handy function to call Wrap with sdk errors.
|
||||
func (e *Error) Wrap(desc string) error { return Wrap(e, desc) }
|
||||
|
||||
// Wrapf extends this error with an additional information.
|
||||
// It's a handy function to call Wrapf with sdk errors.
|
||||
func (e *Error) Wrapf(desc string, args ...interface{}) error { return Wrapf(e, desc, args...) }
|
||||
|
||||
func (e *Error) GRPCStatus() *grpcstatus.Status {
|
||||
return grpcstatus.Newf(e.grpcCode, "codespace %s code %d: %s", e.codespace, e.code, e.desc)
|
||||
}
|
||||
|
||||
func isNilErr(err error) bool {
|
||||
// Reflect usage is necessary to correctly compare with
|
||||
// a nil implementation of an error.
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if reflect.ValueOf(err).Kind() == reflect.Struct {
|
||||
return false
|
||||
}
|
||||
return reflect.ValueOf(err).IsNil()
|
||||
}
|
||||
|
||||
// Wrap extends given error with an additional information.
|
||||
// Wrap extends given error with additional information.
|
||||
//
|
||||
// If the wrapped error does not provide ABCICode method (ie. stdlib errors),
|
||||
// If the wrapped error does not provide ABCICode method (i.e. stdlib errors),
|
||||
// it will be labeled as internal error.
|
||||
//
|
||||
// If err is nil, this returns nil, avoiding the need for an if statement when
|
||||
// wrapping a error returned at the end of a function
|
||||
// wrapping an error returned at the end of a function
|
||||
func Wrap(err error, description string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If this error does not carry the stacktrace information yet, attach
|
||||
// one. This should be done only once per error at the lowest frame
|
||||
// possible (most inner wrap).
|
||||
if stackTrace(err) == nil {
|
||||
err = errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &wrappedError{
|
||||
parent: err,
|
||||
msg: description,
|
||||
}
|
||||
return fmt.Errorf("%s: %w", description, err)
|
||||
}
|
||||
|
||||
// Wrapf extends given error with an additional information.
|
||||
// Wrapf extends given error with additional information.
|
||||
//
|
||||
// This function works like Wrap function with additional functionality of
|
||||
// formatting the input as specified.
|
||||
@ -203,101 +122,3 @@ func Wrapf(err error, format string, args ...interface{}) error {
|
||||
desc := fmt.Sprintf(format, args...)
|
||||
return Wrap(err, desc)
|
||||
}
|
||||
|
||||
type wrappedError struct {
|
||||
// This error layer description.
|
||||
msg string
|
||||
// The underlying error that triggered this one.
|
||||
parent error
|
||||
}
|
||||
|
||||
func (e *wrappedError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.msg, e.parent.Error())
|
||||
}
|
||||
|
||||
func (e *wrappedError) Cause() error {
|
||||
return e.parent
|
||||
}
|
||||
|
||||
// Is reports whether any error in e's chain matches a target.
|
||||
func (e *wrappedError) Is(target error) bool {
|
||||
if e == target {
|
||||
return true
|
||||
}
|
||||
|
||||
w := e.Cause()
|
||||
for {
|
||||
if w == target {
|
||||
return true
|
||||
}
|
||||
|
||||
x, ok := w.(causer)
|
||||
if ok {
|
||||
w = x.Cause()
|
||||
}
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap implements the built-in errors.Unwrap
|
||||
func (e *wrappedError) Unwrap() error {
|
||||
return e.parent
|
||||
}
|
||||
|
||||
// GRPCStatus gets the gRPC status from the wrapped error or returns an unknown gRPC status.
|
||||
func (e *wrappedError) GRPCStatus() *grpcstatus.Status {
|
||||
w := e.Cause()
|
||||
for {
|
||||
if hasStatus, ok := w.(interface {
|
||||
GRPCStatus() *grpcstatus.Status
|
||||
}); ok {
|
||||
status := hasStatus.GRPCStatus()
|
||||
return grpcstatus.New(status.Code(), fmt.Sprintf("%s: %s", status.Message(), e.msg))
|
||||
}
|
||||
|
||||
x, ok := w.(causer)
|
||||
if ok {
|
||||
w = x.Cause()
|
||||
}
|
||||
if x == nil {
|
||||
return grpcstatus.New(grpccodes.Unknown, e.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recover captures a panic and stop its propagation. If panic happens it is
|
||||
// transformed into a ErrPanic instance and assigned to given error. Call this
|
||||
// function using defer in order to work as expected.
|
||||
func Recover(err *error) {
|
||||
if r := recover(); r != nil {
|
||||
*err = Wrapf(ErrPanic, "%v", r)
|
||||
}
|
||||
}
|
||||
|
||||
// WithType is a helper to augment an error with a corresponding type message
|
||||
func WithType(err error, obj interface{}) error {
|
||||
return Wrap(err, fmt.Sprintf("%T", obj))
|
||||
}
|
||||
|
||||
// IsOf checks if a received error is caused by one of the target errors.
|
||||
// It extends the errors.Is functionality to a list of errors.
|
||||
func IsOf(received error, targets ...error) bool {
|
||||
for _, t := range targets {
|
||||
if errors.Is(received, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// causer is an interface implemented by an error that supports wrapping. Use
|
||||
// it to test if an error wraps another error instance.
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
type unpacker interface {
|
||||
Unpack() []error
|
||||
}
|
||||
|
||||
@ -1,229 +1,18 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
stdlib "errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"google.golang.org/grpc/codes"
|
||||
grpcstatus "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type errorsTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestErrorsTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(errorsTestSuite))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) SetupSuite() {
|
||||
s.T().Parallel()
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestCause() {
|
||||
std := stdlib.New("this is a stdlib error")
|
||||
|
||||
cases := map[string]struct {
|
||||
err error
|
||||
root error
|
||||
}{
|
||||
"Errors are self-causing": {
|
||||
err: ErrUnauthorized,
|
||||
root: ErrUnauthorized,
|
||||
},
|
||||
"Wrap reveals root cause": {
|
||||
err: Wrap(ErrUnauthorized, "foo"),
|
||||
root: ErrUnauthorized,
|
||||
},
|
||||
"Cause works for stderr as root": {
|
||||
err: Wrap(std, "Some helpful text"),
|
||||
root: std,
|
||||
},
|
||||
func TestABCIError(t *testing.T) {
|
||||
if err := ABCIError(testCodespace, 2, "custom"); err.Error() != "custom: tx parse error" {
|
||||
t.Errorf("expected error message: custom: tx parse error, got: %v", err.Error())
|
||||
}
|
||||
|
||||
for testName, tc := range cases {
|
||||
s.Require().Equal(tc.root, errors.Cause(tc.err), testName)
|
||||
if err := ABCIError("unknown", 1, "custom"); err.Error() != "custom: unknown" {
|
||||
t.Errorf("expected error message: custom: unknown, got: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestErrorIs() {
|
||||
cases := map[string]struct {
|
||||
a *Error
|
||||
b error
|
||||
wantIs bool
|
||||
}{
|
||||
"instance of the same error": {
|
||||
a: ErrUnauthorized,
|
||||
b: ErrUnauthorized,
|
||||
wantIs: true,
|
||||
},
|
||||
"two different coded errors": {
|
||||
a: ErrUnauthorized,
|
||||
b: ErrOutOfGas,
|
||||
wantIs: false,
|
||||
},
|
||||
"successful comparison to a wrapped error": {
|
||||
a: ErrUnauthorized,
|
||||
b: Wrap(ErrUnauthorized, "gone"),
|
||||
wantIs: true,
|
||||
},
|
||||
"unsuccessful comparison to a wrapped error": {
|
||||
a: ErrUnauthorized,
|
||||
b: Wrap(ErrInsufficientFee, "too big"),
|
||||
wantIs: false,
|
||||
},
|
||||
"not equal to stdlib error": {
|
||||
a: ErrUnauthorized,
|
||||
b: fmt.Errorf("stdlib error"),
|
||||
wantIs: false,
|
||||
},
|
||||
"not equal to a wrapped stdlib error": {
|
||||
a: ErrUnauthorized,
|
||||
b: Wrap(fmt.Errorf("stdlib error"), "wrapped"),
|
||||
wantIs: false,
|
||||
},
|
||||
"nil is nil": {
|
||||
a: nil,
|
||||
b: nil,
|
||||
wantIs: true,
|
||||
},
|
||||
"nil is any error nil": {
|
||||
a: nil,
|
||||
b: (*customError)(nil),
|
||||
wantIs: true,
|
||||
},
|
||||
"nil is not not-nil": {
|
||||
a: nil,
|
||||
b: ErrUnauthorized,
|
||||
wantIs: false,
|
||||
},
|
||||
"not-nil is not nil": {
|
||||
a: ErrUnauthorized,
|
||||
b: nil,
|
||||
wantIs: false,
|
||||
},
|
||||
}
|
||||
for testName, tc := range cases {
|
||||
s.Require().Equal(tc.wantIs, tc.a.Is(tc.b), testName)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestIsOf() {
|
||||
require := s.Require()
|
||||
|
||||
var errNil *Error
|
||||
err := ErrInvalidAddress
|
||||
errW := Wrap(ErrLogic, "more info")
|
||||
|
||||
require.False(IsOf(errNil), "nil error should always have no causer")
|
||||
require.False(IsOf(errNil, err), "nil error should always have no causer")
|
||||
|
||||
require.False(IsOf(err))
|
||||
require.False(IsOf(err, nil))
|
||||
require.False(IsOf(err, ErrLogic))
|
||||
|
||||
require.True(IsOf(errW, ErrLogic))
|
||||
require.True(IsOf(errW, err, ErrLogic))
|
||||
require.True(IsOf(errW, nil, errW), "error should much itself")
|
||||
err2 := errors.New("other error")
|
||||
require.True(IsOf(err2, nil, err2), "error should much itself")
|
||||
}
|
||||
|
||||
type customError struct{}
|
||||
|
||||
func (customError) Error() string {
|
||||
return "custom error"
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestWrapEmpty() {
|
||||
s.Require().Nil(Wrap(nil, "wrapping <nil>"))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestWrappedIs() {
|
||||
require := s.Require()
|
||||
err := Wrap(ErrTxTooLarge, "context")
|
||||
require.True(stdlib.Is(err, ErrTxTooLarge))
|
||||
|
||||
err = Wrap(err, "more context")
|
||||
require.True(stdlib.Is(err, ErrTxTooLarge))
|
||||
|
||||
err = Wrap(err, "even more context")
|
||||
require.True(stdlib.Is(err, ErrTxTooLarge))
|
||||
|
||||
err = Wrap(ErrInsufficientFee, "...")
|
||||
require.False(stdlib.Is(err, ErrTxTooLarge))
|
||||
|
||||
errs := stdlib.New("other")
|
||||
require.True(stdlib.Is(errs, errs))
|
||||
|
||||
errw := &wrappedError{"msg", errs}
|
||||
require.True(errw.Is(errw), "should match itself")
|
||||
|
||||
require.True(stdlib.Is(ErrInsufficientFee.Wrap("wrapped"), ErrInsufficientFee))
|
||||
require.True(IsOf(ErrInsufficientFee.Wrap("wrapped"), ErrInsufficientFee))
|
||||
require.True(stdlib.Is(ErrInsufficientFee.Wrapf("wrapped"), ErrInsufficientFee))
|
||||
require.True(IsOf(ErrInsufficientFee.Wrapf("wrapped"), ErrInsufficientFee))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestWrappedIsMultiple() {
|
||||
errTest := errors.New("test error")
|
||||
errTest2 := errors.New("test error 2")
|
||||
err := Wrap(errTest2, Wrap(errTest, "some random description").Error())
|
||||
s.Require().True(stdlib.Is(err, errTest2))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestWrappedIsFail() {
|
||||
errTest := errors.New("test error")
|
||||
errTest2 := errors.New("test error 2")
|
||||
err := Wrap(errTest2, Wrap(errTest, "some random description").Error())
|
||||
s.Require().False(stdlib.Is(err, errTest))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestWrappedUnwrap() {
|
||||
errTest := errors.New("test error")
|
||||
err := Wrap(errTest, "some random description")
|
||||
s.Require().Equal(errTest, stdlib.Unwrap(err))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestWrappedUnwrapMultiple() {
|
||||
errTest := errors.New("test error")
|
||||
errTest2 := errors.New("test error 2")
|
||||
err := Wrap(errTest2, Wrap(errTest, "some random description").Error())
|
||||
s.Require().Equal(errTest2, stdlib.Unwrap(err))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestWrappedUnwrapFail() {
|
||||
errTest := errors.New("test error")
|
||||
errTest2 := errors.New("test error 2")
|
||||
err := Wrap(errTest2, Wrap(errTest, "some random description").Error())
|
||||
s.Require().NotEqual(errTest, stdlib.Unwrap(err))
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestABCIError() {
|
||||
s.Require().Equal("custom: tx parse error", ABCIError(testCodespace, 2, "custom").Error())
|
||||
s.Require().Equal("custom: unknown", ABCIError("unknown", 1, "custom").Error())
|
||||
}
|
||||
|
||||
func (s *errorsTestSuite) TestGRPCStatus() {
|
||||
s.Require().Equal(codes.Unknown, grpcstatus.Code(errInternal))
|
||||
s.Require().Equal(codes.NotFound, grpcstatus.Code(ErrNotFound))
|
||||
|
||||
status, ok := grpcstatus.FromError(ErrNotFound)
|
||||
s.Require().True(ok)
|
||||
s.Require().Equal("codespace testtesttest code 38: not found", status.Message())
|
||||
|
||||
// test wrapping
|
||||
s.Require().Equal(codes.Unimplemented, grpcstatus.Code(ErrNotSupported.Wrap("test")))
|
||||
s.Require().Equal(codes.FailedPrecondition, grpcstatus.Code(ErrConflict.Wrapf("test %s", "foo")))
|
||||
|
||||
status, ok = grpcstatus.FromError(ErrNotFound.Wrap("test"))
|
||||
s.Require().True(ok)
|
||||
s.Require().Equal("codespace testtesttest code 38: not found: test", status.Message())
|
||||
}
|
||||
|
||||
const testCodespace = "testtesttest"
|
||||
|
||||
var (
|
||||
@ -252,8 +41,5 @@ var (
|
||||
ErrUnknownExtensionOptions = Register(testCodespace, 31, "unknown extension options")
|
||||
ErrPackAny = Register(testCodespace, 33, "failed packing protobuf message to Any")
|
||||
ErrLogic = Register(testCodespace, 35, "internal logic error")
|
||||
ErrConflict = RegisterWithGRPCCode(testCodespace, 36, codes.FailedPrecondition, "conflict")
|
||||
ErrNotSupported = RegisterWithGRPCCode(testCodespace, 37, codes.Unimplemented, "feature not supported")
|
||||
ErrNotFound = RegisterWithGRPCCode(testCodespace, 38, codes.NotFound, "not found")
|
||||
ErrIO = Register(testCodespace, 39, "Internal IO error")
|
||||
)
|
||||
|
||||
@ -1,23 +1,5 @@
|
||||
module cosmossdk.io/errors
|
||||
module cosmossdk.io/errors/v2
|
||||
|
||||
go 1.20
|
||||
// NOTE: this go.mod should have zero dependencies.
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
google.golang.org/grpc v1.55.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
go 1.22
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@ -1,12 +0,0 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// AssertNil panics on error
|
||||
// Should be only used with interface methods, which require return error, but the
|
||||
// error is always nil
|
||||
func AssertNil(err error) {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("logic error - this should never happen. %w", err))
|
||||
}
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func matchesFunc(f errors.Frame, prefixes ...string) bool {
|
||||
fn := funcName(f)
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(fn, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// funcName returns the name of this function, if known.
|
||||
func funcName(f errors.Frame) string {
|
||||
// this looks a bit like magic, but follows example here:
|
||||
// https://github.com/pkg/errors/blob/v0.8.1/stack.go#L43-L50
|
||||
pc := uintptr(f) - 1
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return fn.Name()
|
||||
}
|
||||
|
||||
func fileLine(f errors.Frame) (string, int) {
|
||||
// this looks a bit like magic, but follows example here:
|
||||
// https://github.com/pkg/errors/blob/v0.8.1/stack.go#L14-L27
|
||||
// as this is where we get the Frames
|
||||
pc := uintptr(f) - 1
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return "unknown", 0
|
||||
}
|
||||
return fn.FileLine(pc)
|
||||
}
|
||||
|
||||
func trimInternal(st errors.StackTrace) errors.StackTrace {
|
||||
// trim our internal parts here
|
||||
// manual error creation, or runtime for caught panics
|
||||
for matchesFunc(st[0],
|
||||
// where we create errors
|
||||
"cosmossdk.io/errors.Wrap",
|
||||
"cosmossdk.io/errors.Wrapf",
|
||||
"cosmossdk.io/errors.WithType",
|
||||
// runtime are added on panics
|
||||
"runtime.",
|
||||
// _test is defined in coverage tests, causing failure
|
||||
// "/_test/"
|
||||
) {
|
||||
st = st[1:]
|
||||
}
|
||||
// trim out outer wrappers (runtime.goexit and test library if present)
|
||||
for l := len(st) - 1; l > 0 && matchesFunc(st[l], "runtime.", "testing."); l-- {
|
||||
st = st[:l]
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func writeSimpleFrame(s io.Writer, f errors.Frame) {
|
||||
file, line := fileLine(f)
|
||||
// cut file at "github.com/"
|
||||
// TODO: generalize better for other hosts?
|
||||
chunks := strings.SplitN(file, "github.com/", 2)
|
||||
if len(chunks) == 2 {
|
||||
file = chunks[1]
|
||||
}
|
||||
_, _ = fmt.Fprintf(s, " [%s:%d]", file, line)
|
||||
}
|
||||
|
||||
// Format works like pkg/errors, with additions.
|
||||
// %s is just the error message
|
||||
// %+v is the full stack trace
|
||||
// %v appends a compressed [filename:line] where the error
|
||||
//
|
||||
// was created
|
||||
//
|
||||
// Inspired by https://github.com/pkg/errors/blob/v0.8.1/errors.go#L162-L176
|
||||
func (e *wrappedError) Format(s fmt.State, verb rune) {
|
||||
// normal output here....
|
||||
if verb != 'v' {
|
||||
_, _ = fmt.Fprint(s, e.Error())
|
||||
return
|
||||
}
|
||||
// work with the stack trace... whole or part
|
||||
stack := trimInternal(stackTrace(e))
|
||||
if s.Flag('+') {
|
||||
_, _ = fmt.Fprintf(s, "%+v\n", stack)
|
||||
_, _ = fmt.Fprint(s, e.Error())
|
||||
} else {
|
||||
_, _ = fmt.Fprint(s, e.Error())
|
||||
writeSimpleFrame(s, stack[0])
|
||||
}
|
||||
}
|
||||
|
||||
// stackTrace returns the first found stack trace frame carried by given error
|
||||
// or any wrapped error. It returns nil if no stack trace is found.
|
||||
func stackTrace(err error) errors.StackTrace {
|
||||
type stackTracer interface {
|
||||
StackTrace() errors.StackTrace
|
||||
}
|
||||
|
||||
for {
|
||||
if st, ok := err.(stackTracer); ok {
|
||||
return st.StackTrace()
|
||||
}
|
||||
|
||||
if c, ok := err.(causer); ok {
|
||||
err = c.Cause()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *errorsTestSuite) TestStackTrace() {
|
||||
cases := map[string]struct {
|
||||
err error
|
||||
wantError string
|
||||
}{
|
||||
"New gives us a stacktrace": {
|
||||
err: Wrap(ErrNoSignatures, "name"),
|
||||
wantError: "name: no signatures supplied",
|
||||
},
|
||||
"Wrapping stderr gives us a stacktrace": {
|
||||
err: Wrap(fmt.Errorf("foo"), "standard"),
|
||||
wantError: "standard: foo",
|
||||
},
|
||||
"Wrapping pkg/errors gives us clean stacktrace": {
|
||||
err: Wrap(errors.New("bar"), "pkg"),
|
||||
wantError: "pkg: bar",
|
||||
},
|
||||
"Wrapping inside another function is still clean": {
|
||||
err: Wrap(fmt.Errorf("indirect"), "do the do"),
|
||||
wantError: "do the do: indirect",
|
||||
},
|
||||
}
|
||||
|
||||
// Wrapping code is unwanted in the errors stack trace.
|
||||
unwantedSrc := []string{
|
||||
"cosmossdk.io/errors.Wrap\n",
|
||||
"cosmossdk.io/errors.Wrapf\n",
|
||||
"runtime.goexit\n",
|
||||
}
|
||||
const thisTestSrc = "errors/stacktrace_test.go"
|
||||
|
||||
for _, tc := range cases {
|
||||
s.Require().True(reflect.DeepEqual(tc.err.Error(), tc.wantError))
|
||||
s.Require().NotNil(stackTrace(tc.err))
|
||||
fullStack := fmt.Sprintf("%+v", tc.err)
|
||||
s.Require().True(strings.Contains(fullStack, thisTestSrc))
|
||||
s.Require().True(strings.Contains(fullStack, tc.wantError))
|
||||
|
||||
for _, src := range unwantedSrc {
|
||||
if strings.Contains(fullStack, src) {
|
||||
s.T().Logf("Stack trace below\n----%s\n----", fullStack)
|
||||
s.T().Logf("full stack contains unwanted source file path: %q", src)
|
||||
}
|
||||
}
|
||||
|
||||
tinyStack := fmt.Sprintf("%v", tc.err)
|
||||
s.Require().True(strings.HasPrefix(tinyStack, tc.wantError))
|
||||
s.Require().False(strings.Contains(tinyStack, "\n"))
|
||||
// contains a link to where it was created, which must
|
||||
// be here, not the Wrap() function
|
||||
s.Require().True(strings.Contains(tinyStack, thisTestSrc))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user