feat: prep for errors v2 (#20539)
Co-authored-by: unknown unknown <unknown@unknown>
This commit is contained in:
@@ -34,6 +34,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
### API Breaking
|
||||
|
||||
* [#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) v2 errors 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)
|
||||
|
||||
|
||||
+14
-41
@@ -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.
|
||||
|
||||
+18
-105
@@ -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
|
||||
)
|
||||
|
||||
@@ -183,16 +104,8 @@ 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" }
|
||||
|
||||
+3
-148
@@ -2,9 +2,6 @@ package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// UndefinedCodespace when we explicitly declare no codespace
|
||||
@@ -63,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.
|
||||
@@ -102,58 +99,6 @@ 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 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.
|
||||
//
|
||||
// If the wrapped error does not provide ABCICode method (ie. stdlib errors),
|
||||
@@ -166,17 +111,7 @@ func Wrap(err error, description string) error {
|
||||
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.
|
||||
@@ -187,83 +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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if received == nil {
|
||||
return false
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
+5
-199
@@ -1,212 +1,18 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
stdlib "errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
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(nil), "nil should always have no causer")
|
||||
require.False(IsOf(nil, err), "nil should always have no causer")
|
||||
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())
|
||||
}
|
||||
|
||||
const testCodespace = "testtesttest"
|
||||
|
||||
var (
|
||||
|
||||
+1
-15
@@ -1,17 +1,3 @@
|
||||
module cosmossdk.io/errors
|
||||
module cosmossdk.io/errors/v2
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // 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
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,28 +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/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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,120 +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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user