From f29524922bfee4bf03f9c426c716e639cac63ea9 Mon Sep 17 00:00:00 2001 From: lysu Date: Fri, 7 Sep 2018 13:52:50 +0800 Subject: [PATCH 01/59] make tidb adaptor in errors. (#2) --- errors.go | 130 +++++++++++++++++++++++++++---------- errors_test.go | 166 +++++++++++++++++++++++++++++++++++++++++++++--- format_test.go | 55 ++++++++-------- group.go | 33 ++++++++++ juju_adaptor.go | 76 ++++++++++++++++++++++ stack.go | 6 ++ stack_test.go | 23 ++++--- 7 files changed, 407 insertions(+), 82 deletions(-) create mode 100644 group.go create mode 100644 juju_adaptor.go diff --git a/errors.go b/errors.go index 842ee804..f809e707 100644 --- a/errors.go +++ b/errors.go @@ -64,14 +64,9 @@ // // Retrieving the stack trace of an error or wrapper // -// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are -// invoked. This information can be retrieved with the following interface. -// -// type stackTracer interface { -// StackTrace() errors.StackTrace -// } -// -// Where errors.StackTrace is defined as +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are invoked. +// This information can be retrieved with the StackTracer interface that returns +// a StackTrace. Where errors.StackTrace is defined as // // type StackTrace []Frame // @@ -79,15 +74,12 @@ // the fmt.Formatter interface that can be used for printing information about // the stack trace of this error. For example: // -// if err, ok := err.(stackTracer); ok { -// for _, f := range err.StackTrace() { +// if stacked := errors.GetStackTracer(err); stacked != nil { +// for _, f := range stacked.StackTrace() { // fmt.Printf("%+s:%d", f) // } // } // -// stackTracer interface is not exported by this package, but is considered a part -// of stable public API. -// // See the documentation for Frame.Format for more details. package errors @@ -115,6 +107,21 @@ func Errorf(format string, args ...interface{}) error { } } +// StackTraceAware is an optimization to avoid repetitive traversals of an error chain. +// HasStack checks for this marker first. +// Annotate/Wrap and Annotatef/Wrapf will produce this marker. +type StackTraceAware interface { + HasStack() bool +} + +// HasStack tells whether a StackTracer exists in the error chain +func HasStack(err error) bool { + if errWithStack, ok := err.(StackTraceAware); ok { + return errWithStack.HasStack() + } + return GetStackTracer(err) != nil +} + // fundamental is an error that has a message and a stack, but no caller. type fundamental struct { msg string @@ -145,12 +152,38 @@ func WithStack(err error) error { if err == nil { return nil } + return &withStack{ err, callers(), } } +// AddStack is similar to WithStack. +// However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. +func AddStack(err error) error { + if HasStack(err) { + return err + } + return WithStack(err) +} + +// GetStackTracer will return the first StackTracer in the causer chain. +// This function is used by AddStack to avoid creating redundant stack traces. +// +// You can also use the StackTracer interface on the returned error to get the stack trace. +func GetStackTracer(origErr error) StackTracer { + var stacked StackTracer + WalkDeep(origErr, func(err error) bool { + if stackTracer, ok := err.(StackTracer); ok { + stacked = stackTracer + return true + } + return false + }) + return stacked +} + type withStack struct { error *stack @@ -175,15 +208,19 @@ func (w *withStack) Format(s fmt.State, verb rune) { } // Wrap returns an error annotating err with a stack trace -// at the point Wrap is called, and the supplied message. -// If err is nil, Wrap returns nil. +// at the point Annotate is called, and the supplied message. +// If err is nil, Annotate returns nil. +// +// Deprecated: use Annotate instead func Wrap(err error, message string) error { if err == nil { return nil } + hasStack := HasStack(err) err = &withMessage{ - cause: err, - msg: message, + cause: err, + msg: message, + causeHasStack: hasStack, } return &withStack{ err, @@ -192,15 +229,19 @@ func Wrap(err error, message string) error { } // Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is call, and the format specifier. -// If err is nil, Wrapf returns nil. +// at the point Annotatef is call, and the format specifier. +// If err is nil, Annotatef returns nil. +// +// Deprecated: use Annotatef instead func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } + hasStack := HasStack(err) err = &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, } return &withStack{ err, @@ -215,18 +256,21 @@ func WithMessage(err error, message string) error { return nil } return &withMessage{ - cause: err, - msg: message, + cause: err, + msg: message, + causeHasStack: HasStack(err), } } type withMessage struct { - cause error - msg string + cause error + msg string + causeHasStack bool } -func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } -func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) HasStack() bool { return w.causeHasStack } func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { @@ -254,16 +298,34 @@ func (w *withMessage) Format(s fmt.State, verb rune) { // be returned. If the error is nil, nil will be returned without further // investigation. func Cause(err error) error { + cause := Unwrap(err) + if cause == nil { + return err + } + return Cause(cause) +} + +// Unwrap uses causer to return the next error in the chain or nil. +// This goes one-level deeper, whereas Cause goes as far as possible +func Unwrap(err error) error { type causer interface { Cause() error } + if unErr, ok := err.(causer); ok { + return unErr.Cause() + } + return nil +} - for err != nil { - cause, ok := err.(causer) - if !ok { - break +// Find an error in the chain that matches a test function +func Find(origErr error, test func(error) bool) error { + var foundErr error + WalkDeep(origErr, func(err error) bool { + if test(err) { + foundErr = err + return true } - err = cause.Cause() - } - return err + return false + }) + return foundErr } diff --git a/errors_test.go b/errors_test.go index c4e6eef6..7f5e225f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "reflect" + "strconv" "testing" ) @@ -28,7 +29,7 @@ func TestNew(t *testing.T) { } func TestWrapNil(t *testing.T) { - got := Wrap(nil, "no error") + got := Annotate(nil, "no error") if got != nil { t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) } @@ -41,11 +42,11 @@ func TestWrap(t *testing.T) { want string }{ {io.EOF, "read error", "read error: EOF"}, - {Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + {Annotate(io.EOF, "read error"), "client error", "client error: read error: EOF"}, } for _, tt := range tests { - got := Wrap(tt.err, tt.message).Error() + got := Annotate(tt.err, tt.message).Error() if got != tt.want { t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) } @@ -79,7 +80,7 @@ func TestCause(t *testing.T) { want: io.EOF, }, { // caused error returns cause - err: Wrap(io.EOF, "ignored"), + err: Annotate(io.EOF, "ignored"), want: io.EOF, }, { err: x, // return from errors.New @@ -96,6 +97,12 @@ func TestCause(t *testing.T) { }, { WithStack(io.EOF), io.EOF, + }, { + AddStack(nil), + nil, + }, { + AddStack(io.EOF), + io.EOF, }} for i, tt := range tests { @@ -107,7 +114,7 @@ func TestCause(t *testing.T) { } func TestWrapfNil(t *testing.T) { - got := Wrapf(nil, "no error") + got := Annotate(nil, "no error") if got != nil { t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) } @@ -120,12 +127,12 @@ func TestWrapf(t *testing.T) { want string }{ {io.EOF, "read error", "read error: EOF"}, - {Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, - {Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, + {Annotatef(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, + {Annotatef(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, } for _, tt := range tests { - got := Wrapf(tt.err, tt.message).Error() + got := Annotatef(tt.err, tt.message).Error() if got != tt.want { t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) } @@ -154,6 +161,10 @@ func TestWithStackNil(t *testing.T) { if got != nil { t.Errorf("WithStack(nil): got %#v, expected nil", got) } + got = AddStack(nil) + if got != nil { + t.Errorf("AddStack(nil): got %#v, expected nil", got) + } } func TestWithStack(t *testing.T) { @@ -173,6 +184,50 @@ func TestWithStack(t *testing.T) { } } +func TestAddStack(t *testing.T) { + tests := []struct { + err error + want string + }{ + {io.EOF, "EOF"}, + {AddStack(io.EOF), "EOF"}, + } + + for _, tt := range tests { + got := AddStack(tt.err).Error() + if got != tt.want { + t.Errorf("AddStack(%v): got: %v, want %v", tt.err, got, tt.want) + } + } +} + +func TestGetStackTracer(t *testing.T) { + orig := io.EOF + if GetStackTracer(orig) != nil { + t.Errorf("GetStackTracer: got: %v, want %v", GetStackTracer(orig), nil) + } + stacked := AddStack(orig) + if GetStackTracer(stacked).(error) != stacked { + t.Errorf("GetStackTracer(stacked): got: %v, want %v", GetStackTracer(stacked), stacked) + } + final := AddStack(stacked) + if GetStackTracer(final).(error) != stacked { + t.Errorf("GetStackTracer(final): got: %v, want %v", GetStackTracer(final), stacked) + } +} + +func TestAddStackDedup(t *testing.T) { + stacked := WithStack(io.EOF) + err := AddStack(stacked) + if err != stacked { + t.Errorf("AddStack: got: %+v, want %+v", err, stacked) + } + err = WithStack(stacked) + if err == stacked { + t.Errorf("WithStack: got: %v, don't want %v", err, stacked) + } +} + func TestWithMessageNil(t *testing.T) { got := WithMessage(nil, "no error") if got != nil { @@ -209,12 +264,14 @@ func TestErrorEquality(t *testing.T) { errors.New("EOF"), New("EOF"), Errorf("EOF"), - Wrap(io.EOF, "EOF"), - Wrapf(io.EOF, "EOF%d", 2), + Annotate(io.EOF, "EOF"), + Annotatef(io.EOF, "EOF%d", 2), WithMessage(nil, "whoops"), WithMessage(io.EOF, "whoops"), WithStack(io.EOF), WithStack(nil), + AddStack(io.EOF), + AddStack(nil), } for i := range vals { @@ -223,3 +280,92 @@ func TestErrorEquality(t *testing.T) { } } } + +func TestFind(t *testing.T) { + eNew := errors.New("error") + wrapped := Annotate(nilError{}, "nil") + tests := []struct { + err error + finder func(error) bool + found error + }{ + {io.EOF, func(_ error) bool { return true }, io.EOF}, + {io.EOF, func(_ error) bool { return false }, nil}, + {io.EOF, func(err error) bool { return err == io.EOF }, io.EOF}, + {io.EOF, func(err error) bool { return err != io.EOF }, nil}, + + {eNew, func(err error) bool { return true }, eNew}, + {eNew, func(err error) bool { return false }, nil}, + + {nilError{}, func(err error) bool { return true }, nilError{}}, + {nilError{}, func(err error) bool { return false }, nil}, + {nilError{}, func(err error) bool { _, ok := err.(nilError); return ok }, nilError{}}, + + {wrapped, func(err error) bool { return true }, wrapped}, + {wrapped, func(err error) bool { return false }, nil}, + {wrapped, func(err error) bool { _, ok := err.(nilError); return ok }, nilError{}}, + } + + for _, tt := range tests { + got := Find(tt.err, tt.finder) + if got != tt.found { + t.Errorf("WithMessage(%v): got: %q, want %q", tt.err, got, tt.found) + } + } +} + +type errWalkTest struct { + cause error + sub []error + v int +} + +func (e *errWalkTest) Error() string { + return strconv.Itoa(e.v) +} + +func (e *errWalkTest) Cause() error { + return e.cause +} + +func (e *errWalkTest) Errors() []error { + return e.sub +} + +func testFind(err error, v int) bool { + return WalkDeep(err, func(err error) bool { + e := err.(*errWalkTest) + return e.v == v + }) +} + +func TestWalkDeep(t *testing.T) { + err := &errWalkTest{ + sub: []error{ + &errWalkTest{ + v: 10, + cause: &errWalkTest{v: 11}, + }, + &errWalkTest{ + v: 20, + cause: &errWalkTest{v: 21, cause: &errWalkTest{v: 22}}, + }, + &errWalkTest{ + v: 30, + cause: &errWalkTest{v: 31}, + }, + }, + } + + if !testFind(err, 11) { + t.Errorf("not found in first cause chain") + } + + if !testFind(err, 22) { + t.Errorf("not found in siblings") + } + + if testFind(err, 32) { + t.Errorf("found not exists") + } +} diff --git a/format_test.go b/format_test.go index c2eef5f0..30275a3f 100644 --- a/format_test.go +++ b/format_test.go @@ -71,45 +71,45 @@ func TestFormatWrap(t *testing.T) { format string want string }{{ - Wrap(New("error"), "error2"), + Annotate(New("error"), "error2"), "%s", "error2: error", }, { - Wrap(New("error"), "error2"), + Annotate(New("error"), "error2"), "%v", "error2: error", }, { - Wrap(New("error"), "error2"), + Annotate(New("error"), "error2"), "%+v", "error\n" + "github.com/pkg/errors.TestFormatWrap\n" + "\t.+/github.com/pkg/errors/format_test.go:82", }, { - Wrap(io.EOF, "error"), + Annotate(io.EOF, "error"), "%s", "error: EOF", }, { - Wrap(io.EOF, "error"), + Annotate(io.EOF, "error"), "%v", "error: EOF", }, { - Wrap(io.EOF, "error"), + Annotate(io.EOF, "error"), "%+v", "EOF\n" + "error\n" + "github.com/pkg/errors.TestFormatWrap\n" + "\t.+/github.com/pkg/errors/format_test.go:96", }, { - Wrap(Wrap(io.EOF, "error1"), "error2"), + Annotate(Annotate(io.EOF, "error1"), "error2"), "%+v", "EOF\n" + "error1\n" + "github.com/pkg/errors.TestFormatWrap\n" + "\t.+/github.com/pkg/errors/format_test.go:103\n", }, { - Wrap(New("error with space"), "context"), + Annotate(New("error with space"), "context"), "%q", - `"context: error with space"`, + `context: error with space`, }} for i, tt := range tests { @@ -123,30 +123,30 @@ func TestFormatWrapf(t *testing.T) { format string want string }{{ - Wrapf(io.EOF, "error%d", 2), + Annotatef(io.EOF, "error%d", 2), "%s", "error2: EOF", }, { - Wrapf(io.EOF, "error%d", 2), + Annotatef(io.EOF, "error%d", 2), "%v", "error2: EOF", }, { - Wrapf(io.EOF, "error%d", 2), + Annotatef(io.EOF, "error%d", 2), "%+v", "EOF\n" + "error2\n" + "github.com/pkg/errors.TestFormatWrapf\n" + "\t.+/github.com/pkg/errors/format_test.go:134", }, { - Wrapf(New("error"), "error%d", 2), + Annotatef(New("error"), "error%d", 2), "%s", "error2: error", }, { - Wrapf(New("error"), "error%d", 2), + Annotatef(New("error"), "error%d", 2), "%v", "error2: error", }, { - Wrapf(New("error"), "error%d", 2), + Annotatef(New("error"), "error%d", 2), "%+v", "error\n" + "github.com/pkg/errors.TestFormatWrapf\n" + @@ -202,7 +202,7 @@ func TestFormatWithStack(t *testing.T) { "github.com/pkg/errors.TestFormatWithStack\n" + "\t.+/github.com/pkg/errors/format_test.go:197"}, }, { - WithStack(WithStack(Wrapf(io.EOF, "message"))), + WithStack(WithStack(Annotatef(io.EOF, "message"))), "%+v", []string{"EOF", "message", @@ -269,7 +269,7 @@ func TestFormatWithMessage(t *testing.T) { "%+v", []string{"EOF", "addition1", "addition2"}, }, { - Wrap(WithMessage(io.EOF, "error1"), "error2"), + Annotate(WithMessage(io.EOF, "error1"), "error2"), "%+v", []string{"EOF", "error1", "error2", "github.com/pkg/errors.TestFormatWithMessage\n" + @@ -290,15 +290,13 @@ func TestFormatWithMessage(t *testing.T) { "\t.+/github.com/pkg/errors/format_test.go:285", "error"}, }, { - WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"), + WithMessage(Annotate(WithStack(io.EOF), "inside-error"), "outside-error"), "%+v", []string{ "EOF", "github.com/pkg/errors.TestFormatWithMessage\n" + "\t.+/github.com/pkg/errors/format_test.go:293", "inside-error", - "github.com/pkg/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pkg/errors/format_test.go:293", "outside-error"}, }} @@ -307,7 +305,7 @@ func TestFormatWithMessage(t *testing.T) { } } -func TestFormatGeneric(t *testing.T) { +/*func TestFormatGeneric(t *testing.T) { starts := []struct { err error want []string @@ -315,11 +313,11 @@ func TestFormatGeneric(t *testing.T) { {New("new-error"), []string{ "new-error", "github.com/pkg/errors.TestFormatGeneric\n" + - "\t.+/github.com/pkg/errors/format_test.go:315"}, + "\t.+/github.com/pkg/errors/format_test.go:313"}, }, {Errorf("errorf-error"), []string{ "errorf-error", "github.com/pkg/errors.TestFormatGeneric\n" + - "\t.+/github.com/pkg/errors/format_test.go:319"}, + "\t.+/github.com/pkg/errors/format_test.go:317"}, }, {errors.New("errors-new-error"), []string{ "errors-new-error"}, }, @@ -333,17 +331,17 @@ func TestFormatGeneric(t *testing.T) { func(err error) error { return WithStack(err) }, []string{ "github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" + - ".+/github.com/pkg/errors/format_test.go:333", + ".+/github.com/pkg/errors/format_test.go:331", }, }, { - func(err error) error { return Wrap(err, "wrap-error") }, + func(err error) error { return Annotate(err, "wrap-error") }, []string{ "wrap-error", "github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" + - ".+/github.com/pkg/errors/format_test.go:339", + ".+/github.com/pkg/errors/format_test.go:337", }, }, { - func(err error) error { return Wrapf(err, "wrapf-error%d", 1) }, + func(err error) error { return Annotatef(err, "wrapf-error%d", 1) }, []string{ "wrapf-error1", "github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" + @@ -358,9 +356,10 @@ func TestFormatGeneric(t *testing.T) { testFormatCompleteCompare(t, s, err, "%+v", want, false) testGenericRecursive(t, err, want, wrappers, 3) } -} +}*/ func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { + t.Helper() got := fmt.Sprintf(format, arg) gotLines := strings.SplitN(got, "\n", -1) wantLines := strings.SplitN(want, "\n", -1) diff --git a/group.go b/group.go new file mode 100644 index 00000000..003932c9 --- /dev/null +++ b/group.go @@ -0,0 +1,33 @@ +package errors + +// ErrorGroup is an interface for multiple errors that are not a chain. +// This happens for example when executing multiple operations in parallel. +type ErrorGroup interface { + Errors() []error +} + +// WalkDeep does a depth-first traversal of all errors. +// Any ErrorGroup is traversed (after going deep). +// The visitor function can return true to end the traversal early +// In that case, WalkDeep will return true, otherwise false. +func WalkDeep(err error, visitor func(err error) bool) bool { + // Go deep + unErr := err + for unErr != nil { + if done := visitor(unErr); done { + return true + } + unErr = Unwrap(unErr) + } + + // Go wide + if group, ok := err.(ErrorGroup); ok { + for _, err := range group.Errors() { + if early := WalkDeep(err, visitor); early { + return true + } + } + } + + return false +} diff --git a/juju_adaptor.go b/juju_adaptor.go new file mode 100644 index 00000000..773a1970 --- /dev/null +++ b/juju_adaptor.go @@ -0,0 +1,76 @@ +package errors + +import ( + "fmt" +) + +// ==================== juju adaptor start ======================== + +// Trace annotates err with a stack trace at the point WithStack was called. +// If err is nil or already contain stack trace return directly. +func Trace(err error) error { + return AddStack(err) +} + +func Annotate(err error, message string) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: message, + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +func Annotatef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +// ErrorStack will format a stack trace if it is available, otherwise it will be Error() +func ErrorStack(err error) string { + if err == nil { + return "" + } + return fmt.Sprintf("%+v", err) +} + +// NotFoundf represents an error with not found message. +func NotFoundf(format string, args ...interface{}) error { + return Errorf(format+" not found", args...) +} + +// BadRequestf represents an error with bad request message. +func BadRequestf(format string, args ...interface{}) error { + return Errorf(format+" bad request", args...) +} + +// NotSupportedf represents an error with not supported message. +func NotSupportedf(format string, args ...interface{}) error { + return Errorf(format+" not supported", args...) +} + +// ==================== juju adaptor end ======================== diff --git a/stack.go b/stack.go index 2874a048..f01dd86e 100644 --- a/stack.go +++ b/stack.go @@ -8,6 +8,12 @@ import ( "strings" ) +// StackTracer retrieves the StackTrace +// Generally you would want to use the GetStackTracer function to do that. +type StackTracer interface { + StackTrace() StackTrace +} + // Frame represents a program counter inside a stack frame. type Frame uintptr diff --git a/stack_test.go b/stack_test.go index 85fc4195..0b4ee989 100644 --- a/stack_test.go +++ b/stack_test.go @@ -156,12 +156,12 @@ func TestStackTrace(t *testing.T) { "\t.+/github.com/pkg/errors/stack_test.go:154", }, }, { - Wrap(New("ooh"), "ahh"), []string{ + Annotate(New("ooh"), "ahh"), []string{ "github.com/pkg/errors.TestStackTrace\n" + "\t.+/github.com/pkg/errors/stack_test.go:159", // this is the stack of Wrap, not New }, }, { - Cause(Wrap(New("ooh"), "ahh")), []string{ + Cause(Annotate(New("ooh"), "ahh")), []string{ "github.com/pkg/errors.TestStackTrace\n" + "\t.+/github.com/pkg/errors/stack_test.go:164", // this is the stack of New }, @@ -187,14 +187,17 @@ func TestStackTrace(t *testing.T) { }, }} for i, tt := range tests { - x, ok := tt.err.(interface { + ste, ok := tt.err.(interface { StackTrace() StackTrace }) if !ok { - t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err) - continue + ste = tt.err.(interface { + Cause() error + }).Cause().(interface { + StackTrace() StackTrace + }) } - st := x.StackTrace() + st := ste.StackTrace() for j, want := range tt.want { testFormatRegexp(t, i, st[j], "%+v", want) } @@ -253,19 +256,19 @@ func TestStackTraceFormat(t *testing.T) { }, { stackTrace()[:2], "%v", - `\[stack_test.go:207 stack_test.go:254\]`, + `[stack_test.go:207 stack_test.go:254]`, }, { stackTrace()[:2], "%+v", "\n" + "github.com/pkg/errors.stackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:207\n" + + "\t.+/github.com/pkg/errors/stack_test.go:210\n" + "github.com/pkg/errors.TestStackTraceFormat\n" + - "\t.+/github.com/pkg/errors/stack_test.go:258", + "\t.+/github.com/pkg/errors/stack_test.go:261", }, { stackTrace()[:2], "%#v", - `\[\]errors.Frame{stack_test.go:207, stack_test.go:266}`, + `\[\]errors.Frame{stack_test.go:210, stack_test.go:269}`, }} for i, tt := range tests { From f879a1f7e806a6feafd3d2ce50db4155327be6ff Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Thu, 6 Sep 2018 01:27:00 -0700 Subject: [PATCH 02/59] documentation for changes --- errors.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/errors.go b/errors.go index f809e707..0168061f 100644 --- a/errors.go +++ b/errors.go @@ -13,24 +13,24 @@ // // Adding context to an error // -// The errors.Wrap function returns a new error that adds context to the -// original error by recording a stack trace at the point Wrap is called, +// The errors.Annotate function returns a new error that adds context to the +// original error by recording a stack trace at the point Annotate is called, // and the supplied message. For example // // _, err := ioutil.ReadAll(r) // if err != nil { -// return errors.Wrap(err, "read failed") +// return errors.Annotate(err, "read failed") // } // -// If additional control is required the errors.WithStack and errors.WithMessage -// functions destructure errors.Wrap into its component operations of annotating +// If additional control is required the errors.AddStack and errors.WithMessage +// functions destructure errors.Annotate into its component operations of annotating // an error with a stack trace and an a message, respectively. // // Retrieving the cause of an error // -// Using errors.Wrap constructs a stack of errors, adding context to the +// Using errors.Annotate constructs a stack of errors, adding context to the // preceding error. Depending on the nature of the error it may be necessary -// to reverse the operation of errors.Wrap to retrieve the original error +// to reverse the operation of errors.Annotate to retrieve the original error // for inspection. Any error value which implements this interface // // type causer interface { @@ -50,6 +50,7 @@ // // causer interface is not exported by this package, but is considered a part // of stable public API. +// errors.Unwrap is also available: this will retrieve the next error in the chain. // // Formatted printing of errors // @@ -64,7 +65,7 @@ // // Retrieving the stack trace of an error or wrapper // -// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are invoked. +// New, Errorf, Annotate, and Annotatef record a stack trace at the point they are invoked. // This information can be retrieved with the StackTracer interface that returns // a StackTrace. Where errors.StackTrace is defined as // @@ -81,6 +82,8 @@ // } // // See the documentation for Frame.Format for more details. +// +// errors.Find can be used to search for an error in the error chain. package errors import ( @@ -148,6 +151,8 @@ func (f *fundamental) Format(s fmt.State, verb rune) { // WithStack annotates err with a stack trace at the point WithStack was called. // If err is nil, WithStack returns nil. +// +// Deprecated: use AddStack func WithStack(err error) error { if err == nil { return nil @@ -317,7 +322,8 @@ func Unwrap(err error) error { return nil } -// Find an error in the chain that matches a test function +// Find an error in the chain that matches a test function. +// returns nil if no error is found. func Find(origErr error, test func(error) bool) error { var foundErr error WalkDeep(origErr, func(err error) bool { From 7f213f5cd056c2e152b09602621bc8f4acaf7844 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Thu, 6 Sep 2018 12:22:47 -0700 Subject: [PATCH 03/59] add NewStack(), which generates a stack trace This avoids using hacky code to get a trace. --- stack.go | 18 ++++++++++++++++++ stack_test.go | 22 +++++++++++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/stack.go b/stack.go index f01dd86e..c7c580fd 100644 --- a/stack.go +++ b/stack.go @@ -151,3 +151,21 @@ func funcname(name string) string { i = strings.Index(name, ".") return name[i+1:] } + +// NewStack is for library implementers that want to generate a stack trace. +// Normally you should use AddStack to get an error with a stack trace. +// Stack trace information can be generated by calling this function. +// It can then be turned into a stack trace by calling .StackTrace() +// +// This function takes an optional argument for the position in the stack to start from. +// This avoids putting stack generation function calls in the stack trace. +// It defaults to 1, which removes the NewStack function from the stack trace. +func NewStack(position ...int) StackTracer { + stackPosition := 1 + if len(position) > 0 { + stackPosition = position[0] + } + + adjustedStack := (*callers())[stackPosition:] + return &adjustedStack +} diff --git a/stack_test.go b/stack_test.go index 0b4ee989..518b9374 100644 --- a/stack_test.go +++ b/stack_test.go @@ -204,12 +204,12 @@ func TestStackTrace(t *testing.T) { } } +// This comment helps to maintain original line numbers +// Perhaps this test is too fragile :) func stackTrace() StackTrace { - const depth = 8 - var pcs [depth]uintptr - n := runtime.Callers(1, pcs[:]) - var st stack = pcs[0:n] - return st.StackTrace() + return NewStack(0).StackTrace() + // This comment helps to maintain original line numbers + // Perhaps this test is too fragile :) } func TestStackTraceFormat(t *testing.T) { @@ -275,3 +275,15 @@ func TestStackTraceFormat(t *testing.T) { testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) } } + +func TestNewStack(t *testing.T) { + got := NewStack().StackTrace() + want := NewStack().StackTrace() + if got[0] != want[0] { + t.Errorf("NewStack(remove NewStack): want: %v, got: %v", want, got) + } + gotFirst := fmt.Sprintf("%+v", got[0])[0:15] + if gotFirst != "testing.tRunner" { + t.Errorf("NewStack(): want: %v, got: %+v", "testing.tRunner", gotFirst) + } +} From a39f23b24153e5b599641be675f66cccb68b4895 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Fri, 7 Sep 2018 09:29:03 -0700 Subject: [PATCH 04/59] require that NewStack take an argument --- stack.go | 28 +++++++++++++--------------- stack_test.go | 4 ++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/stack.go b/stack.go index c7c580fd..a2d74d12 100644 --- a/stack.go +++ b/stack.go @@ -137,9 +137,13 @@ func (s *stack) StackTrace() StackTrace { } func callers() *stack { + return callersSkip(4) +} + +func callersSkip(skip int) *stack { const depth = 32 var pcs [depth]uintptr - n := runtime.Callers(3, pcs[:]) + n := runtime.Callers(skip, pcs[:]) var st stack = pcs[0:n] return &st } @@ -153,19 +157,13 @@ func funcname(name string) string { } // NewStack is for library implementers that want to generate a stack trace. -// Normally you should use AddStack to get an error with a stack trace. -// Stack trace information can be generated by calling this function. -// It can then be turned into a stack trace by calling .StackTrace() +// Normally you should insted use AddStack to get an error with a stack trace. // -// This function takes an optional argument for the position in the stack to start from. -// This avoids putting stack generation function calls in the stack trace. -// It defaults to 1, which removes the NewStack function from the stack trace. -func NewStack(position ...int) StackTracer { - stackPosition := 1 - if len(position) > 0 { - stackPosition = position[0] - } - - adjustedStack := (*callers())[stackPosition:] - return &adjustedStack +// The result of this function can be turned into a stack trace by calling .StackTrace() +// +// This function takes an argument for the number of stack frames to skip. +// This avoids putting stack generation function calls like this one in the stack trace. +// You probably want to give it a value of 1, which removes the NewStack function from the stack trace. +func NewStack(skip int) StackTracer { + return callersSkip(skip + 3) } diff --git a/stack_test.go b/stack_test.go index 518b9374..56034258 100644 --- a/stack_test.go +++ b/stack_test.go @@ -277,8 +277,8 @@ func TestStackTraceFormat(t *testing.T) { } func TestNewStack(t *testing.T) { - got := NewStack().StackTrace() - want := NewStack().StackTrace() + got := NewStack(1).StackTrace() + want := NewStack(1).StackTrace() if got[0] != want[0] { t.Errorf("NewStack(remove NewStack): want: %v, got: %v", want, got) } From 5dc497ef83f0b859c0bafcdf815f3b522b60d06c Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Fri, 7 Sep 2018 09:43:29 -0700 Subject: [PATCH 05/59] doc fix --- stack.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stack.go b/stack.go index a2d74d12..6edd7e56 100644 --- a/stack.go +++ b/stack.go @@ -163,7 +163,8 @@ func funcname(name string) string { // // This function takes an argument for the number of stack frames to skip. // This avoids putting stack generation function calls like this one in the stack trace. -// You probably want to give it a value of 1, which removes the NewStack function from the stack trace. +// A value of 0 will give you the line that called NewStack(0) +// A library author wrapping this in their own function will want to use a value of at least 1. func NewStack(skip int) StackTracer { return callersSkip(skip + 3) } From 0548e8c67dc6c635b98f8f643d0af516d34c0860 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Mon, 10 Sep 2018 19:46:30 -0700 Subject: [PATCH 06/59] add an Errors utility helper (#8) --- group.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/group.go b/group.go index 003932c9..e5a969ab 100644 --- a/group.go +++ b/group.go @@ -6,6 +6,15 @@ type ErrorGroup interface { Errors() []error } +// Errors uses the ErrorGroup interface to return a slice of errors. +// If the ErrorGroup interface is not implemented it returns an array containing just the given error. +func Errors(err error) []error { + if eg, ok := err.(ErrorGroup); ok { + return eg.Errors() + } + return []error{err} +} + // WalkDeep does a depth-first traversal of all errors. // Any ErrorGroup is traversed (after going deep). // The visitor function can return true to end the traversal early From e41050e56884e210c92d46ee610b26c35f757f20 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Mon, 10 Sep 2018 19:48:19 -0700 Subject: [PATCH 07/59] move GetStackTracer to stack.go (#7) --- errors.go | 16 ---------------- stack.go | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/errors.go b/errors.go index 0168061f..4e2e9673 100644 --- a/errors.go +++ b/errors.go @@ -173,22 +173,6 @@ func AddStack(err error) error { return WithStack(err) } -// GetStackTracer will return the first StackTracer in the causer chain. -// This function is used by AddStack to avoid creating redundant stack traces. -// -// You can also use the StackTracer interface on the returned error to get the stack trace. -func GetStackTracer(origErr error) StackTracer { - var stacked StackTracer - WalkDeep(origErr, func(err error) bool { - if stackTracer, ok := err.(StackTracer); ok { - stacked = stackTracer - return true - } - return false - }) - return stacked -} - type withStack struct { error *stack diff --git a/stack.go b/stack.go index 6edd7e56..cbebb620 100644 --- a/stack.go +++ b/stack.go @@ -14,6 +14,22 @@ type StackTracer interface { StackTrace() StackTrace } +// GetStackTracer will return the first StackTracer in the causer chain. +// This function is used by AddStack to avoid creating redundant stack traces. +// +// You can also use the StackTracer interface on the returned error to get the stack trace. +func GetStackTracer(origErr error) StackTracer { + var stacked StackTracer + WalkDeep(origErr, func(err error) bool { + if stackTracer, ok := err.(StackTracer); ok { + stacked = stackTracer + return true + } + return false + }) + return stacked +} + // Frame represents a program counter inside a stack frame. type Frame uintptr From dc8ffe785c7fc9a74eeb5241814d77f1c5fb5e58 Mon Sep 17 00:00:00 2001 From: Chris Stockton Date: Tue, 11 Sep 2018 17:01:16 +0800 Subject: [PATCH 08/59] reduce allocations when printing stack traces (#149) --- bench_test.go | 49 ++++++++++++++++++++++++++++++++++++- stack.go | 68 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 102 insertions(+), 15 deletions(-) diff --git a/bench_test.go b/bench_test.go index 903b5f2d..c906870e 100644 --- a/bench_test.go +++ b/bench_test.go @@ -25,7 +25,7 @@ func yesErrors(at, depth int) error { // GlobalE is an exported global to store the result of benchmark results, // preventing the compiler from optimising the benchmark functions away. -var GlobalE error +var GlobalE interface{} func BenchmarkErrors(b *testing.B) { type run struct { @@ -61,3 +61,50 @@ func BenchmarkErrors(b *testing.B) { }) } } + +func BenchmarkStackFormatting(b *testing.B) { + type run struct { + stack int + format string + } + runs := []run{ + {10, "%s"}, + {10, "%v"}, + {10, "%+v"}, + {30, "%s"}, + {30, "%v"}, + {30, "%+v"}, + {60, "%s"}, + {60, "%v"}, + {60, "%+v"}, + } + + var stackStr string + for _, r := range runs { + name := fmt.Sprintf("%s-stack-%d", r.format, r.stack) + b.Run(name, func(b *testing.B) { + err := yesErrors(0, r.stack) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + stackStr = fmt.Sprintf(r.format, err) + } + b.StopTimer() + }) + } + + for _, r := range runs { + name := fmt.Sprintf("%s-stacktrace-%d", r.format, r.stack) + b.Run(name, func(b *testing.B) { + err := yesErrors(0, r.stack) + st := err.(*fundamental).stack.StackTrace() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + stackStr = fmt.Sprintf(r.format, st) + } + b.StopTimer() + }) + } + GlobalE = stackStr +} diff --git a/stack.go b/stack.go index cbebb620..bb1e6a84 100644 --- a/stack.go +++ b/stack.go @@ -1,10 +1,12 @@ package errors import ( + "bytes" "fmt" "io" "path" "runtime" + "strconv" "strings" ) @@ -72,6 +74,11 @@ func (f Frame) line() int { // GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { + f.format(s, s, verb) +} + +// format allows stack trace printing calls to be made with a bytes.Buffer. +func (f Frame) format(w io.Writer, s fmt.State, verb rune) { switch verb { case 's': switch { @@ -79,23 +86,25 @@ func (f Frame) Format(s fmt.State, verb rune) { pc := f.pc() fn := runtime.FuncForPC(pc) if fn == nil { - io.WriteString(s, "unknown") + io.WriteString(w, "unknown") } else { file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + io.WriteString(w, fn.Name()) + io.WriteString(w, "\n\t") + io.WriteString(w, file) } default: - io.WriteString(s, path.Base(f.file())) + io.WriteString(w, path.Base(f.file())) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + io.WriteString(w, strconv.Itoa(f.line())) case 'n': name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) + io.WriteString(w, funcname(name)) case 'v': - f.Format(s, 's') - io.WriteString(s, ":") - f.Format(s, 'd') + f.format(w, s, 's') + io.WriteString(w, ":") + f.format(w, s, 'd') } } @@ -111,23 +120,50 @@ type StackTrace []Frame // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { + var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): - for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) + b.Grow(len(st) * stackMinLen) + for _, fr := range st { + b.WriteByte('\n') + fr.format(&b, s, verb) } case s.Flag('#'): - fmt.Fprintf(s, "%#v", []Frame(st)) + fmt.Fprintf(&b, "%#v", []Frame(st)) default: - fmt.Fprintf(s, "%v", []Frame(st)) + st.formatSlice(&b, s, verb) } case 's': - fmt.Fprintf(s, "%s", []Frame(st)) + st.formatSlice(&b, s, verb) + } + io.Copy(s, &b) +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { + b.WriteByte('[') + if len(st) == 0 { + b.WriteByte(']') + return + } + + b.Grow(len(st) * (stackMinLen / 4)) + st[0].format(b, s, verb) + for _, fr := range st[1:] { + b.WriteByte(' ') + fr.format(b, s, verb) } + b.WriteByte(']') } +// stackMinLen is a best-guess at the minimum length of a stack trace. It +// doesn't need to be exact, just give a good enough head start for the buffer +// to avoid the expensive early growth. +const stackMinLen = 96 + // stack represents a stack of program counters. type stack []uintptr @@ -136,10 +172,14 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): + var b bytes.Buffer + b.Grow(len(*s) * stackMinLen) for _, pc := range *s { f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) + b.WriteByte('\n') + f.format(&b, st, 'v') } + io.Copy(st, &b) } } } From 017f38e9e2a4ad0df849a2795ceb3b644d04fdf1 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Wed, 19 Sep 2018 20:56:23 -0700 Subject: [PATCH 09/59] update docs around deprecation --- errors.go | 17 ++++++++++------- juju_adaptor.go | 7 +++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/errors.go b/errors.go index 4e2e9673..2e1d3f62 100644 --- a/errors.go +++ b/errors.go @@ -152,7 +152,8 @@ func (f *fundamental) Format(s fmt.State, verb rune) { // WithStack annotates err with a stack trace at the point WithStack was called. // If err is nil, WithStack returns nil. // -// Deprecated: use AddStack +// For most use cases this is deprecated and AddStack should be used (which will ensure just one stack trace). +// However, one may want to use this in some situations, for example to create a 2nd trace across a goroutine. func WithStack(err error) error { if err == nil { return nil @@ -197,10 +198,11 @@ func (w *withStack) Format(s fmt.State, verb rune) { } // Wrap returns an error annotating err with a stack trace -// at the point Annotate is called, and the supplied message. -// If err is nil, Annotate returns nil. +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. // -// Deprecated: use Annotate instead +// For most use cases this is deprecated in favor of Annotate. +// Annotate avoids creating duplicate stack traces. func Wrap(err error, message string) error { if err == nil { return nil @@ -218,10 +220,11 @@ func Wrap(err error, message string) error { } // Wrapf returns an error annotating err with a stack trace -// at the point Annotatef is call, and the format specifier. -// If err is nil, Annotatef returns nil. +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. // -// Deprecated: use Annotatef instead +// For most use cases this is deprecated in favor of Annotatef. +// Annotatef avoids creating duplicate stack traces. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil diff --git a/juju_adaptor.go b/juju_adaptor.go index 773a1970..0b20f573 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -6,12 +6,12 @@ import ( // ==================== juju adaptor start ======================== -// Trace annotates err with a stack trace at the point WithStack was called. -// If err is nil or already contain stack trace return directly. +// Trace just calls AddStack. func Trace(err error) error { return AddStack(err) } +// Annotate adds a message and ensures there is a stack trace. func Annotate(err error, message string) error { if err == nil { return nil @@ -31,6 +31,7 @@ func Annotate(err error, message string) error { } } +// Annotatef adds a message and ensures there is a stack trace. func Annotatef(err error, format string, args ...interface{}) error { if err == nil { return nil @@ -51,6 +52,8 @@ func Annotatef(err error, format string, args ...interface{}) error { } // ErrorStack will format a stack trace if it is available, otherwise it will be Error() +// If the error is nil, the empty string is returned +// Note that this just calls fmt.Sprintf("%+v", err) func ErrorStack(err error) string { if err == nil { return "" From 30964f0ae14ca4c27b852007a9dbaba3c66ea393 Mon Sep 17 00:00:00 2001 From: WangXiangUSTC <347249478@qq.com> Date: Tue, 23 Oct 2018 17:57:47 +0800 Subject: [PATCH 10/59] add some function to adjust juju/error --- juju_adaptor.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/juju_adaptor.go b/juju_adaptor.go index 0b20f573..ccfb2fc2 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -2,6 +2,7 @@ package errors import ( "fmt" + "strings" ) // ==================== juju adaptor start ======================== @@ -61,6 +62,11 @@ func ErrorStack(err error) string { return fmt.Sprintf("%+v", err) } +// IsNotFound reports whether err was not found error. +func IsNotFound(err error) bool { + return strings.Contains(err.Error(), "not found") +} + // NotFoundf represents an error with not found message. func NotFoundf(format string, args ...interface{}) error { return Errorf(format+" not found", args...) @@ -76,4 +82,18 @@ func NotSupportedf(format string, args ...interface{}) error { return Errorf(format+" not supported", args...) } +// NotValidf represents an error with not valid message. +func NotValidf(format string, args ...interface{}) error { + return Errorf(format+" not valid", args...) +} + +func IsAlreadyExists(err error) bool { + return strings.Contains(err.Error(), "already exists") +} + +// AlreadyExistsf represents an error with already exists message. +func AlreadyExistsf(format string, args ...interface{}) error { + return Errorf(format+" already exists", args...) +} + // ==================== juju adaptor end ======================== From c22a95629595d66d2902c19514ad2e68147bcb9b Mon Sep 17 00:00:00 2001 From: WangXiangUSTC <347249478@qq.com> Date: Tue, 23 Oct 2018 18:00:01 +0800 Subject: [PATCH 11/59] add comment --- juju_adaptor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/juju_adaptor.go b/juju_adaptor.go index ccfb2fc2..8bcfe2f3 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -87,6 +87,7 @@ func NotValidf(format string, args ...interface{}) error { return Errorf(format+" not valid", args...) } +// IsAlreadyExists reports whether err was already exists error. func IsAlreadyExists(err error) bool { return strings.Contains(err.Error(), "already exists") } From 08948d856f8b0ac49f150436eb025a8c3c65a570 Mon Sep 17 00:00:00 2001 From: lysu Date: Mon, 18 Mar 2019 12:29:50 +0800 Subject: [PATCH 12/59] errors: make errors.Trace mid-stack inlineable --- juju_adaptor.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/juju_adaptor.go b/juju_adaptor.go index 8bcfe2f3..215b4b58 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -9,6 +9,9 @@ import ( // Trace just calls AddStack. func Trace(err error) error { + if err == nil { + return nil + } return AddStack(err) } From cdf856f0ab8be13e8dff0296bfa9bb2a677ce815 Mon Sep 17 00:00:00 2001 From: lysu Date: Wed, 15 May 2019 13:36:41 +0800 Subject: [PATCH 13/59] add NewNoStackError support (#13) --- juju_adaptor.go | 11 +++++++++++ stack_test.go | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/juju_adaptor.go b/juju_adaptor.go index 215b4b58..7abb262d 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -55,6 +55,17 @@ func Annotatef(err error, format string, args ...interface{}) error { } } +var emptyStack stack + +// NewNoStackError creates error without error stack +// later duplicate trace will no longer generate Stack too. +func NewNoStackError(msg string) error { + return &fundamental{ + msg: msg, + stack: &emptyStack, + } +} + // ErrorStack will format a stack trace if it is available, otherwise it will be Error() // If the error is nil, the empty string is returned // Note that this just calls fmt.Sprintf("%+v", err) diff --git a/stack_test.go b/stack_test.go index 56034258..cf9efadf 100644 --- a/stack_test.go +++ b/stack_test.go @@ -287,3 +287,13 @@ func TestNewStack(t *testing.T) { t.Errorf("NewStack(): want: %v, got: %+v", "testing.tRunner", gotFirst) } } + +func TestSuspendStackError(t *testing.T) { + err := NewNoStackError("test error") + err = Trace(err) + err = Trace(err) + result := fmt.Sprintf("%+v", err) + if result != "test error" { + t.Errorf("NewNoStackError(): want %s, got %v", "test error", result) + } +} From d95478cc77ff015facf0dbd088f4160ba4307f8d Mon Sep 17 00:00:00 2001 From: lysu Date: Wed, 15 May 2019 13:56:44 +0800 Subject: [PATCH 14/59] add SuspendStack support (#15) --- juju_adaptor.go | 12 ++++++++++++ stack_test.go | 13 ++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/juju_adaptor.go b/juju_adaptor.go index 7abb262d..97a4b2b2 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -66,6 +66,18 @@ func NewNoStackError(msg string) error { } } +// SuspendStack suspends stack for a exists error. +// it can be used to suspend follow up Trace do not add stack. +func SuspendStack(err error) error { + if err == nil { + return err + } + return withStack{ + err, + &emptyStack, + } +} + // ErrorStack will format a stack trace if it is available, otherwise it will be Error() // If the error is nil, the empty string is returned // Note that this just calls fmt.Sprintf("%+v", err) diff --git a/stack_test.go b/stack_test.go index cf9efadf..16e39129 100644 --- a/stack_test.go +++ b/stack_test.go @@ -2,6 +2,7 @@ package errors import ( "fmt" + "io" "runtime" "testing" ) @@ -288,7 +289,7 @@ func TestNewStack(t *testing.T) { } } -func TestSuspendStackError(t *testing.T) { +func TestNewNoStackError(t *testing.T) { err := NewNoStackError("test error") err = Trace(err) err = Trace(err) @@ -297,3 +298,13 @@ func TestSuspendStackError(t *testing.T) { t.Errorf("NewNoStackError(): want %s, got %v", "test error", result) } } + +func TestSuspendStackError(t *testing.T) { + err := io.EOF + err = SuspendStack(err) + err = Trace(err) + result := fmt.Sprintf("%+v", err) + if result != "EOF" { + t.Errorf("NewNoStackError(): want %s, got %v", "EOF", result) + } +} From fc6e4ce558343e6eab2450e7653502fee61d9ad6 Mon Sep 17 00:00:00 2001 From: lysu Date: Wed, 15 May 2019 16:44:45 +0800 Subject: [PATCH 15/59] fix suspend with cause bug (#16) --- juju_adaptor.go | 25 ++++++++++++++++++++++--- stack_test.go | 17 ++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/juju_adaptor.go b/juju_adaptor.go index 97a4b2b2..ece86cd6 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -66,18 +66,37 @@ func NewNoStackError(msg string) error { } } -// SuspendStack suspends stack for a exists error. -// it can be used to suspend follow up Trace do not add stack. +// SuspendStack suspends stack generate for error. func SuspendStack(err error) error { if err == nil { return err } - return withStack{ + cleared := clearStack(err) + if cleared { + return err + } + return &withStack{ err, &emptyStack, } } +func clearStack(err error) (cleared bool) { + switch typedErr := err.(type) { + case *withMessage: + return clearStack(typedErr.Cause()) + case *fundamental: + typedErr.stack = &emptyStack + return true + case *withStack: + typedErr.stack = &emptyStack + clearStack(typedErr.Cause()) + return true + default: + return false + } +} + // ErrorStack will format a stack trace if it is available, otherwise it will be Error() // If the error is nil, the empty string is returned // Note that this just calls fmt.Sprintf("%+v", err) diff --git a/stack_test.go b/stack_test.go index 16e39129..7ef28fe0 100644 --- a/stack_test.go +++ b/stack_test.go @@ -299,7 +299,7 @@ func TestNewNoStackError(t *testing.T) { } } -func TestSuspendStackError(t *testing.T) { +func TestSuspendError(t *testing.T) { err := io.EOF err = SuspendStack(err) err = Trace(err) @@ -307,4 +307,19 @@ func TestSuspendStackError(t *testing.T) { if result != "EOF" { t.Errorf("NewNoStackError(): want %s, got %v", "EOF", result) } + if io.EOF != Cause(err) { + t.Errorf("SuspendStackError can not got back origion error.") + } +} + +func TestSuspendTracedWithMessageError(t *testing.T) { + tracedErr := Trace(io.EOF) + tracedErr = WithStack(tracedErr) + tracedErr = WithMessage(tracedErr, "1") + tracedErr = SuspendStack(tracedErr) + tracedErr = Trace(tracedErr) + result := fmt.Sprintf("%+v", tracedErr) + if result != "EOF\n1" { + t.Errorf("NewNoStackError(): want %s, got %v", "EOF\n1", result) + } } From 95897b64e011c18f05b4daac8ff3098a6d80c57e Mon Sep 17 00:00:00 2001 From: LiMingji Date: Fri, 9 Aug 2019 17:25:03 +0800 Subject: [PATCH 16/59] fix stack_test.go wrong line number(#17) --- stack_test.go | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/stack_test.go b/stack_test.go index 7ef28fe0..d581ad6e 100644 --- a/stack_test.go +++ b/stack_test.go @@ -15,19 +15,19 @@ func TestFrameLine(t *testing.T) { want int }{{ Frame(initpc), - 9, + 10, }, { func() Frame { var pc, _, _, _ = runtime.Caller(0) return Frame(pc) }(), - 20, + 21, }, { func() Frame { var pc, _, _, _ = runtime.Caller(1) return Frame(pc) }(), - 28, + 29, }, { Frame(0), // invalid PC 0, @@ -66,8 +66,8 @@ func TestFrameFormat(t *testing.T) { }, { Frame(initpc), "%+s", - "github.com/pkg/errors.init\n" + - "\t.+/github.com/pkg/errors/stack_test.go", + "github.com/pingcap/errors.init\n" + + "\t.+/github.com/pingcap/errors/stack_test.go", }, { Frame(0), "%s", @@ -79,7 +79,7 @@ func TestFrameFormat(t *testing.T) { }, { Frame(initpc), "%d", - "9", + "10", }, { Frame(0), "%d", @@ -109,12 +109,12 @@ func TestFrameFormat(t *testing.T) { }, { Frame(initpc), "%v", - "stack_test.go:9", + "stack_test.go:10", }, { Frame(initpc), "%+v", - "github.com/pkg/errors.init\n" + - "\t.+/github.com/pkg/errors/stack_test.go:9", + "github.com/pingcap/errors.init\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:10", }, { Frame(0), "%v", @@ -132,7 +132,7 @@ func TestFuncname(t *testing.T) { }{ {"", ""}, {"runtime.main", "main"}, - {"github.com/pkg/errors.funcname", "funcname"}, + {"github.com/pingcap/errors.funcname", "funcname"}, {"funcname", "funcname"}, {"io.copyBuffer", "copyBuffer"}, {"main.(*R).Write", "(*R).Write"}, @@ -153,25 +153,25 @@ func TestStackTrace(t *testing.T) { want []string }{{ New("ooh"), []string{ - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:154", + "github.com/pingcap/errors.TestStackTrace\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:155", }, }, { Annotate(New("ooh"), "ahh"), []string{ - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:159", // this is the stack of Wrap, not New + "github.com/pingcap/errors.TestStackTrace\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:160", // this is the stack of Wrap, not New }, }, { Cause(Annotate(New("ooh"), "ahh")), []string{ - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:164", // this is the stack of New + "github.com/pingcap/errors.TestStackTrace\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:165", // this is the stack of New }, }, { func() error { return New("ooh") }(), []string{ - `github.com/pkg/errors.(func·009|TestStackTrace.func1)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New's caller + `github.com/pingcap/errors.(func·009|TestStackTrace.func1)` + + "\n\t.+/github.com/pingcap/errors/stack_test.go:170", // this is the stack of New + "github.com/pingcap/errors.TestStackTrace\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:170", // this is the stack of New's caller }, }, { Cause(func() error { @@ -179,12 +179,12 @@ func TestStackTrace(t *testing.T) { return Errorf("hello %s", fmt.Sprintf("world")) }() }()), []string{ - `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:178", // this is the stack of Errorf - `github.com/pkg/errors.(func·011|TestStackTrace.func2)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:179", // this is the stack of Errorf's caller - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Errorf's caller's caller + `github.com/pingcap/errors.(func·010|TestStackTrace.func2.1)` + + "\n\t.+/github.com/pingcap/errors/stack_test.go:179", // this is the stack of Errorf + `github.com/pingcap/errors.(func·011|TestStackTrace.func2)` + + "\n\t.+/github.com/pingcap/errors/stack_test.go:180", // this is the stack of Errorf's caller + "github.com/pingcap/errors.TestStackTrace\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:181", // this is the stack of Errorf's caller's caller }, }} for i, tt := range tests { @@ -262,14 +262,14 @@ func TestStackTraceFormat(t *testing.T) { stackTrace()[:2], "%+v", "\n" + - "github.com/pkg/errors.stackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:210\n" + - "github.com/pkg/errors.TestStackTraceFormat\n" + - "\t.+/github.com/pkg/errors/stack_test.go:261", + "github.com/pingcap/errors.stackTrace\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:211\n" + + "github.com/pingcap/errors.TestStackTraceFormat\n" + + "\t.+/github.com/pingcap/errors/stack_test.go:262", }, { stackTrace()[:2], "%#v", - `\[\]errors.Frame{stack_test.go:210, stack_test.go:269}`, + `\[\]errors.Frame{stack_test.go:211, stack_test.go:270}`, }} for i, tt := range tests { From 4e113ddee29e69e1bc58a547e8febb1d155cb120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=B2=9A?= <36239017+YuJuncen@users.noreply.github.com> Date: Wed, 29 Jul 2020 09:21:36 +0800 Subject: [PATCH 17/59] extract terror from pingcap/parser (#21) --- go.mod | 10 ++ go.sum | 66 +++++++ terror_dsl.go | 73 ++++++++ terror_error.go | 293 +++++++++++++++++++++++++++++++ terror_gen.go | 79 +++++++++ terror_registry.go | 176 +++++++++++++++++++ terror_test/terror_test.go | 341 +++++++++++++++++++++++++++++++++++++ 7 files changed, 1038 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 terror_dsl.go create mode 100644 terror_error.go create mode 100644 terror_gen.go create mode 100644 terror_registry.go create mode 100644 terror_test/terror_test.go diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..e0fb516c --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/pingcap/errors + +go 1.14 + +require ( + github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 + github.com/pingcap/log v0.0.0-20200511115504-543df19646ad + github.com/pkg/errors v0.9.1 + go.uber.org/zap v1.15.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..f2f8f423 --- /dev/null +++ b/go.sum @@ -0,0 +1,66 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 h1:USx2/E1bX46VG32FIw034Au6seQ2fY9NEILmNh/UlQg= +github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ= +github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/log v0.0.0-20200511115504-543df19646ad h1:SveG82rmu/GFxYanffxsSF503SiQV+2JLnWEiGiF+Tc= +github.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/terror_dsl.go b/terror_dsl.go new file mode 100644 index 00000000..e2e55eed --- /dev/null +++ b/terror_dsl.go @@ -0,0 +1,73 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "github.com/pingcap/log" + "go.uber.org/zap" +) + +// Builder is the builder of Error. +type Builder struct { + err *Error + class *ErrClass +} + +// TextualCode is is the textual identity of this error internally, +// note that this error code can only be duplicated in different Registry or ErrorClass. +func (b *Builder) TextualCode(text ErrCodeText) *Builder { + b.err.codeText = text + return b +} + +// NumericCode is is the numeric identity of this error internally, +// note that this error code can only be duplicated in different Registry or ErrorClass. +func (b *Builder) NumericCode(num ErrCode) *Builder { + b.err.code = num + return b +} + +// Description is the expanded detail of why this error occurred. +// This could be written by developer at a static env, +// and the more detail this field explaining the better, +// even some guess of the cause could be included. +func (b *Builder) Description(desc string) *Builder { + b.err.Description = desc + return b +} + +// Workaround shows how to work around this error. +// It's used to teach the users how to solve the error if occurring in the real environment. +func (b *Builder) Workaround(wd string) *Builder { + b.err.Workaround = wd + return b +} + +// MessageTemplate is the template of the error string that can be formatted after +// calling `GenWithArgs` method. +// currently, printf style template is used. +func (b *Builder) MessageTemplate(template string) *Builder { + b.err.message = template + return b +} + +// Build ends the define of the error. +func (b *Builder) Build() *Error { + if ok := b.class.RegisterError(b.err); !ok { + log.Panic("replicated error prototype created", + zap.String("ID", string(b.err.ID())), + zap.String("RFCCode", string(b.err.RFCCode()))) + } + return b.err +} diff --git a/terror_error.go b/terror_error.go new file mode 100644 index 00000000..f6b147e8 --- /dev/null +++ b/terror_error.go @@ -0,0 +1,293 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "encoding/json" + "fmt" + "runtime" + "strconv" + "strings" +) + +// Error is the 'prototype' of a type of errors. +// Use DefineError to make a *Error: +// var ErrUnavailable = ClassRegion.DefineError(). +// TextualCode("Unavailable"). +// Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + +// "This error usually occurs when the TiKV server is busy or the TiKV node is down."). +// Workaround("Check the status, monitoring data and log of the TiKV server."). +// MessageTemplate("Region %d is unavailable"). +// Build() +// +// "throw" it at runtime: +// func Somewhat() error { +// ... +// if err != nil { +// // generate a stackful error use the message template at defining, +// // also see FastGen(it's stackless), GenWithStack(it uses custom message template). +// return ErrUnavailable.GenWithStackByArgs(region.ID) +// } +// } +// +// testing whether an error belongs to a prototype: +// if ErrUnavailable.Equal(err) { +// // handle this error. +// } +type Error struct { + class *ErrClass + code ErrCode + // codeText is the textual describe of the error code + codeText ErrCodeText + // message is a template of the description of this error. + // printf-style formatting is enabled. + message string + // The workaround field: how to work around this error. + // It's used to teach the users how to solve the error if occurring in the real environment. + Workaround string + // Description is the expanded detail of why this error occurred. + // This could be written by developer at a static env, + // and the more detail this field explaining the better, + // even some guess of the cause could be included. + Description string + args []interface{} + file string + line int +} + +// Class returns ErrClass +func (e *Error) Class() *ErrClass { + return e.class +} + +// Code returns the numeric code of this error. +// ID() will return textual error if there it is, +// when you just want to get the purely numeric error +// (e.g., for mysql protocol transmission.), this would be useful. +func (e *Error) Code() ErrCode { + return e.code +} + +// Code returns ErrorCode, by the RFC: +// +// The error code is a 3-tuple of abbreviated component name, error class and error code, +// joined by a colon like {Component}:{ErrorClass}:{InnerErrorCode}. +func (e *Error) RFCCode() RFCErrorCode { + ec := e.Class() + if ec == nil { + return RFCErrorCode(e.ID()) + } + reg := ec.registry + // Maybe top-level errors. + if reg.Name == "" { + return RFCErrorCode(fmt.Sprintf("%s:%s", + ec.Description, + e.ID(), + )) + } + return RFCErrorCode(fmt.Sprintf("%s:%s:%s", + reg.Name, + ec.Description, + e.ID(), + )) +} + +// ID returns the ID of this error. +func (e *Error) ID() ErrorID { + if e.codeText != "" { + return ErrorID(e.codeText) + } + return ErrorID(strconv.Itoa(int(e.code))) +} + +// Location returns the location where the error is created, +// implements juju/errors locationer interface. +func (e *Error) Location() (file string, line int) { + return e.file, e.line +} + +// MessageTemplate returns the error message template of this error. +func (e *Error) MessageTemplate() string { + return e.message +} + +// Error implements error interface. +func (e *Error) Error() string { + describe := e.codeText + if len(describe) == 0 { + describe = ErrCodeText(strconv.Itoa(int(e.code))) + } + return fmt.Sprintf("[%s] %s", e.RFCCode(), e.getMsg()) +} + +func (e *Error) getMsg() string { + if len(e.args) > 0 { + return fmt.Sprintf(e.message, e.args...) + } + return e.message +} + +func (e *Error) fillLineAndFile(skip int) { + // skip this + _, file, line, ok := runtime.Caller(skip + 1) + if !ok { + e.file = "" + e.line = -1 + return + } + e.file = file + e.line = line +} + +// GenWithStack generates a new *Error with the same class and code, and a new formatted message. +func (e *Error) GenWithStack(format string, args ...interface{}) error { + err := *e + err.message = format + err.args = args + err.fillLineAndFile(1) + return AddStack(&err) +} + +// GenWithStackByArgs generates a new *Error with the same class and code, and new arguments. +func (e *Error) GenWithStackByArgs(args ...interface{}) error { + err := *e + err.args = args + err.fillLineAndFile(1) + return AddStack(&err) +} + +// FastGen generates a new *Error with the same class and code, and a new formatted message. +// This will not call runtime.Caller to get file and line. +func (e *Error) FastGen(format string, args ...interface{}) error { + err := *e + err.message = format + err.args = args + return SuspendStack(&err) +} + +// FastGen generates a new *Error with the same class and code, and a new arguments. +// This will not call runtime.Caller to get file and line. +func (e *Error) FastGenByArgs(args ...interface{}) error { + err := *e + err.args = args + return SuspendStack(&err) +} + +// Equal checks if err is equal to e. +func (e *Error) Equal(err error) bool { + originErr := Cause(err) + if originErr == nil { + return false + } + if error(e) == originErr { + return true + } + inErr, ok := originErr.(*Error) + if !ok { + return false + } + classEquals := e.class.Equal(inErr.class) + idEquals := e.ID() == inErr.ID() + return classEquals && idEquals +} + +// NotEqual checks if err is not equal to e. +func (e *Error) NotEqual(err error) bool { + return !e.Equal(err) +} + +// ErrorEqual returns a boolean indicating whether err1 is equal to err2. +func ErrorEqual(err1, err2 error) bool { + e1 := Cause(err1) + e2 := Cause(err2) + + if e1 == e2 { + return true + } + + if e1 == nil || e2 == nil { + return e1 == e2 + } + + te1, ok1 := e1.(*Error) + te2, ok2 := e2.(*Error) + if ok1 && ok2 { + return te1.Equal(te2) + } + + return e1.Error() == e2.Error() +} + +// ErrorNotEqual returns a boolean indicating whether err1 isn't equal to err2. +func ErrorNotEqual(err1, err2 error) bool { + return !ErrorEqual(err1, err2) +} + +type jsonError struct { + RFCCode RFCErrorCode `json:"code"` + Error string `json:"message"` + Description string `json:"description,omitempty"` + Workaround string `json:"workaround,omitempty"` + Class ErrClassID `json:"classID"` + File string `json:"file"` + Line int `json:"line"` +} + +// MarshalJSON implements json.Marshaler interface. +// aware that this function cannot save a 'registered' status, +// since we cannot access the registry when unmarshaling, +// and the original global registry would be removed here. +// This function is reserved for compatibility. +func (e *Error) MarshalJSON() ([]byte, error) { + return json.Marshal(&jsonError{ + Error: e.getMsg(), + Description: e.Description, + Workaround: e.Workaround, + RFCCode: e.RFCCode(), + Class: e.class.ID, + Line: e.line, + File: e.file, + }) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +// aware that this function cannot create a 'registered' error, +// since we cannot access the registry in this context, +// and the original global registry is removed. +// This function is reserved for compatibility. +func (e *Error) UnmarshalJSON(data []byte) error { + err := &jsonError{} + + if err := json.Unmarshal(data, &err); err != nil { + return Trace(err) + } + codes := strings.Split(string(err.RFCCode), ":") + regName := codes[0] + className := codes[1] + innerCode := codes[2] + if i, errAtoi := strconv.Atoi(innerCode); errAtoi == nil { + e.code = ErrCode(i) + } else { + e.codeText = ErrCodeText(innerCode) + } + e.line = err.Line + e.file = err.File + e.message = err.Error + e.class = &ErrClass{ + Description: className, + ID: err.Class, + registry: &Registry{Name: regName}, + } + return nil +} diff --git a/terror_gen.go b/terror_gen.go new file mode 100644 index 00000000..71d9a900 --- /dev/null +++ b/terror_gen.go @@ -0,0 +1,79 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "fmt" + "github.com/pingcap/log" + "go.uber.org/zap" + "io" + "regexp" +) + +const tomlTemplate = `[error.%s] +error = '''%s''' +description = '''%s''' +workaround = '''%s''' +` + +func (e *Error) exportTo(writer io.Writer) error { + desc := e.Description + if e.Description == "" { + log.Warn("error description missed", zap.String("error", string(e.RFCCode()))) + desc = "N/A" + } + workaround := e.Workaround + if e.Workaround == "" { + log.Warn("error workaround missed", zap.String("error", string(e.RFCCode()))) + workaround = "N/A" + } + _, err := fmt.Fprintf(writer, tomlTemplate, e.RFCCode(), replaceFlags(e.MessageTemplate()), desc, workaround) + return err +} + +func (ec *ErrClass) exportTo(writer io.Writer) error { + for _, e := range ec.AllErrors() { + if err := e.exportTo(writer); err != nil { + return err + } + } + return nil +} + +// ExportTo export the registry to a writer, as toml format from the RFC. +func (r *Registry) ExportTo(writer io.Writer) error { + for _, ec := range r.errClasses { + if err := ec.exportTo(writer); err != nil { + return err + } + } + return nil +} + +// CPP printf flag with minimal support for explicit argument indexes. +// introductory % character: % +// (optional) one or more flags that modify the behavior of the conversion: [+\-# 0]? +// (optional) integer value or * that specifies minimum field width: ([0-9]+|(\[[0-9]+])?\*)? +// (optional) . followed by integer number or *, or neither that specifies precision of the conversion: (\.([0-9]+|(\[[0-9]+])?\*))? +// the prepending (\[[0-9]+])? is for golang explicit argument indexes: +// The same notation before a '*' for a width or precision selects the argument index holding the value. +// (optional) the notation [n] immediately before the verb indicates +// that the nth one-indexed argument is to be formatted instead: (\[[0-9]+])? +// conversion format specifier: [vTtbcdoOqxXUeEfFgGsp] +// %% shouldn't be replaced. +var flagRe, _ = regexp.Compile(`%[+\-# 0]?([0-9]+|(\[[0-9]+])?\*)?(\.([0-9]+|(\[[0-9]+])?\*))?(\[[0-9]+])?[vTtbcdoOqxXUeEfFgGsp]`) + +func replaceFlags(origin string) string { + return string(flagRe.ReplaceAll([]byte(origin), []byte("{placeholder}"))) +} diff --git a/terror_registry.go b/terror_registry.go new file mode 100644 index 00000000..12685638 --- /dev/null +++ b/terror_registry.go @@ -0,0 +1,176 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "fmt" +) + +// Registry is a set of errors for a component. +type Registry struct { + Name string + errClasses map[ErrClassID]ErrClass +} + +// ErrCode represents a specific error type in a error class. +// Same error code can be used in different error classes. +type ErrCode int + +// ErrCodeText is a textual error code that represents a specific error type in a error class. +type ErrCodeText string + +// ErrClass represents a class of errors. +// You can create error 'prototypes' of this class. +type ErrClass struct { + ID ErrClassID + Description string + errors map[ErrorID]*Error + registry *Registry +} + +type ErrorID string +type ErrClassID int +type RFCErrorCode string + +// NewRegistry make a registry, where ErrClasses register to. +// One component should create only one registry, named by the RFC. +// For TiDB ecosystem components, when creating registry, +// please use the component name to name the registry, see below example: +// +// TiKV: KV +// PD: PD +// DM: DM +// BR: BR +// TiCDC: CDC +// Lightning: LN +// TiFlash: FLASH +// Dumpling: DP +func NewRegistry(name string) *Registry { + return &Registry{Name: name, errClasses: map[ErrClassID]ErrClass{}} +} + +// RegisterErrorClass registers new error class for terror. +func (r *Registry) RegisterErrorClass(classCode int, desc string) ErrClass { + code := ErrClassID(classCode) + if _, exists := r.errClasses[code]; exists { + panic(fmt.Sprintf("duplicate register ClassCode %d - %s", code, desc)) + } + errClass := ErrClass{ + ID: code, + Description: desc, + errors: map[ErrorID]*Error{}, + registry: r, + } + r.errClasses[code] = errClass + return errClass +} + +// String implements fmt.Stringer interface. +func (ec *ErrClass) String() string { + return ec.Description +} + +// Equal tests whether the other error is in this class. +func (ec *ErrClass) Equal(other *ErrClass) bool { + if ec == nil || other == nil { + return ec == other + } + return ec.ID == other.ID +} + +// EqualClass returns true if err is *Error with the same class. +func (ec *ErrClass) EqualClass(err error) bool { + e := Cause(err) + if e == nil { + return false + } + if te, ok := e.(*Error); ok { + return te.class.Equal(ec) + } + return false +} + +// NotEqualClass returns true if err is not *Error with the same class. +func (ec *ErrClass) NotEqualClass(err error) bool { + return !ec.EqualClass(err) +} + +// New defines an *Error with an error code and an error message. +// Usually used to create base *Error. +// This function is reserved for compatibility, if possible, use DefineError instead. +func (ec *ErrClass) New(code ErrCode, message string) *Error { + return ec.DefineError(). + NumericCode(code). + MessageTemplate(message). + Build() +} + +// DefineError is the entrance of the define error DSL, +// simple usage: +// ``` +// ClassExecutor.DefineError(). +// TextualCode("ExecutorAbsent"). +// MessageTemplate("executor is taking vacation at %s"). +// Build() +// ``` +func (ec *ErrClass) DefineError() *Builder { + return &Builder{ + err: &Error{}, + class: ec, + } +} + +// RegisterError try to register an error to a class. +// return true if success. +func (ec *ErrClass) RegisterError(err *Error) bool { + if _, ok := ec.errors[err.ID()]; ok { + return false + } + err.class = ec + ec.errors[err.ID()] = err + return true +} + +// AllErrors returns all errors of this ErrClass +// Note this isn't thread-safe. +// You shouldn't modify the returned slice without copying. +func (ec *ErrClass) AllErrors() []*Error { + all := make([]*Error, 0, len(ec.errors)) + for _, err := range ec.errors { + all = append(all, err) + } + return all +} + +// AllErrorClasses returns all errClasses that has been registered. +// Note this isn't thread-safe. +func (r *Registry) AllErrorClasses() []ErrClass { + all := make([]ErrClass, 0, len(r.errClasses)) + for _, errClass := range r.errClasses { + all = append(all, errClass) + } + return all +} + +// Synthesize synthesizes an *Error in the air +// it didn't register error into ErrClass +// so it's goroutine-safe +// and often be used to create Error came from other systems like TiKV. +func (ec *ErrClass) Synthesize(code ErrCode, message string) *Error { + return &Error{ + class: ec, + code: code, + message: message, + } +} diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go new file mode 100644 index 00000000..71ceffab --- /dev/null +++ b/terror_test/terror_test.go @@ -0,0 +1,341 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package terror_test + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "runtime" + "sort" + "strings" + "testing" + "time" + + . "github.com/pingcap/check" + "github.com/pingcap/errors" +) + +// Error classes. +// Those fields below are copied from the original version of terror, +// so that we can reuse those test cases. +var ( + reg = errors.NewRegistry("DB") + ClassExecutor = reg.RegisterErrorClass(5, "executor") + ClassKV = reg.RegisterErrorClass(8, "kv") + ClassOptimizer = reg.RegisterErrorClass(10, "planner") + ClassParser = reg.RegisterErrorClass(11, "parser") + ClassServer = reg.RegisterErrorClass(15, "server") + ClassTable = reg.RegisterErrorClass(19, "table") +) + +const ( + CodeExecResultIsEmpty errors.ErrCode = 3 + CodeMissConnectionID errors.ErrCode = 1 + CodeResultUndetermined errors.ErrCode = 2 +) + +func TestT(t *testing.T) { + CustomVerboseFlag = true + TestingT(t) +} + +var _ = Suite(&testTErrorSuite{}) + +type testTErrorSuite struct { +} + +func (s *testTErrorSuite) TestErrCode(c *C) { + c.Assert(CodeMissConnectionID, Equals, errors.ErrCode(1)) + c.Assert(CodeResultUndetermined, Equals, errors.ErrCode(2)) +} + +func (s *testTErrorSuite) TestTError(c *C) { + c.Assert(ClassParser.String(), Not(Equals), "") + c.Assert(ClassOptimizer.String(), Not(Equals), "") + c.Assert(ClassKV.String(), Not(Equals), "") + c.Assert(ClassServer.String(), Not(Equals), "") + + parserErr := ClassParser.New(errors.ErrCode(100), "error 100") + c.Assert(parserErr.Error(), Not(Equals), "") + c.Assert(ClassParser.EqualClass(parserErr), IsTrue) + c.Assert(ClassParser.NotEqualClass(parserErr), IsFalse) + + c.Assert(ClassOptimizer.EqualClass(parserErr), IsFalse) + optimizerErr := ClassOptimizer.New(errors.ErrCode(2), "abc") + c.Assert(ClassOptimizer.EqualClass(errors.New("abc")), IsFalse) + c.Assert(ClassOptimizer.EqualClass(nil), IsFalse) + c.Assert(optimizerErr.Equal(optimizerErr.GenWithStack("def")), IsTrue) + c.Assert(optimizerErr.Equal(errors.Trace(optimizerErr.GenWithStack("def"))), IsTrue) + c.Assert(optimizerErr.Equal(nil), IsFalse) + c.Assert(optimizerErr.Equal(errors.New("abc")), IsFalse) + + // Test case for FastGen. + c.Assert(optimizerErr.Equal(optimizerErr.FastGen("def")), IsTrue) + c.Assert(optimizerErr.Equal(optimizerErr.FastGen("def: %s", "def")), IsTrue) + kvErr := ClassKV.New(1062, "key already exist") + e := kvErr.FastGen("Duplicate entry '%d' for key 'PRIMARY'", 1) + c.Assert(e, NotNil) + c.Assert(e.Error(), Equals, "[DB:kv:1062] Duplicate entry '1' for key 'PRIMARY'") +} + +func (s *testTErrorSuite) TestJson(c *C) { + prevTErr := ClassTable.New(CodeExecResultIsEmpty, "json test") + buf, err := json.Marshal(prevTErr) + c.Assert(err, IsNil) + var curTErr errors.Error + err = json.Unmarshal(buf, &curTErr) + c.Assert(err, IsNil) + isEqual := prevTErr.Equal(&curTErr) + c.Assert(isEqual, IsTrue) +} + +var predefinedErr = ClassExecutor.New(errors.ErrCode(123), "predefiend error") +var predefinedTextualErr = ClassExecutor.DefineError(). + TextualCode("ExecutorAbsent"). + MessageTemplate("executor is taking vacation at %s"). + Build() + +func example() error { + err := call() + return errors.Trace(err) +} + +func call() error { + return predefinedErr.GenWithStack("error message:%s", "abc") +} + +func (s *testTErrorSuite) TestTraceAndLocation(c *C) { + err := example() + stack := errors.ErrorStack(err) + lines := strings.Split(stack, "\n") + goroot := strings.ReplaceAll(runtime.GOROOT(), string(os.PathSeparator), "/") + var sysStack = 0 + for _, line := range lines { + if strings.Contains(line, goroot) { + sysStack++ + } + } + c.Assert(len(lines)-(2*sysStack), Equals, 15, Commentf("stack =\n%s", stack)) + var containTerr bool + for _, v := range lines { + if strings.Contains(v, "terror_test.go") { + containTerr = true + break + } + } + c.Assert(containTerr, IsTrue) +} + +func (s *testTErrorSuite) TestErrorEqual(c *C) { + e1 := errors.New("test error") + c.Assert(e1, NotNil) + + e2 := errors.Trace(e1) + c.Assert(e2, NotNil) + + e3 := errors.Trace(e2) + c.Assert(e3, NotNil) + + c.Assert(errors.Cause(e2), Equals, e1) + c.Assert(errors.Cause(e3), Equals, e1) + c.Assert(errors.Cause(e2), Equals, errors.Cause(e3)) + + e4 := errors.New("test error") + c.Assert(errors.Cause(e4), Not(Equals), e1) + + e5 := errors.Errorf("test error") + c.Assert(errors.Cause(e5), Not(Equals), e1) + + c.Assert(errors.ErrorEqual(e1, e2), IsTrue) + c.Assert(errors.ErrorEqual(e1, e3), IsTrue) + c.Assert(errors.ErrorEqual(e1, e4), IsTrue) + c.Assert(errors.ErrorEqual(e1, e5), IsTrue) + + var e6 error + + c.Assert(errors.ErrorEqual(nil, nil), IsTrue) + c.Assert(errors.ErrorNotEqual(e1, e6), IsTrue) + code1 := errors.ErrCode(9001) + code2 := errors.ErrCode(9002) + te1 := ClassParser.Synthesize(code1, "abc") + te3 := ClassKV.New(code1, "abc") + te4 := ClassKV.New(code2, "abc") + c.Assert(errors.ErrorEqual(te1, te3), IsFalse) + c.Assert(errors.ErrorEqual(te3, te4), IsFalse) +} + +func (s *testTErrorSuite) TestNewError(c *C) { + today := time.Now().Weekday().String() + err := predefinedTextualErr.GenWithStackByArgs(today) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[DB:executor:ExecutorAbsent] executor is taking vacation at "+today) +} + +func (s *testTErrorSuite) TestAllErrClasses(c *C) { + items := []errors.ErrClass{ + ClassExecutor, ClassKV, ClassOptimizer, ClassParser, ClassServer, ClassTable, + } + registered := reg.AllErrorClasses() + + // sort it to align them. + sort.Slice(items, func(i, j int) bool { + return items[i].ID < items[j].ID + }) + sort.Slice(registered, func(i, j int) bool { + return registered[i].ID < registered[j].ID + }) + + for i := range items { + c.Assert(items[i].ID, Equals, registered[i].ID) + } +} + +func (s *testTErrorSuite) TestErrorExists(c *C) { + origin := ClassParser.DefineError(). + TextualCode("EverythingAlright"). + MessageTemplate("that was a joke, hoo!"). + Build() + + c.Assert(func() { + _ = ClassParser.DefineError(). + TextualCode("EverythingAlright"). + MessageTemplate("that was another joke, hoo!"). + Build() + }, Panics, "replicated error prototype created") + + // difference at either code or text should be different error + changeCode := ClassParser.DefineError(). + NumericCode(4399). + MessageTemplate("that was a joke, hoo!"). + Build() + changeText := ClassParser.DefineError(). + TextualCode("EverythingBad"). + MessageTemplate("that was not a joke, folks!"). + Build() + containsErr := func(e error) bool { + for _, err := range ClassParser.AllErrors() { + if err.Equal(e) { + return true + } + } + return false + } + c.Assert(containsErr(origin), IsTrue) + c.Assert(containsErr(changeCode), IsTrue) + c.Assert(containsErr(changeText), IsTrue) +} + +func (s *testTErrorSuite) TestRFCCode(c *C) { + reg := errors.NewRegistry("TEST") + errc1 := reg.RegisterErrorClass(1, "TestErr1") + errc2 := reg.RegisterErrorClass(2, "TestErr2") + c1err1 := errc1.DefineError(). + TextualCode("Err1"). + MessageTemplate("nothing"). + Build() + c2err2 := errc2.DefineError(). + TextualCode("Err2"). + MessageTemplate("nothing"). + Build() + c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TEST:TestErr1:Err1")) + c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TEST:TestErr2:Err2")) + blankReg := errors.NewRegistry("") + errb := blankReg.RegisterErrorClass(1, "Blank") + berr := errb.DefineError(). + TextualCode("B1"). + MessageTemplate("nothing"). + Workaround(`Do nothing`). + Build() + c.Assert(berr.RFCCode(), Equals, errors.RFCErrorCode("Blank:B1")) +} + +const ( + somewhatErrorTOML = `[error.KV:Somewhat:Foo] +error = '''some {placeholder} thing happened, and some {placeholder} goes verbose. I'm {placeholder} percent confusing... +Maybe only {placeholder} peaces of placeholders can save me... Oh my {placeholder}.{placeholder}!''' +description = '''N/A''' +workaround = '''N/A''' +` + err8005TOML = `[error.KV:2PC:8005] +error = '''Write Conflict, txnStartTS is stale''' +description = '''A certain Raft Group is not available, such as the number of replicas is not enough. +This error usually occurs when the TiKV server is busy or the TiKV node is down.''' +` + "workaround = '''Check whether `tidb_disable_txn_auto_retry` is set to `on`. If so, set it to `off`; " + + "if it is already `off`, increase the value of `tidb_retry_limit` until the error no longer occurs.'''\n" + errUnavailableTOML = `[error.KV:Region:Unavailable] +error = '''Region is unavailable''' +description = '''A certain Raft Group is not available, such as the number of replicas is not enough. +This error usually occurs when the TiKV server is busy or the TiKV node is down.''' +workaround = '''Check the status, monitoring data and log of the TiKV server.''' +` +) + +func (*testTErrorSuite) TestExport(c *C) { + RegKV := errors.NewRegistry("KV") + Class2PC := RegKV.RegisterErrorClass(1, "2PC") + _ = Class2PC.DefineError(). + NumericCode(8005). + Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + + "This error usually occurs when the TiKV server is busy or the TiKV node is down."). + Workaround("Check whether `tidb_disable_txn_auto_retry` is set to `on`. If so, set it to `off`; " + + "if it is already `off`, increase the value of `tidb_retry_limit` until the error no longer occurs."). + MessageTemplate("Write Conflict, txnStartTS is stale"). + Build() + + ClassRegion := RegKV.RegisterErrorClass(2, "Region") + _ = ClassRegion.DefineError(). + TextualCode("Unavailable"). + Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + + "This error usually occurs when the TiKV server is busy or the TiKV node is down."). + Workaround("Check the status, monitoring data and log of the TiKV server."). + MessageTemplate("Region is unavailable"). + Build() + + ClassSomewhat := RegKV.RegisterErrorClass(3, "Somewhat") + _ = ClassSomewhat.DefineError(). + TextualCode("Foo"). + MessageTemplate("some %.6s thing happened, and some %#v goes verbose. I'm %6.3f percent confusing...\n" + + "Maybe only %[3]*.[2]*[1]f peaces of placeholders can save me... Oh my %s.%d!"). + Build() + + result := bytes.NewBuffer([]byte{}) + err := RegKV.ExportTo(result) + c.Assert(err, IsNil) + resultStr := result.String() + fmt.Println("Result: ") + fmt.Print(resultStr) + c.Assert(strings.Contains(resultStr, somewhatErrorTOML), IsTrue) + c.Assert(strings.Contains(resultStr, err8005TOML), IsTrue) + c.Assert(strings.Contains(resultStr, errUnavailableTOML), IsTrue) +} + +func (*testTErrorSuite) TestLineAndFile(c *C) { + err := predefinedTextualErr.GenWithStackByArgs("everyday") + _, f, l, _ := runtime.Caller(0) + terr, ok := errors.Cause(err).(*errors.Error) + c.Assert(ok, IsTrue) + file, line := terr.Location() + c.Assert(file, Equals, f) + c.Assert(line, Equals, l-1) + + err2 := predefinedTextualErr.GenWithStackByArgs("everyday and everywhere") + _, f2, l2, _ := runtime.Caller(0) + terr2, ok2 := errors.Cause(err2).(*errors.Error) + c.Assert(ok2, IsTrue) + file2, line2 := terr2.Location() + c.Assert(file2, Equals, f2) + c.Assert(line2, Equals, l2-1) +} From eba4f1d8f6de6d151bbfc112b8a90d5bfeb4f8bf Mon Sep 17 00:00:00 2001 From: Lingyu Song Date: Wed, 2 Sep 2020 18:42:58 +0800 Subject: [PATCH 18/59] add standard error library (#24) --- .gitignore | 1 + terror_dsl.go | 73 ------------- terror_error.go | 155 ++++++++++++++++++--------- terror_gen.go | 79 -------------- terror_registry.go | 176 ------------------------------- terror_test/terror_test.go | 209 +++---------------------------------- 6 files changed, 124 insertions(+), 569 deletions(-) delete mode 100644 terror_dsl.go delete mode 100644 terror_gen.go delete mode 100644 terror_registry.go diff --git a/.gitignore b/.gitignore index daf913b1..0c9e3298 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Folders _obj _test +.idea # Architecture specific extensions/prefixes *.[568vq] diff --git a/terror_dsl.go b/terror_dsl.go deleted file mode 100644 index e2e55eed..00000000 --- a/terror_dsl.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errors - -import ( - "github.com/pingcap/log" - "go.uber.org/zap" -) - -// Builder is the builder of Error. -type Builder struct { - err *Error - class *ErrClass -} - -// TextualCode is is the textual identity of this error internally, -// note that this error code can only be duplicated in different Registry or ErrorClass. -func (b *Builder) TextualCode(text ErrCodeText) *Builder { - b.err.codeText = text - return b -} - -// NumericCode is is the numeric identity of this error internally, -// note that this error code can only be duplicated in different Registry or ErrorClass. -func (b *Builder) NumericCode(num ErrCode) *Builder { - b.err.code = num - return b -} - -// Description is the expanded detail of why this error occurred. -// This could be written by developer at a static env, -// and the more detail this field explaining the better, -// even some guess of the cause could be included. -func (b *Builder) Description(desc string) *Builder { - b.err.Description = desc - return b -} - -// Workaround shows how to work around this error. -// It's used to teach the users how to solve the error if occurring in the real environment. -func (b *Builder) Workaround(wd string) *Builder { - b.err.Workaround = wd - return b -} - -// MessageTemplate is the template of the error string that can be formatted after -// calling `GenWithArgs` method. -// currently, printf style template is used. -func (b *Builder) MessageTemplate(template string) *Builder { - b.err.message = template - return b -} - -// Build ends the define of the error. -func (b *Builder) Build() *Error { - if ok := b.class.RegisterError(b.err); !ok { - log.Panic("replicated error prototype created", - zap.String("ID", string(b.err.ID())), - zap.String("RFCCode", string(b.err.RFCCode()))) - } - return b.err -} diff --git a/terror_error.go b/terror_error.go index f6b147e8..ad48c996 100644 --- a/terror_error.go +++ b/terror_error.go @@ -21,6 +21,16 @@ import ( "strings" ) +// ErrCode represents a specific error type in a error class. +// Same error code can be used in different error classes. +type ErrCode int + +// ErrCodeText is a textual error code that represents a specific error type in a error class. +type ErrCodeText string + +type ErrorID string +type RFCErrorCode string + // Error is the 'prototype' of a type of errors. // Use DefineError to make a *Error: // var ErrUnavailable = ClassRegion.DefineError(). @@ -46,8 +56,7 @@ import ( // // handle this error. // } type Error struct { - class *ErrClass - code ErrCode + code ErrCode // codeText is the textual describe of the error code codeText ErrCodeText // message is a template of the description of this error. @@ -55,20 +64,17 @@ type Error struct { message string // The workaround field: how to work around this error. // It's used to teach the users how to solve the error if occurring in the real environment. - Workaround string + workaround string // Description is the expanded detail of why this error occurred. // This could be written by developer at a static env, // and the more detail this field explaining the better, // even some guess of the cause could be included. - Description string - args []interface{} - file string - line int -} - -// Class returns ErrClass -func (e *Error) Class() *ErrClass { - return e.class + description string + // Cause is used to warp some third party error. + cause error + args []interface{} + file string + line int } // Code returns the numeric code of this error. @@ -84,23 +90,7 @@ func (e *Error) Code() ErrCode { // The error code is a 3-tuple of abbreviated component name, error class and error code, // joined by a colon like {Component}:{ErrorClass}:{InnerErrorCode}. func (e *Error) RFCCode() RFCErrorCode { - ec := e.Class() - if ec == nil { - return RFCErrorCode(e.ID()) - } - reg := ec.registry - // Maybe top-level errors. - if reg.Name == "" { - return RFCErrorCode(fmt.Sprintf("%s:%s", - ec.Description, - e.ID(), - )) - } - return RFCErrorCode(fmt.Sprintf("%s:%s:%s", - reg.Name, - ec.Description, - e.ID(), - )) + return RFCErrorCode(e.ID()) } // ID returns the ID of this error. @@ -124,14 +114,17 @@ func (e *Error) MessageTemplate() string { // Error implements error interface. func (e *Error) Error() string { + if e == nil { + return "" + } describe := e.codeText if len(describe) == 0 { describe = ErrCodeText(strconv.Itoa(int(e.code))) } - return fmt.Sprintf("[%s] %s", e.RFCCode(), e.getMsg()) + return fmt.Sprintf("[%s]%s", e.RFCCode(), e.GetMsg()) } -func (e *Error) getMsg() string { +func (e *Error) GetMsg() string { if len(e.args) > 0 { return fmt.Sprintf(e.message, e.args...) } @@ -197,9 +190,8 @@ func (e *Error) Equal(err error) bool { if !ok { return false } - classEquals := e.class.Equal(inErr.class) idEquals := e.ID() == inErr.ID() - return classEquals && idEquals + return idEquals } // NotEqual checks if err is not equal to e. @@ -239,7 +231,6 @@ type jsonError struct { Error string `json:"message"` Description string `json:"description,omitempty"` Workaround string `json:"workaround,omitempty"` - Class ErrClassID `json:"classID"` File string `json:"file"` Line int `json:"line"` } @@ -251,11 +242,10 @@ type jsonError struct { // This function is reserved for compatibility. func (e *Error) MarshalJSON() ([]byte, error) { return json.Marshal(&jsonError{ - Error: e.getMsg(), - Description: e.Description, - Workaround: e.Workaround, + Error: e.GetMsg(), + Description: e.description, + Workaround: e.workaround, RFCCode: e.RFCCode(), - Class: e.class.ID, Line: e.line, File: e.file, }) @@ -273,21 +263,90 @@ func (e *Error) UnmarshalJSON(data []byte) error { return Trace(err) } codes := strings.Split(string(err.RFCCode), ":") - regName := codes[0] - className := codes[1] - innerCode := codes[2] + innerCode := codes[len(codes)-1] if i, errAtoi := strconv.Atoi(innerCode); errAtoi == nil { e.code = ErrCode(i) - } else { - e.codeText = ErrCodeText(innerCode) } + e.codeText = ErrCodeText(err.RFCCode) e.line = err.Line e.file = err.File e.message = err.Error - e.class = &ErrClass{ - Description: className, - ID: err.Class, - registry: &Registry{Name: regName}, - } return nil } + +func (e *Error) Wrap(err error) *Error { + if err != nil { + newErr := *e + newErr.cause = err + return &newErr + } + return e +} + +func (e *Error) Cause() error { + root := Unwrap(e.cause) + if root == nil { + return e.cause + } + return root +} + +func (e *Error) FastGenWithCause(args ...interface{}) error { + err := *e + if e.cause != nil { + err.message = e.cause.Error() + } + err.args = args + return SuspendStack(&err) +} + +func (e *Error) GenWithStackByCause(args ...interface{}) error { + err := *e + if e.cause != nil { + err.message = e.cause.Error() + } + err.args = args + err.fillLineAndFile(1) + return AddStack(&err) +} + +type NormalizeOption func(*Error) + +// Description returns a NormalizeOption to set description. +func Description(desc string) NormalizeOption { + return func(e *Error) { + e.description = desc + } +} + +// Workaround returns a NormalizeOption to set workaround. +func Workaround(wr string) NormalizeOption { + return func(e *Error) { + e.workaround = wr + } +} + +// RFCCodeText returns a NormalizeOption to set RFC error code. +func RFCCodeText(codeText string) NormalizeOption { + return func(e *Error) { + e.codeText = ErrCodeText(codeText) + } +} + +// MySQLErrorCode returns a NormalizeOption to set error code. +func MySQLErrorCode(code int) NormalizeOption { + return func(e *Error) { + e.code = ErrCode(code) + } +} + +// Normalize creates a new Error object. +func Normalize(message string, opts ...NormalizeOption) *Error { + e := &Error{ + message: message, + } + for _, opt := range opts { + opt(e) + } + return e +} diff --git a/terror_gen.go b/terror_gen.go deleted file mode 100644 index 71d9a900..00000000 --- a/terror_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errors - -import ( - "fmt" - "github.com/pingcap/log" - "go.uber.org/zap" - "io" - "regexp" -) - -const tomlTemplate = `[error.%s] -error = '''%s''' -description = '''%s''' -workaround = '''%s''' -` - -func (e *Error) exportTo(writer io.Writer) error { - desc := e.Description - if e.Description == "" { - log.Warn("error description missed", zap.String("error", string(e.RFCCode()))) - desc = "N/A" - } - workaround := e.Workaround - if e.Workaround == "" { - log.Warn("error workaround missed", zap.String("error", string(e.RFCCode()))) - workaround = "N/A" - } - _, err := fmt.Fprintf(writer, tomlTemplate, e.RFCCode(), replaceFlags(e.MessageTemplate()), desc, workaround) - return err -} - -func (ec *ErrClass) exportTo(writer io.Writer) error { - for _, e := range ec.AllErrors() { - if err := e.exportTo(writer); err != nil { - return err - } - } - return nil -} - -// ExportTo export the registry to a writer, as toml format from the RFC. -func (r *Registry) ExportTo(writer io.Writer) error { - for _, ec := range r.errClasses { - if err := ec.exportTo(writer); err != nil { - return err - } - } - return nil -} - -// CPP printf flag with minimal support for explicit argument indexes. -// introductory % character: % -// (optional) one or more flags that modify the behavior of the conversion: [+\-# 0]? -// (optional) integer value or * that specifies minimum field width: ([0-9]+|(\[[0-9]+])?\*)? -// (optional) . followed by integer number or *, or neither that specifies precision of the conversion: (\.([0-9]+|(\[[0-9]+])?\*))? -// the prepending (\[[0-9]+])? is for golang explicit argument indexes: -// The same notation before a '*' for a width or precision selects the argument index holding the value. -// (optional) the notation [n] immediately before the verb indicates -// that the nth one-indexed argument is to be formatted instead: (\[[0-9]+])? -// conversion format specifier: [vTtbcdoOqxXUeEfFgGsp] -// %% shouldn't be replaced. -var flagRe, _ = regexp.Compile(`%[+\-# 0]?([0-9]+|(\[[0-9]+])?\*)?(\.([0-9]+|(\[[0-9]+])?\*))?(\[[0-9]+])?[vTtbcdoOqxXUeEfFgGsp]`) - -func replaceFlags(origin string) string { - return string(flagRe.ReplaceAll([]byte(origin), []byte("{placeholder}"))) -} diff --git a/terror_registry.go b/terror_registry.go deleted file mode 100644 index 12685638..00000000 --- a/terror_registry.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2020 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errors - -import ( - "fmt" -) - -// Registry is a set of errors for a component. -type Registry struct { - Name string - errClasses map[ErrClassID]ErrClass -} - -// ErrCode represents a specific error type in a error class. -// Same error code can be used in different error classes. -type ErrCode int - -// ErrCodeText is a textual error code that represents a specific error type in a error class. -type ErrCodeText string - -// ErrClass represents a class of errors. -// You can create error 'prototypes' of this class. -type ErrClass struct { - ID ErrClassID - Description string - errors map[ErrorID]*Error - registry *Registry -} - -type ErrorID string -type ErrClassID int -type RFCErrorCode string - -// NewRegistry make a registry, where ErrClasses register to. -// One component should create only one registry, named by the RFC. -// For TiDB ecosystem components, when creating registry, -// please use the component name to name the registry, see below example: -// -// TiKV: KV -// PD: PD -// DM: DM -// BR: BR -// TiCDC: CDC -// Lightning: LN -// TiFlash: FLASH -// Dumpling: DP -func NewRegistry(name string) *Registry { - return &Registry{Name: name, errClasses: map[ErrClassID]ErrClass{}} -} - -// RegisterErrorClass registers new error class for terror. -func (r *Registry) RegisterErrorClass(classCode int, desc string) ErrClass { - code := ErrClassID(classCode) - if _, exists := r.errClasses[code]; exists { - panic(fmt.Sprintf("duplicate register ClassCode %d - %s", code, desc)) - } - errClass := ErrClass{ - ID: code, - Description: desc, - errors: map[ErrorID]*Error{}, - registry: r, - } - r.errClasses[code] = errClass - return errClass -} - -// String implements fmt.Stringer interface. -func (ec *ErrClass) String() string { - return ec.Description -} - -// Equal tests whether the other error is in this class. -func (ec *ErrClass) Equal(other *ErrClass) bool { - if ec == nil || other == nil { - return ec == other - } - return ec.ID == other.ID -} - -// EqualClass returns true if err is *Error with the same class. -func (ec *ErrClass) EqualClass(err error) bool { - e := Cause(err) - if e == nil { - return false - } - if te, ok := e.(*Error); ok { - return te.class.Equal(ec) - } - return false -} - -// NotEqualClass returns true if err is not *Error with the same class. -func (ec *ErrClass) NotEqualClass(err error) bool { - return !ec.EqualClass(err) -} - -// New defines an *Error with an error code and an error message. -// Usually used to create base *Error. -// This function is reserved for compatibility, if possible, use DefineError instead. -func (ec *ErrClass) New(code ErrCode, message string) *Error { - return ec.DefineError(). - NumericCode(code). - MessageTemplate(message). - Build() -} - -// DefineError is the entrance of the define error DSL, -// simple usage: -// ``` -// ClassExecutor.DefineError(). -// TextualCode("ExecutorAbsent"). -// MessageTemplate("executor is taking vacation at %s"). -// Build() -// ``` -func (ec *ErrClass) DefineError() *Builder { - return &Builder{ - err: &Error{}, - class: ec, - } -} - -// RegisterError try to register an error to a class. -// return true if success. -func (ec *ErrClass) RegisterError(err *Error) bool { - if _, ok := ec.errors[err.ID()]; ok { - return false - } - err.class = ec - ec.errors[err.ID()] = err - return true -} - -// AllErrors returns all errors of this ErrClass -// Note this isn't thread-safe. -// You shouldn't modify the returned slice without copying. -func (ec *ErrClass) AllErrors() []*Error { - all := make([]*Error, 0, len(ec.errors)) - for _, err := range ec.errors { - all = append(all, err) - } - return all -} - -// AllErrorClasses returns all errClasses that has been registered. -// Note this isn't thread-safe. -func (r *Registry) AllErrorClasses() []ErrClass { - all := make([]ErrClass, 0, len(r.errClasses)) - for _, errClass := range r.errClasses { - all = append(all, errClass) - } - return all -} - -// Synthesize synthesizes an *Error in the air -// it didn't register error into ErrClass -// so it's goroutine-safe -// and often be used to create Error came from other systems like TiKV. -func (ec *ErrClass) Synthesize(code ErrCode, message string) *Error { - return &Error{ - class: ec, - code: code, - message: message, - } -} diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 71ceffab..eecd9e94 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -14,12 +14,9 @@ package terror_test import ( - "bytes" "encoding/json" - "fmt" "os" "runtime" - "sort" "strings" "testing" "time" @@ -28,19 +25,6 @@ import ( "github.com/pingcap/errors" ) -// Error classes. -// Those fields below are copied from the original version of terror, -// so that we can reuse those test cases. -var ( - reg = errors.NewRegistry("DB") - ClassExecutor = reg.RegisterErrorClass(5, "executor") - ClassKV = reg.RegisterErrorClass(8, "kv") - ClassOptimizer = reg.RegisterErrorClass(10, "planner") - ClassParser = reg.RegisterErrorClass(11, "parser") - ClassServer = reg.RegisterErrorClass(15, "server") - ClassTable = reg.RegisterErrorClass(19, "table") -) - const ( CodeExecResultIsEmpty errors.ErrCode = 3 CodeMissConnectionID errors.ErrCode = 1 @@ -62,37 +46,8 @@ func (s *testTErrorSuite) TestErrCode(c *C) { c.Assert(CodeResultUndetermined, Equals, errors.ErrCode(2)) } -func (s *testTErrorSuite) TestTError(c *C) { - c.Assert(ClassParser.String(), Not(Equals), "") - c.Assert(ClassOptimizer.String(), Not(Equals), "") - c.Assert(ClassKV.String(), Not(Equals), "") - c.Assert(ClassServer.String(), Not(Equals), "") - - parserErr := ClassParser.New(errors.ErrCode(100), "error 100") - c.Assert(parserErr.Error(), Not(Equals), "") - c.Assert(ClassParser.EqualClass(parserErr), IsTrue) - c.Assert(ClassParser.NotEqualClass(parserErr), IsFalse) - - c.Assert(ClassOptimizer.EqualClass(parserErr), IsFalse) - optimizerErr := ClassOptimizer.New(errors.ErrCode(2), "abc") - c.Assert(ClassOptimizer.EqualClass(errors.New("abc")), IsFalse) - c.Assert(ClassOptimizer.EqualClass(nil), IsFalse) - c.Assert(optimizerErr.Equal(optimizerErr.GenWithStack("def")), IsTrue) - c.Assert(optimizerErr.Equal(errors.Trace(optimizerErr.GenWithStack("def"))), IsTrue) - c.Assert(optimizerErr.Equal(nil), IsFalse) - c.Assert(optimizerErr.Equal(errors.New("abc")), IsFalse) - - // Test case for FastGen. - c.Assert(optimizerErr.Equal(optimizerErr.FastGen("def")), IsTrue) - c.Assert(optimizerErr.Equal(optimizerErr.FastGen("def: %s", "def")), IsTrue) - kvErr := ClassKV.New(1062, "key already exist") - e := kvErr.FastGen("Duplicate entry '%d' for key 'PRIMARY'", 1) - c.Assert(e, NotNil) - c.Assert(e.Error(), Equals, "[DB:kv:1062] Duplicate entry '1' for key 'PRIMARY'") -} - func (s *testTErrorSuite) TestJson(c *C) { - prevTErr := ClassTable.New(CodeExecResultIsEmpty, "json test") + prevTErr := errors.Normalize("json test", errors.MySQLErrorCode(int(CodeExecResultIsEmpty)), errors.RFCCodeText("abc:1105")) buf, err := json.Marshal(prevTErr) c.Assert(err, IsNil) var curTErr errors.Error @@ -102,11 +57,8 @@ func (s *testTErrorSuite) TestJson(c *C) { c.Assert(isEqual, IsTrue) } -var predefinedErr = ClassExecutor.New(errors.ErrCode(123), "predefiend error") -var predefinedTextualErr = ClassExecutor.DefineError(). - TextualCode("ExecutorAbsent"). - MessageTemplate("executor is taking vacation at %s"). - Build() +var predefinedErr = errors.Normalize("predefiend error", errors.MySQLErrorCode(123)) +var predefinedTextualErr = errors.Normalize("executor is taking vacation at %s", errors.RFCCodeText("executor:ExecutorAbsent")) func example() error { err := call() @@ -168,160 +120,24 @@ func (s *testTErrorSuite) TestErrorEqual(c *C) { c.Assert(errors.ErrorEqual(nil, nil), IsTrue) c.Assert(errors.ErrorNotEqual(e1, e6), IsTrue) - code1 := errors.ErrCode(9001) - code2 := errors.ErrCode(9002) - te1 := ClassParser.Synthesize(code1, "abc") - te3 := ClassKV.New(code1, "abc") - te4 := ClassKV.New(code2, "abc") - c.Assert(errors.ErrorEqual(te1, te3), IsFalse) - c.Assert(errors.ErrorEqual(te3, te4), IsFalse) } func (s *testTErrorSuite) TestNewError(c *C) { today := time.Now().Weekday().String() err := predefinedTextualErr.GenWithStackByArgs(today) c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "[DB:executor:ExecutorAbsent] executor is taking vacation at "+today) -} - -func (s *testTErrorSuite) TestAllErrClasses(c *C) { - items := []errors.ErrClass{ - ClassExecutor, ClassKV, ClassOptimizer, ClassParser, ClassServer, ClassTable, - } - registered := reg.AllErrorClasses() - - // sort it to align them. - sort.Slice(items, func(i, j int) bool { - return items[i].ID < items[j].ID - }) - sort.Slice(registered, func(i, j int) bool { - return registered[i].ID < registered[j].ID - }) - - for i := range items { - c.Assert(items[i].ID, Equals, registered[i].ID) - } -} - -func (s *testTErrorSuite) TestErrorExists(c *C) { - origin := ClassParser.DefineError(). - TextualCode("EverythingAlright"). - MessageTemplate("that was a joke, hoo!"). - Build() - - c.Assert(func() { - _ = ClassParser.DefineError(). - TextualCode("EverythingAlright"). - MessageTemplate("that was another joke, hoo!"). - Build() - }, Panics, "replicated error prototype created") - - // difference at either code or text should be different error - changeCode := ClassParser.DefineError(). - NumericCode(4399). - MessageTemplate("that was a joke, hoo!"). - Build() - changeText := ClassParser.DefineError(). - TextualCode("EverythingBad"). - MessageTemplate("that was not a joke, folks!"). - Build() - containsErr := func(e error) bool { - for _, err := range ClassParser.AllErrors() { - if err.Equal(e) { - return true - } - } - return false - } - c.Assert(containsErr(origin), IsTrue) - c.Assert(containsErr(changeCode), IsTrue) - c.Assert(containsErr(changeText), IsTrue) + c.Assert(err.Error(), Equals, "[executor:ExecutorAbsent]executor is taking vacation at "+today) } func (s *testTErrorSuite) TestRFCCode(c *C) { - reg := errors.NewRegistry("TEST") - errc1 := reg.RegisterErrorClass(1, "TestErr1") - errc2 := reg.RegisterErrorClass(2, "TestErr2") - c1err1 := errc1.DefineError(). - TextualCode("Err1"). - MessageTemplate("nothing"). - Build() - c2err2 := errc2.DefineError(). - TextualCode("Err2"). - MessageTemplate("nothing"). - Build() - c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TEST:TestErr1:Err1")) - c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TEST:TestErr2:Err2")) - blankReg := errors.NewRegistry("") - errb := blankReg.RegisterErrorClass(1, "Blank") - berr := errb.DefineError(). - TextualCode("B1"). - MessageTemplate("nothing"). - Workaround(`Do nothing`). - Build() + c1err1 := errors.Normalize("nothing", errors.RFCCodeText("TestErr1:Err1")) + c2err2 := errors.Normalize("nothing", errors.RFCCodeText("TestErr2:Err2")) + c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TestErr1:Err1")) + c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TestErr2:Err2")) + berr := errors.Normalize("nothing", errors.RFCCodeText("Blank:B1"), errors.Workaround(`Do nothing`)) c.Assert(berr.RFCCode(), Equals, errors.RFCErrorCode("Blank:B1")) } -const ( - somewhatErrorTOML = `[error.KV:Somewhat:Foo] -error = '''some {placeholder} thing happened, and some {placeholder} goes verbose. I'm {placeholder} percent confusing... -Maybe only {placeholder} peaces of placeholders can save me... Oh my {placeholder}.{placeholder}!''' -description = '''N/A''' -workaround = '''N/A''' -` - err8005TOML = `[error.KV:2PC:8005] -error = '''Write Conflict, txnStartTS is stale''' -description = '''A certain Raft Group is not available, such as the number of replicas is not enough. -This error usually occurs when the TiKV server is busy or the TiKV node is down.''' -` + "workaround = '''Check whether `tidb_disable_txn_auto_retry` is set to `on`. If so, set it to `off`; " + - "if it is already `off`, increase the value of `tidb_retry_limit` until the error no longer occurs.'''\n" - errUnavailableTOML = `[error.KV:Region:Unavailable] -error = '''Region is unavailable''' -description = '''A certain Raft Group is not available, such as the number of replicas is not enough. -This error usually occurs when the TiKV server is busy or the TiKV node is down.''' -workaround = '''Check the status, monitoring data and log of the TiKV server.''' -` -) - -func (*testTErrorSuite) TestExport(c *C) { - RegKV := errors.NewRegistry("KV") - Class2PC := RegKV.RegisterErrorClass(1, "2PC") - _ = Class2PC.DefineError(). - NumericCode(8005). - Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + - "This error usually occurs when the TiKV server is busy or the TiKV node is down."). - Workaround("Check whether `tidb_disable_txn_auto_retry` is set to `on`. If so, set it to `off`; " + - "if it is already `off`, increase the value of `tidb_retry_limit` until the error no longer occurs."). - MessageTemplate("Write Conflict, txnStartTS is stale"). - Build() - - ClassRegion := RegKV.RegisterErrorClass(2, "Region") - _ = ClassRegion.DefineError(). - TextualCode("Unavailable"). - Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + - "This error usually occurs when the TiKV server is busy or the TiKV node is down."). - Workaround("Check the status, monitoring data and log of the TiKV server."). - MessageTemplate("Region is unavailable"). - Build() - - ClassSomewhat := RegKV.RegisterErrorClass(3, "Somewhat") - _ = ClassSomewhat.DefineError(). - TextualCode("Foo"). - MessageTemplate("some %.6s thing happened, and some %#v goes verbose. I'm %6.3f percent confusing...\n" + - "Maybe only %[3]*.[2]*[1]f peaces of placeholders can save me... Oh my %s.%d!"). - Build() - - result := bytes.NewBuffer([]byte{}) - err := RegKV.ExportTo(result) - c.Assert(err, IsNil) - resultStr := result.String() - fmt.Println("Result: ") - fmt.Print(resultStr) - c.Assert(strings.Contains(resultStr, somewhatErrorTOML), IsTrue) - c.Assert(strings.Contains(resultStr, err8005TOML), IsTrue) - c.Assert(strings.Contains(resultStr, errUnavailableTOML), IsTrue) -} - func (*testTErrorSuite) TestLineAndFile(c *C) { err := predefinedTextualErr.GenWithStackByArgs("everyday") _, f, l, _ := runtime.Caller(0) @@ -339,3 +155,10 @@ func (*testTErrorSuite) TestLineAndFile(c *C) { c.Assert(file2, Equals, f2) c.Assert(line2, Equals, l2-1) } + +func (*testTErrorSuite) TestWarpAndField(c *C) { + causeErr := errors.New("load from etcd meet error") + ErrGetLeader := errors.Normalize("fail to get leader", errors.RFCCodeText("member:ErrGetLeader")) + errWithWarpedCause := errors.Annotate(ErrGetLeader, causeErr.Error()) + c.Assert(errWithWarpedCause.Error(), Equals, "load from etcd meet error: [member:ErrGetLeader]fail to get leader") +} From a15ef68f753d253ec4adcb3c0effa5f5ebd3af88 Mon Sep 17 00:00:00 2001 From: Lingyu Song Date: Thu, 17 Sep 2020 19:18:40 +0800 Subject: [PATCH 19/59] fix `Error.Wrap` and adjust compatibility of json related function (#26) --- terror_error.go | 86 +++++++++++++++++++++++++++----------- terror_test/terror_test.go | 23 +++++----- 2 files changed, 74 insertions(+), 35 deletions(-) diff --git a/terror_error.go b/terror_error.go index ad48c996..243e51e6 100644 --- a/terror_error.go +++ b/terror_error.go @@ -31,6 +31,50 @@ type ErrCodeText string type ErrorID string type RFCErrorCode string +// class2RFCCode is used for compatible with old version of TiDB. When +// marshal Error to json, old version of TiDB contain a 'class' field +// which is represented for error class. In order to parser and convert +// json to errors.Error, using this map to convert error class to RFC +// error code text. here is reference: +// https://github.com/pingcap/parser/blob/release-3.0/terror/terror.go#L58 +var class2RFCCode = map[int]string{ + 1: "autoid", + 2: "ddl", + 3: "domain", + 4: "evaluator", + 5: "executor", + 6: "expression", + 7: "admin", + 8: "kv", + 9: "meta", + 10: "planner", + 11: "parser", + 12: "perfschema", + 13: "privilege", + 14: "schema", + 15: "server", + 16: "struct", + 17: "variable", + 18: "xeval", + 19: "table", + 20: "types", + 21: "global", + 22: "mocktikv", + 23: "json", + 24: "tikv", + 25: "session", + 26: "plugin", + 27: "util", +} +var rfcCode2class map[string]int + +func init() { + rfcCode2class = make(map[string]int) + for k, v := range class2RFCCode { + rfcCode2class[v] = k + } +} + // Error is the 'prototype' of a type of errors. // Use DefineError to make a *Error: // var ErrUnavailable = ClassRegion.DefineError(). @@ -227,12 +271,11 @@ func ErrorNotEqual(err1, err2 error) bool { } type jsonError struct { - RFCCode RFCErrorCode `json:"code"` - Error string `json:"message"` - Description string `json:"description,omitempty"` - Workaround string `json:"workaround,omitempty"` - File string `json:"file"` - Line int `json:"line"` + // Deprecated field, please use `RFCCode` instead. + Class int `json:"class"` + Code int `json:"code"` + Msg string `json:"message"` + RFCCode string `json:"rfccode"` } // MarshalJSON implements json.Marshaler interface. @@ -241,13 +284,12 @@ type jsonError struct { // and the original global registry would be removed here. // This function is reserved for compatibility. func (e *Error) MarshalJSON() ([]byte, error) { + ec := strings.Split(string(e.codeText), ":")[0] return json.Marshal(&jsonError{ - Error: e.GetMsg(), - Description: e.description, - Workaround: e.workaround, - RFCCode: e.RFCCode(), - Line: e.line, - File: e.file, + Class: rfcCode2class[ec], + Code: int(e.code), + Msg: e.GetMsg(), + RFCCode: string(e.codeText), }) } @@ -257,20 +299,16 @@ func (e *Error) MarshalJSON() ([]byte, error) { // and the original global registry is removed. // This function is reserved for compatibility. func (e *Error) UnmarshalJSON(data []byte) error { - err := &jsonError{} - - if err := json.Unmarshal(data, &err); err != nil { + tErr := &jsonError{} + if err := json.Unmarshal(data, &tErr); err != nil { return Trace(err) } - codes := strings.Split(string(err.RFCCode), ":") - innerCode := codes[len(codes)-1] - if i, errAtoi := strconv.Atoi(innerCode); errAtoi == nil { - e.code = ErrCode(i) + e.codeText = ErrCodeText(tErr.RFCCode) + if tErr.RFCCode == "" && tErr.Class > 0 { + e.codeText = ErrCodeText(class2RFCCode[tErr.Class] + ":" + strconv.Itoa(tErr.Code)) } - e.codeText = ErrCodeText(err.RFCCode) - e.line = err.Line - e.file = err.File - e.message = err.Error + e.code = ErrCode(tErr.Code) + e.message = tErr.Msg return nil } @@ -280,7 +318,7 @@ func (e *Error) Wrap(err error) *Error { newErr.cause = err return &newErr } - return e + return nil } func (e *Error) Cause() error { diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index eecd9e94..1b88b000 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -46,17 +46,6 @@ func (s *testTErrorSuite) TestErrCode(c *C) { c.Assert(CodeResultUndetermined, Equals, errors.ErrCode(2)) } -func (s *testTErrorSuite) TestJson(c *C) { - prevTErr := errors.Normalize("json test", errors.MySQLErrorCode(int(CodeExecResultIsEmpty)), errors.RFCCodeText("abc:1105")) - buf, err := json.Marshal(prevTErr) - c.Assert(err, IsNil) - var curTErr errors.Error - err = json.Unmarshal(buf, &curTErr) - c.Assert(err, IsNil) - isEqual := prevTErr.Equal(&curTErr) - c.Assert(isEqual, IsTrue) -} - var predefinedErr = errors.Normalize("predefiend error", errors.MySQLErrorCode(123)) var predefinedTextualErr = errors.Normalize("executor is taking vacation at %s", errors.RFCCodeText("executor:ExecutorAbsent")) @@ -69,6 +58,18 @@ func call() error { return predefinedErr.GenWithStack("error message:%s", "abc") } +func (s *testTErrorSuite) TestJson(c *C) { + tmpErr := errors.Normalize("this is a test error", errors.RFCCodeText("ddl:-1"), errors.MySQLErrorCode(-1)) + buf, err := json.Marshal(tmpErr) + c.Assert(err, IsNil) + var curTErr errors.Error + err = json.Unmarshal(buf, &curTErr) + c.Assert(err, IsNil) + isEqual := tmpErr.Equal(&curTErr) + c.Assert(curTErr.Error(), Equals, tmpErr.Error()) + c.Assert(isEqual, IsTrue) +} + func (s *testTErrorSuite) TestTraceAndLocation(c *C) { err := example() stack := errors.ErrorStack(err) From bbdd097cc9e8436c24c0c70b779e453cda9edaa3 Mon Sep 17 00:00:00 2001 From: Feng Liyuan Date: Thu, 15 Oct 2020 19:11:40 +0800 Subject: [PATCH 20/59] redact arguments for Error (#27) --- terror_error.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/terror_error.go b/terror_error.go index 243e51e6..df097fe1 100644 --- a/terror_error.go +++ b/terror_error.go @@ -21,6 +21,9 @@ import ( "strings" ) +// RedactLogEnabled defines whether the arguments of Error need to be redacted. +var RedactLogEnabled bool = false + // ErrCode represents a specific error type in a error class. // Same error code can be used in different error classes. type ErrCode int @@ -109,11 +112,16 @@ type Error struct { // The workaround field: how to work around this error. // It's used to teach the users how to solve the error if occurring in the real environment. workaround string - // Description is the expanded detail of why this error occurred. + // description is the expanded detail of why this error occurred. // This could be written by developer at a static env, // and the more detail this field explaining the better, // even some guess of the cause could be included. description string + // redactArgsPos defines the positions of arguments in message that need to be redacted. + // And it is controlled by the global var RedactLogEnabled. + // For example, an original error is `Duplicate entry 'PRIMARY' for key 'key'`, + // when RedactLogEnabled is ON and redactArgsPos is [0, 1], the error is `Duplicate entry '?' for key '?'`. + redactArgsPos []int // Cause is used to warp some third party error. cause error args []interface{} @@ -189,6 +197,7 @@ func (e *Error) fillLineAndFile(skip int) { // GenWithStack generates a new *Error with the same class and code, and a new formatted message. func (e *Error) GenWithStack(format string, args ...interface{}) error { + // TODO: RedactErrorArg err := *e err.message = format err.args = args @@ -198,6 +207,7 @@ func (e *Error) GenWithStack(format string, args ...interface{}) error { // GenWithStackByArgs generates a new *Error with the same class and code, and new arguments. func (e *Error) GenWithStackByArgs(args ...interface{}) error { + RedactErrorArg(args, e.redactArgsPos) err := *e err.args = args err.fillLineAndFile(1) @@ -207,6 +217,7 @@ func (e *Error) GenWithStackByArgs(args ...interface{}) error { // FastGen generates a new *Error with the same class and code, and a new formatted message. // This will not call runtime.Caller to get file and line. func (e *Error) FastGen(format string, args ...interface{}) error { + // TODO: RedactErrorArg err := *e err.message = format err.args = args @@ -216,6 +227,7 @@ func (e *Error) FastGen(format string, args ...interface{}) error { // FastGen generates a new *Error with the same class and code, and a new arguments. // This will not call runtime.Caller to get file and line. func (e *Error) FastGenByArgs(args ...interface{}) error { + RedactErrorArg(args, e.redactArgsPos) err := *e err.args = args return SuspendStack(&err) @@ -243,6 +255,17 @@ func (e *Error) NotEqual(err error) bool { return !e.Equal(err) } +// RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. +func RedactErrorArg(args []interface{}, position []int) { + if RedactLogEnabled { + for _, pos := range position { + if len(args) > pos { + args[pos] = "?" + } + } + } +} + // ErrorEqual returns a boolean indicating whether err1 is equal to err2. func ErrorEqual(err1, err2 error) bool { e1 := Cause(err1) @@ -364,6 +387,12 @@ func Workaround(wr string) NormalizeOption { } } +func RedactArgs(pos []int) NormalizeOption { + return func(e *Error) { + e.redactArgsPos = pos + } +} + // RFCCodeText returns a NormalizeOption to set RFC error code. func RFCCodeText(codeText string) NormalizeOption { return func(e *Error) { From 7ba5bb7d194df5e370ac127dc44552e601eaecdd Mon Sep 17 00:00:00 2001 From: Feng Liyuan Date: Wed, 21 Oct 2020 12:08:01 +0800 Subject: [PATCH 21/59] fix datarace of RedactLogEnabled --- go.mod | 1 + terror_error.go | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index e0fb516c..f5a4166f 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,6 @@ require ( github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 github.com/pingcap/log v0.0.0-20200511115504-543df19646ad github.com/pkg/errors v0.9.1 + go.uber.org/atomic v1.6.0 go.uber.org/zap v1.15.0 ) diff --git a/terror_error.go b/terror_error.go index df097fe1..a0b0ee74 100644 --- a/terror_error.go +++ b/terror_error.go @@ -19,10 +19,12 @@ import ( "runtime" "strconv" "strings" + + "go.uber.org/atomic" ) // RedactLogEnabled defines whether the arguments of Error need to be redacted. -var RedactLogEnabled bool = false +var RedactLogEnabled atomic.Bool // ErrCode represents a specific error type in a error class. // Same error code can be used in different error classes. @@ -257,7 +259,7 @@ func (e *Error) NotEqual(err error) bool { // RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. func RedactErrorArg(args []interface{}, position []int) { - if RedactLogEnabled { + if RedactLogEnabled.Load() { for _, pos := range position { if len(args) > pos { args[pos] = "?" From 3551daeb9d8e2b725f4140a2beef9e09aa669db8 Mon Sep 17 00:00:00 2001 From: Lonng Date: Thu, 29 Oct 2020 11:01:45 +0800 Subject: [PATCH 22/59] Remove the description/workaround fields from Error definition Signed-off-by: Lonng --- compatible_shim.go | 99 ++++++++++++++++++++++++++++ terror_error.go => normalize.go | 113 +------------------------------- terror_test/terror_test.go | 2 +- 3 files changed, 102 insertions(+), 112 deletions(-) create mode 100644 compatible_shim.go rename terror_error.go => normalize.go (69%) diff --git a/compatible_shim.go b/compatible_shim.go new file mode 100644 index 00000000..1e6bb911 --- /dev/null +++ b/compatible_shim.go @@ -0,0 +1,99 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "encoding/json" + "strconv" + "strings" +) + +// class2RFCCode is used for compatible with old version of TiDB. When +// marshal Error to json, old version of TiDB contain a 'class' field +// which is represented for error class. In order to parser and convert +// json to errors.Error, using this map to convert error class to RFC +// error code text. here is reference: +// https://github.com/pingcap/parser/blob/release-3.0/terror/terror.go#L58 +var class2RFCCode = map[int]string{ + 1: "autoid", + 2: "ddl", + 3: "domain", + 4: "evaluator", + 5: "executor", + 6: "expression", + 7: "admin", + 8: "kv", + 9: "meta", + 10: "planner", + 11: "parser", + 12: "perfschema", + 13: "privilege", + 14: "schema", + 15: "server", + 16: "struct", + 17: "variable", + 18: "xeval", + 19: "table", + 20: "types", + 21: "global", + 22: "mocktikv", + 23: "json", + 24: "tikv", + 25: "session", + 26: "plugin", + 27: "util", +} +var rfcCode2class map[string]int + +func init() { + rfcCode2class = make(map[string]int) + for k, v := range class2RFCCode { + rfcCode2class[v] = k + } +} + + +// MarshalJSON implements json.Marshaler interface. +// aware that this function cannot save a 'registered' status, +// since we cannot access the registry when unmarshaling, +// and the original global registry would be removed here. +// This function is reserved for compatibility. +func (e *Error) MarshalJSON() ([]byte, error) { + ec := strings.Split(string(e.codeText), ":")[0] + return json.Marshal(&jsonError{ + Class: rfcCode2class[ec], + Code: int(e.code), + Msg: e.GetMsg(), + RFCCode: string(e.codeText), + }) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +// aware that this function cannot create a 'registered' error, +// since we cannot access the registry in this context, +// and the original global registry is removed. +// This function is reserved for compatibility. +func (e *Error) UnmarshalJSON(data []byte) error { + tErr := &jsonError{} + if err := json.Unmarshal(data, &tErr); err != nil { + return Trace(err) + } + e.codeText = ErrCodeText(tErr.RFCCode) + if tErr.RFCCode == "" && tErr.Class > 0 { + e.codeText = ErrCodeText(class2RFCCode[tErr.Class] + ":" + strconv.Itoa(tErr.Code)) + } + e.code = ErrCode(tErr.Code) + e.message = tErr.Msg + return nil +} diff --git a/terror_error.go b/normalize.go similarity index 69% rename from terror_error.go rename to normalize.go index a0b0ee74..dbdc7b57 100644 --- a/terror_error.go +++ b/normalize.go @@ -14,13 +14,10 @@ package errors import ( - "encoding/json" "fmt" + "go.uber.org/atomic" "runtime" "strconv" - "strings" - - "go.uber.org/atomic" ) // RedactLogEnabled defines whether the arguments of Error need to be redacted. @@ -36,59 +33,9 @@ type ErrCodeText string type ErrorID string type RFCErrorCode string -// class2RFCCode is used for compatible with old version of TiDB. When -// marshal Error to json, old version of TiDB contain a 'class' field -// which is represented for error class. In order to parser and convert -// json to errors.Error, using this map to convert error class to RFC -// error code text. here is reference: -// https://github.com/pingcap/parser/blob/release-3.0/terror/terror.go#L58 -var class2RFCCode = map[int]string{ - 1: "autoid", - 2: "ddl", - 3: "domain", - 4: "evaluator", - 5: "executor", - 6: "expression", - 7: "admin", - 8: "kv", - 9: "meta", - 10: "planner", - 11: "parser", - 12: "perfschema", - 13: "privilege", - 14: "schema", - 15: "server", - 16: "struct", - 17: "variable", - 18: "xeval", - 19: "table", - 20: "types", - 21: "global", - 22: "mocktikv", - 23: "json", - 24: "tikv", - 25: "session", - 26: "plugin", - 27: "util", -} -var rfcCode2class map[string]int - -func init() { - rfcCode2class = make(map[string]int) - for k, v := range class2RFCCode { - rfcCode2class[v] = k - } -} - // Error is the 'prototype' of a type of errors. // Use DefineError to make a *Error: -// var ErrUnavailable = ClassRegion.DefineError(). -// TextualCode("Unavailable"). -// Description("A certain Raft Group is not available, such as the number of replicas is not enough.\n" + -// "This error usually occurs when the TiKV server is busy or the TiKV node is down."). -// Workaround("Check the status, monitoring data and log of the TiKV server."). -// MessageTemplate("Region %d is unavailable"). -// Build() +// var ErrUnavailable = errors.Normalize("Region %d is unavailable", errors.RFCCodeText("Unavailable")) // // "throw" it at runtime: // func Somewhat() error { @@ -111,14 +58,6 @@ type Error struct { // message is a template of the description of this error. // printf-style formatting is enabled. message string - // The workaround field: how to work around this error. - // It's used to teach the users how to solve the error if occurring in the real environment. - workaround string - // description is the expanded detail of why this error occurred. - // This could be written by developer at a static env, - // and the more detail this field explaining the better, - // even some guess of the cause could be included. - description string // redactArgsPos defines the positions of arguments in message that need to be redacted. // And it is controlled by the global var RedactLogEnabled. // For example, an original error is `Duplicate entry 'PRIMARY' for key 'key'`, @@ -303,40 +242,6 @@ type jsonError struct { RFCCode string `json:"rfccode"` } -// MarshalJSON implements json.Marshaler interface. -// aware that this function cannot save a 'registered' status, -// since we cannot access the registry when unmarshaling, -// and the original global registry would be removed here. -// This function is reserved for compatibility. -func (e *Error) MarshalJSON() ([]byte, error) { - ec := strings.Split(string(e.codeText), ":")[0] - return json.Marshal(&jsonError{ - Class: rfcCode2class[ec], - Code: int(e.code), - Msg: e.GetMsg(), - RFCCode: string(e.codeText), - }) -} - -// UnmarshalJSON implements json.Unmarshaler interface. -// aware that this function cannot create a 'registered' error, -// since we cannot access the registry in this context, -// and the original global registry is removed. -// This function is reserved for compatibility. -func (e *Error) UnmarshalJSON(data []byte) error { - tErr := &jsonError{} - if err := json.Unmarshal(data, &tErr); err != nil { - return Trace(err) - } - e.codeText = ErrCodeText(tErr.RFCCode) - if tErr.RFCCode == "" && tErr.Class > 0 { - e.codeText = ErrCodeText(class2RFCCode[tErr.Class] + ":" + strconv.Itoa(tErr.Code)) - } - e.code = ErrCode(tErr.Code) - e.message = tErr.Msg - return nil -} - func (e *Error) Wrap(err error) *Error { if err != nil { newErr := *e @@ -375,20 +280,6 @@ func (e *Error) GenWithStackByCause(args ...interface{}) error { type NormalizeOption func(*Error) -// Description returns a NormalizeOption to set description. -func Description(desc string) NormalizeOption { - return func(e *Error) { - e.description = desc - } -} - -// Workaround returns a NormalizeOption to set workaround. -func Workaround(wr string) NormalizeOption { - return func(e *Error) { - e.workaround = wr - } -} - func RedactArgs(pos []int) NormalizeOption { return func(e *Error) { e.redactArgsPos = pos diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 1b88b000..7967412f 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -135,7 +135,7 @@ func (s *testTErrorSuite) TestRFCCode(c *C) { c2err2 := errors.Normalize("nothing", errors.RFCCodeText("TestErr2:Err2")) c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TestErr1:Err1")) c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TestErr2:Err2")) - berr := errors.Normalize("nothing", errors.RFCCodeText("Blank:B1"), errors.Workaround(`Do nothing`)) + berr := errors.Normalize("nothing", errors.RFCCodeText("Blank:B1")) c.Assert(berr.RFCCode(), Equals, errors.RFCErrorCode("Blank:B1")) } From 179486bc8faccb79f0a520274855d113ed35934f Mon Sep 17 00:00:00 2001 From: Lonng Date: Thu, 29 Oct 2020 12:06:34 +0800 Subject: [PATCH 23/59] Update compatible_shim.go Co-authored-by: kennytm --- compatible_shim.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compatible_shim.go b/compatible_shim.go index 1e6bb911..ba446e08 100644 --- a/compatible_shim.go +++ b/compatible_shim.go @@ -21,7 +21,7 @@ import ( // class2RFCCode is used for compatible with old version of TiDB. When // marshal Error to json, old version of TiDB contain a 'class' field -// which is represented for error class. In order to parser and convert +// which is represented for error class. In order to parse and convert // json to errors.Error, using this map to convert error class to RFC // error code text. here is reference: // https://github.com/pingcap/parser/blob/release-3.0/terror/terror.go#L58 From 2fa89ccbde0e152545493f5a9f69ab49ef135458 Mon Sep 17 00:00:00 2001 From: Lonng Date: Thu, 29 Oct 2020 12:06:57 +0800 Subject: [PATCH 24/59] address comment Signed-off-by: Lonng --- normalize.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/normalize.go b/normalize.go index dbdc7b57..5712fe72 100644 --- a/normalize.go +++ b/normalize.go @@ -15,9 +15,10 @@ package errors import ( "fmt" - "go.uber.org/atomic" "runtime" "strconv" + + "go.uber.org/atomic" ) // RedactLogEnabled defines whether the arguments of Error need to be redacted. From 54f8f4e25676226367e273e4ff3a24a9981e2bc9 Mon Sep 17 00:00:00 2001 From: Allen Zhong Date: Thu, 26 Nov 2020 15:57:53 +0800 Subject: [PATCH 25/59] move tiup/components/errdoc/errdoc-gen to errors/errdoc-gen --- errdoc-gen/README.md | 6 + errdoc-gen/main.go | 339 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 errdoc-gen/README.md create mode 100644 errdoc-gen/main.go diff --git a/errdoc-gen/README.md b/errdoc-gen/README.md new file mode 100644 index 00000000..8f98afcc --- /dev/null +++ b/errdoc-gen/README.md @@ -0,0 +1,6 @@ +## Usage + +```shell script +# eg: ./errdoc-gen --source devel/pingap/tidb --module github.com/pingcap/tidb --output devel/pingap/tidb/errors.toml +./errdoc-gen --source /path/to/source/code --module ${module-name} --output /path/to/errors.toml +``` \ No newline at end of file diff --git a/errdoc-gen/main.go b/errdoc-gen/main.go new file mode 100644 index 00000000..21f17084 --- /dev/null +++ b/errdoc-gen/main.go @@ -0,0 +1,339 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" +) + +var opt struct { + source string + module string + output string + retainCode bool +} + +func init() { + flag.StringVar(&opt.source, "source", "", "The source directory of error documentation") + flag.StringVar(&opt.module, "module", "", "The module name of target repository") + flag.StringVar(&opt.output, "output", "", "The output path of error documentation file") + flag.BoolVar(&opt.retainCode, "retain-code", false, "Retain the generated code when generator exit") +} + +func log(format string, args ...interface{}) { + fmt.Println(fmt.Sprintf(format, args...)) +} + +func fatal(format string, args ...interface{}) { + log(format, args...) + os.Exit(1) +} + +const autoDirectoryName = "_errdoc-generator" +const entryFileName = "main.go" + +func main() { + flag.Parse() + if opt.source == "" { + fatal("The source directory cannot be empty") + } + + source, err := filepath.EvalSymlinks(opt.source) + if err != nil { + fatal("Evaluate symbol link path %s failed: %v", opt.source, err) + } + opt.source = source + + _, err = os.Stat(filepath.Join(opt.source, "go.mod")) + if os.IsNotExist(err) { + fatal("The source directory is not the root path of codebase(go.mod not found)") + } + + if opt.output == "" { + opt.output = filepath.Join(opt.source, "errors.toml") + log("The --output argument is missing, default to %s", opt.output) + } + + errNames, err := errdoc(opt.source, opt.module) + if err != nil { + log("Extract the error documentation failed: %+v", err) + } + + targetDir := filepath.Join(opt.source, autoDirectoryName) + if err := os.MkdirAll(targetDir, 0755); err != nil { + fatal("Cannot create the errdoc: %+v", err) + } + + if !opt.retainCode { + defer os.RemoveAll(targetDir) + } + + tmpl := ` +package main + +import ( + "bytes" + "flag" + "io/ioutil" + "os" + "reflect" + "fmt" + "sort" + "strings" + + "github.com/BurntSushi/toml" + "github.com/pingcap/errors" +{{- range $decl := .}} + {{$decl.PackageName}} "{{- $decl.ImportPath}}" +{{- end}} +) + +func main() { + var outpath string + flag.StringVar(&outpath, "output", "", "Specify the error documentation output file path") + flag.Parse() + if outpath == "" { + println("Usage: ./_errdoc-generator --output /path/to/errors.toml") + os.Exit(1) + } + + // Read-in the exists file and merge the description/workaround from exists file + existDefinition := map[string]spec{} + if file, err := ioutil.ReadFile(outpath); err == nil { + err = toml.Unmarshal(file, &existDefinition) + if err != nil { + println(fmt.Sprintf("Invalid toml file %s when merging exists description/workaround: %v", outpath, err)) + os.Exit(1) + } + } + + var allErrors []error + {{- range $decl := .}} + {{- range $err := $decl.ErrNames}} + allErrors = append(allErrors, {{$decl.PackageName}}.{{- $err}}) + {{- end}} + {{- end}} + + var dedup = map[string]spec{} + for _, e := range allErrors { + terr, ok := e.(*errors.Error) + if !ok { + println("Non-normalized error:", e.Error()) + } else { + val := reflect.ValueOf(terr).Elem() + codeText := val.FieldByName("codeText") + message := val.FieldByName("message") + if previous, found := dedup[codeText.String()]; found { + println("Duplicated error code:", codeText.String()) + if message.String() < previous.Error { + continue + } + } + s := spec{ + Code: codeText.String(), + Error: message.String(), + } + if exist, found := existDefinition[s.Code]; found { + s.Description = strings.TrimSpace(exist.Description) + s.Workaround = strings.TrimSpace(exist.Workaround) + } + dedup[codeText.String()] = s + } + } + + var sorted []spec + for _, item := range dedup { + sorted = append(sorted, item) + } + sort.Slice(sorted, func(i, j int) bool { + // TiDB exits duplicated code + if sorted[i].Code == sorted[j].Code { + return sorted[i].Error < sorted[j].Error + } + return sorted[i].Code < sorted[j].Code + }) + + // We don't use toml library to serialize it due to cannot reserve the order for map[string]spec + buffer := bytes.NewBufferString("# AUTOGENERATED BY github.com/pingcap/errors/errdoc-gen\n" + + "# YOU CAN CHANGE THE 'description'/'workaround' FIELDS IF THEM ARE IMPROPER.\n\n") + for _, item := range sorted { + buffer.WriteString(fmt.Sprintf("[\"%s\"]\nerror = '''\n%s\n'''\n", item.Code, item.Error)) + if item.Description != "" { + buffer.WriteString(fmt.Sprintf("description = '''\n%s\n'''\n", item.Description)) + } + if item.Workaround != "" { + buffer.WriteString(fmt.Sprintf("workaround = '''\n%s\n'''\n", item.Workaround)) + } + buffer.WriteString("\n") + } + if err := ioutil.WriteFile(outpath, buffer.Bytes(), os.ModePerm); err != nil { + panic(err) + } +} +` + "type spec struct {\n" + + "Code string\n" + + "Error string `toml:\"error\"`\n" + + "Description string `toml:\"description\"`\n" + + "Workaround string `toml:\"workaround\"`\n" + + "}" + + t, err := template.New("_errdoc-template").Parse(tmpl) + if err != nil { + fatal("Parse template failed: %+v", err) + } + + outFile := filepath.Join(targetDir, entryFileName) + out, err := os.OpenFile(outFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, os.ModePerm) + if err != nil { + fatal("Open %s failed: %+v", outFile, err) + } + defer out.Close() + + if err := t.Execute(out, errNames); err != nil { + fatal("Render template failed: %+v", err) + } + + output, err := filepath.Abs(opt.output) + if err != nil { + fatal("Evaluate the absolute path of output failed: %+v", err) + } + + cmd := exec.Command("go", "run", filepath.Join(autoDirectoryName, entryFileName), "--output", output) + cmd.Dir = opt.source + data, err := cmd.CombinedOutput() + if err != nil { + fatal("Generate %s failed: %v, output:\n%s", opt.output, err, string(data)) + } + + log("Generate successfully, output:\n%s", string(data)) +} + +type errDecl struct { + ImportPath string + PackageName string + ErrNames []string +} + +func errdoc(source, module string) ([]*errDecl, error) { + source, err := filepath.Abs(source) + if err != nil { + return nil, err + } + + dedup := map[string]*errDecl{} + + err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if !strings.HasSuffix(path, ".go") { + return nil + } + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + // Ignore invalid source file + return nil + } + errNames := export(file) + if len(errNames) < 1 { + return nil + } + dirPath := filepath.Dir(path) + subPath, err := filepath.Rel(source, dirPath) + if err != nil { + return err + } + packageName := strings.ReplaceAll(subPath, "/", "_") + if decl, found := dedup[packageName]; found { + decl.ErrNames = append(decl.ErrNames, errNames...) + } else { + decl := &errDecl{ + ImportPath: filepath.Join(module, subPath), + PackageName: packageName, + ErrNames: errNames, + } + dedup[packageName] = decl + } + return nil + }) + + var errDecls []*errDecl + for _, decl := range dedup { + errDecls = append(errDecls, decl) + } + + return errDecls, err +} + +func export(f *ast.File) []string { + if len(f.Decls) == 0 { + return nil + } + + var errNames []string + for _, decl := range f.Decls { + gen, ok := decl.(*ast.GenDecl) + if !ok || len(gen.Specs) == 0 { + continue + } + for _, spec := range gen.Specs { + switch errSpec := spec.(type) { + case *ast.ValueSpec: + // CASES: + // var ErrXXX = errors.Normalize(string, opts...) + // var ( + // ErrYYY = errors.Normalize(string, opts...) + // ErrZZZ = errors.Normalize(string, opts...) + // A = errors.Normalize(string, opts...) + // ) + // var ErrXXX, ErrYYY = errors.Normalize(string, opts...), errors.Normalize(string, opts...) + // var ( + // ErrYYY = errors.Normalize(string, opts...) + // ErrZZZ, ErrWWW = errors.Normalize(string, opts...), errors.Normalize(string, opts...) + // A = errors.Normalize(string, opts...) + // ) + // + if len(errSpec.Names) != len(errSpec.Values) { + continue + } + for i, name := range errSpec.Names { + if !strings.HasPrefix(name.Name, "Err") { + continue + } + _, ok := errSpec.Values[i].(*ast.CallExpr) + if !ok { + continue + } + errNames = append(errNames, name.Name) + } + default: + continue + } + } + } + return errNames +} From 91bf0465e70650ece82d0d85121a7df698774161 Mon Sep 17 00:00:00 2001 From: hillium Date: Thu, 15 Apr 2021 10:29:40 +0800 Subject: [PATCH 26/59] print cause when there is --- normalize.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/normalize.go b/normalize.go index 5712fe72..40a6ec12 100644 --- a/normalize.go +++ b/normalize.go @@ -115,6 +115,9 @@ func (e *Error) Error() string { if len(describe) == 0 { describe = ErrCodeText(strconv.Itoa(int(e.code))) } + if e.cause != nil { + return fmt.Sprintf("[%s]%s: %s", e.RFCCode(), e.GetMsg(), e.cause.Error()) + } return fmt.Sprintf("[%s]%s", e.RFCCode(), e.GetMsg()) } From b0951b28c6a1d543b79ecfa188a05a695d1bd0b1 Mon Sep 17 00:00:00 2001 From: hillium Date: Thu, 15 Apr 2021 14:42:42 +0800 Subject: [PATCH 27/59] added a test case --- normalize_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 normalize_test.go diff --git a/normalize_test.go b/normalize_test.go new file mode 100644 index 00000000..d1dd70b2 --- /dev/null +++ b/normalize_test.go @@ -0,0 +1,33 @@ +package errors + +import ( + "regexp" + "testing" +) + +func errorMatches(t *testing.T, err error, re string) { + if err == nil && re != "" { + t.Errorf("nil error doesn't match %s", re) + return + } + match, reErr := regexp.MatchString(re, err.Error()) + if reErr != nil { + t.Errorf("invalid regexp %s (%s)", re, reErr.Error()) + return + } + if !match { + t.Errorf("error %s doesn't match %s", err.Error(), re) + return + } + t.Logf("passed: %s ~= %s", err.Error(), re) +} + +func TestCauseInErrorMessage(t *testing.T) { + errTest := Normalize("this error just for testing", RFCCodeText("Internal:Test")) + + wrapped := errTest.Wrap(New("everything is alright :)")) + errorMatches(t, wrapped, `\[Internal:Test\]this error just for testing: everything is alright :\)`) + + notWrapped := errTest.GenWithStack("everything is alright") + errorMatches(t, notWrapped, `^\[Internal:Test\]everything is alright$`) +} From 1b4979d79b4be74d0ef4c1089ca24f924c733f1d Mon Sep 17 00:00:00 2001 From: tison Date: Sun, 25 Apr 2021 10:31:14 +0800 Subject: [PATCH 28/59] *: remove deps --- appveyor.yml | 32 ---------- bench_test.go | 2 - format_test.go | 108 +++++++++++++++++----------------- go.mod | 6 +- go.sum | 28 --------- stack_test.go | 2 +- terror_test/terror_test.go | 116 +++++++++++++++++++------------------ 7 files changed, 120 insertions(+), 174 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index a932eade..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: build-{build}.{branch} - -clone_folder: C:\gopath\src\github.com\pkg\errors -shallow_clone: true # for startup speed - -environment: - GOPATH: C:\gopath - -platform: - - x64 - -# http://www.appveyor.com/docs/installed-software -install: - # some helpful output for debugging builds - - go version - - go env - # pre-installed MinGW at C:\MinGW is 32bit only - # but MSYS2 at C:\msys64 has mingw64 - - set PATH=C:\msys64\mingw64\bin;%PATH% - - gcc --version - - g++ --version - -build_script: - - go install -v ./... - -test_script: - - set PATH=C:\gopath\bin;%PATH% - - go test -v ./... - -#artifacts: -# - path: '%GOPATH%\bin\*.exe' -deploy: off diff --git a/bench_test.go b/bench_test.go index c906870e..8d180397 100644 --- a/bench_test.go +++ b/bench_test.go @@ -1,5 +1,3 @@ -// +build go1.7 - package errors import ( diff --git a/format_test.go b/format_test.go index 30275a3f..e8f6f4ff 100644 --- a/format_test.go +++ b/format_test.go @@ -26,8 +26,8 @@ func TestFormatNew(t *testing.T) { New("error"), "%+v", "error\n" + - "github.com/pkg/errors.TestFormatNew\n" + - "\t.+/github.com/pkg/errors/format_test.go:26", + "github.com/pingcap/errors.TestFormatNew\n" + + "\t.+/github.com/pingcap/errors/format_test.go:26", }, { New("error"), "%q", @@ -56,8 +56,8 @@ func TestFormatErrorf(t *testing.T) { Errorf("%s", "error"), "%+v", "error\n" + - "github.com/pkg/errors.TestFormatErrorf\n" + - "\t.+/github.com/pkg/errors/format_test.go:56", + "github.com/pingcap/errors.TestFormatErrorf\n" + + "\t.+/github.com/pingcap/errors/format_test.go:56", }} for i, tt := range tests { @@ -82,8 +82,8 @@ func TestFormatWrap(t *testing.T) { Annotate(New("error"), "error2"), "%+v", "error\n" + - "github.com/pkg/errors.TestFormatWrap\n" + - "\t.+/github.com/pkg/errors/format_test.go:82", + "github.com/pingcap/errors.TestFormatWrap\n" + + "\t.+/github.com/pingcap/errors/format_test.go:82", }, { Annotate(io.EOF, "error"), "%s", @@ -97,15 +97,15 @@ func TestFormatWrap(t *testing.T) { "%+v", "EOF\n" + "error\n" + - "github.com/pkg/errors.TestFormatWrap\n" + - "\t.+/github.com/pkg/errors/format_test.go:96", + "github.com/pingcap/errors.TestFormatWrap\n" + + "\t.+/github.com/pingcap/errors/format_test.go:96", }, { Annotate(Annotate(io.EOF, "error1"), "error2"), "%+v", "EOF\n" + "error1\n" + - "github.com/pkg/errors.TestFormatWrap\n" + - "\t.+/github.com/pkg/errors/format_test.go:103\n", + "github.com/pingcap/errors.TestFormatWrap\n" + + "\t.+/github.com/pingcap/errors/format_test.go:103\n", }, { Annotate(New("error with space"), "context"), "%q", @@ -135,8 +135,8 @@ func TestFormatWrapf(t *testing.T) { "%+v", "EOF\n" + "error2\n" + - "github.com/pkg/errors.TestFormatWrapf\n" + - "\t.+/github.com/pkg/errors/format_test.go:134", + "github.com/pingcap/errors.TestFormatWrapf\n" + + "\t.+/github.com/pingcap/errors/format_test.go:134", }, { Annotatef(New("error"), "error%d", 2), "%s", @@ -149,8 +149,8 @@ func TestFormatWrapf(t *testing.T) { Annotatef(New("error"), "error%d", 2), "%+v", "error\n" + - "github.com/pkg/errors.TestFormatWrapf\n" + - "\t.+/github.com/pkg/errors/format_test.go:149", + "github.com/pingcap/errors.TestFormatWrapf\n" + + "\t.+/github.com/pingcap/errors/format_test.go:149", }} for i, tt := range tests { @@ -175,8 +175,8 @@ func TestFormatWithStack(t *testing.T) { WithStack(io.EOF), "%+v", []string{"EOF", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:175"}, + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:175"}, }, { WithStack(New("error")), "%s", @@ -189,37 +189,37 @@ func TestFormatWithStack(t *testing.T) { WithStack(New("error")), "%+v", []string{"error", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:189", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:189"}, + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:189", + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:189"}, }, { WithStack(WithStack(io.EOF)), "%+v", []string{"EOF", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:197", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:197"}, + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:197", + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:197"}, }, { WithStack(WithStack(Annotatef(io.EOF, "message"))), "%+v", []string{"EOF", "message", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:205", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:205", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:205"}, + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:205", + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:205", + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:205"}, }, { WithStack(Errorf("error%d", 1)), "%+v", []string{"error1", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:216", - "github.com/pkg/errors.TestFormatWithStack\n" + - "\t.+/github.com/pkg/errors/format_test.go:216"}, + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:216", + "github.com/pingcap/errors.TestFormatWithStack\n" + + "\t.+/github.com/pingcap/errors/format_test.go:216"}, }} for i, tt := range tests { @@ -245,8 +245,8 @@ func TestFormatWithMessage(t *testing.T) { "%+v", []string{ "error", - "github.com/pkg/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pkg/errors/format_test.go:244", + "github.com/pingcap/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pingcap/errors/format_test.go:244", "error2"}, }, { WithMessage(io.EOF, "addition1"), @@ -272,30 +272,30 @@ func TestFormatWithMessage(t *testing.T) { Annotate(WithMessage(io.EOF, "error1"), "error2"), "%+v", []string{"EOF", "error1", "error2", - "github.com/pkg/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pkg/errors/format_test.go:272"}, + "github.com/pingcap/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pingcap/errors/format_test.go:272"}, }, { WithMessage(Errorf("error%d", 1), "error2"), "%+v", []string{"error1", - "github.com/pkg/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pkg/errors/format_test.go:278", + "github.com/pingcap/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pingcap/errors/format_test.go:278", "error2"}, }, { WithMessage(WithStack(io.EOF), "error"), "%+v", []string{ "EOF", - "github.com/pkg/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pkg/errors/format_test.go:285", + "github.com/pingcap/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pingcap/errors/format_test.go:285", "error"}, }, { WithMessage(Annotate(WithStack(io.EOF), "inside-error"), "outside-error"), "%+v", []string{ "EOF", - "github.com/pkg/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pkg/errors/format_test.go:293", + "github.com/pingcap/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pingcap/errors/format_test.go:293", "inside-error", "outside-error"}, }} @@ -312,12 +312,12 @@ func TestFormatWithMessage(t *testing.T) { }{ {New("new-error"), []string{ "new-error", - "github.com/pkg/errors.TestFormatGeneric\n" + - "\t.+/github.com/pkg/errors/format_test.go:313"}, + "github.com/pingcap/errors.TestFormatGeneric\n" + + "\t.+/github.com/pingcap/errors/format_test.go:313"}, }, {Errorf("errorf-error"), []string{ "errorf-error", - "github.com/pkg/errors.TestFormatGeneric\n" + - "\t.+/github.com/pkg/errors/format_test.go:317"}, + "github.com/pingcap/errors.TestFormatGeneric\n" + + "\t.+/github.com/pingcap/errors/format_test.go:317"}, }, {errors.New("errors-new-error"), []string{ "errors-new-error"}, }, @@ -330,22 +330,22 @@ func TestFormatWithMessage(t *testing.T) { }, { func(err error) error { return WithStack(err) }, []string{ - "github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" + - ".+/github.com/pkg/errors/format_test.go:331", + "github.com/pingcap/errors.(func·002|TestFormatGeneric.func2)\n\t" + + ".+/github.com/pingcap/errors/format_test.go:331", }, }, { func(err error) error { return Annotate(err, "wrap-error") }, []string{ "wrap-error", - "github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" + - ".+/github.com/pkg/errors/format_test.go:337", + "github.com/pingcap/errors.(func·003|TestFormatGeneric.func3)\n\t" + + ".+/github.com/pingcap/errors/format_test.go:337", }, }, { func(err error) error { return Annotatef(err, "wrapf-error%d", 1) }, []string{ "wrapf-error1", - "github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" + - ".+/github.com/pkg/errors/format_test.go:346", + "github.com/pingcap/errors.(func·004|TestFormatGeneric.func4)\n\t" + + ".+/github.com/pingcap/errors/format_test.go:346", }, }, } diff --git a/go.mod b/go.mod index f5a4166f..d65ad1c4 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/pingcap/errors go 1.14 require ( + github.com/kr/pretty v0.1.0 // indirect github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 - github.com/pingcap/log v0.0.0-20200511115504-543df19646ad github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.4.0 // indirect go.uber.org/atomic v1.6.0 - go.uber.org/zap v1.15.0 + golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index f2f8f423..a559f53b 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,6 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -12,44 +8,25 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 h1:USx2/E1bX46VG32FIw034Au6seQ2fY9NEILmNh/UlQg= github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ= -github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pingcap/log v0.0.0-20200511115504-543df19646ad h1:SveG82rmu/GFxYanffxsSF503SiQV+2JLnWEiGiF+Tc= -github.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -57,10 +34,5 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/stack_test.go b/stack_test.go index d581ad6e..595c7c6c 100644 --- a/stack_test.go +++ b/stack_test.go @@ -27,7 +27,7 @@ func TestFrameLine(t *testing.T) { var pc, _, _, _ = runtime.Caller(1) return Frame(pc) }(), - 29, + 24, }, { Frame(0), // invalid PC 0, diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 7967412f..2d02c70f 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -15,35 +15,30 @@ package terror_test import ( "encoding/json" + "github.com/stretchr/testify/suite" "os" "runtime" "strings" "testing" "time" - . "github.com/pingcap/check" "github.com/pingcap/errors" ) const ( - CodeExecResultIsEmpty errors.ErrCode = 3 CodeMissConnectionID errors.ErrCode = 1 CodeResultUndetermined errors.ErrCode = 2 + CodeExecResultIsEmpty errors.ErrCode = 3 ) -func TestT(t *testing.T) { - CustomVerboseFlag = true - TestingT(t) -} - -var _ = Suite(&testTErrorSuite{}) - -type testTErrorSuite struct { +type TErrorTestSuite struct { + suite.Suite } -func (s *testTErrorSuite) TestErrCode(c *C) { - c.Assert(CodeMissConnectionID, Equals, errors.ErrCode(1)) - c.Assert(CodeResultUndetermined, Equals, errors.ErrCode(2)) +func (s *TErrorTestSuite) TestErrCode() { + s.Equal(CodeMissConnectionID, errors.ErrCode(1)) + s.Equal(CodeResultUndetermined, errors.ErrCode(2)) + s.Equal(CodeExecResultIsEmpty, errors.ErrCode(3)) } var predefinedErr = errors.Normalize("predefiend error", errors.MySQLErrorCode(123)) @@ -58,19 +53,19 @@ func call() error { return predefinedErr.GenWithStack("error message:%s", "abc") } -func (s *testTErrorSuite) TestJson(c *C) { +func (s *TErrorTestSuite) TestJson() { tmpErr := errors.Normalize("this is a test error", errors.RFCCodeText("ddl:-1"), errors.MySQLErrorCode(-1)) buf, err := json.Marshal(tmpErr) - c.Assert(err, IsNil) + s.Nil(err) var curTErr errors.Error err = json.Unmarshal(buf, &curTErr) - c.Assert(err, IsNil) + s.Nil(err) isEqual := tmpErr.Equal(&curTErr) - c.Assert(curTErr.Error(), Equals, tmpErr.Error()) - c.Assert(isEqual, IsTrue) + s.Equal(curTErr.Error(), tmpErr.Error()) + s.True(isEqual) } -func (s *testTErrorSuite) TestTraceAndLocation(c *C) { +func (s *TErrorTestSuite) TestTraceAndLocation() { err := example() stack := errors.ErrorStack(err) lines := strings.Split(stack, "\n") @@ -81,7 +76,7 @@ func (s *testTErrorSuite) TestTraceAndLocation(c *C) { sysStack++ } } - c.Assert(len(lines)-(2*sysStack), Equals, 15, Commentf("stack =\n%s", stack)) + s.Equalf(13, len(lines)-(2*sysStack), "stack = \n%s", stack) var containTerr bool for _, v := range lines { if strings.Contains(v, "terror_test.go") { @@ -89,77 +84,88 @@ func (s *testTErrorSuite) TestTraceAndLocation(c *C) { break } } - c.Assert(containTerr, IsTrue) + s.True(containTerr) } -func (s *testTErrorSuite) TestErrorEqual(c *C) { +func (s *TErrorTestSuite) TestErrorEqual() { e1 := errors.New("test error") - c.Assert(e1, NotNil) + s.NotNil(e1) e2 := errors.Trace(e1) - c.Assert(e2, NotNil) + s.NotNil(e2) e3 := errors.Trace(e2) - c.Assert(e3, NotNil) + s.NotNil(e3) - c.Assert(errors.Cause(e2), Equals, e1) - c.Assert(errors.Cause(e3), Equals, e1) - c.Assert(errors.Cause(e2), Equals, errors.Cause(e3)) + s.Equal(e1, errors.Cause(e2)) + s.Equal(e1, errors.Cause(e3)) + s.Equal(errors.Cause(e3), errors.Cause(e2)) e4 := errors.New("test error") - c.Assert(errors.Cause(e4), Not(Equals), e1) + s.Equal(e1, errors.Cause(e4)) e5 := errors.Errorf("test error") - c.Assert(errors.Cause(e5), Not(Equals), e1) + s.NotEqual(e1, errors.Cause(e5)) - c.Assert(errors.ErrorEqual(e1, e2), IsTrue) - c.Assert(errors.ErrorEqual(e1, e3), IsTrue) - c.Assert(errors.ErrorEqual(e1, e4), IsTrue) - c.Assert(errors.ErrorEqual(e1, e5), IsTrue) + s.True(errors.ErrorEqual(e1, e2)) + s.True(errors.ErrorEqual(e1, e3)) + s.True(errors.ErrorEqual(e1, e4)) + s.True(errors.ErrorEqual(e1, e5)) var e6 error - c.Assert(errors.ErrorEqual(nil, nil), IsTrue) - c.Assert(errors.ErrorNotEqual(e1, e6), IsTrue) + s.True(errors.ErrorEqual(nil, nil)) + s.True(errors.ErrorNotEqual(e1, e6)) } -func (s *testTErrorSuite) TestNewError(c *C) { +func (s *TErrorTestSuite) TestNewError() { today := time.Now().Weekday().String() err := predefinedTextualErr.GenWithStackByArgs(today) - c.Assert(err, NotNil) - c.Assert(err.Error(), Equals, "[executor:ExecutorAbsent]executor is taking vacation at "+today) + s.NotNil(err) + s.Equal("[executor:ExecutorAbsent]executor is taking vacation at " + today, err.Error()) } -func (s *testTErrorSuite) TestRFCCode(c *C) { +func (s *TErrorTestSuite) TestRFCCode() { c1err1 := errors.Normalize("nothing", errors.RFCCodeText("TestErr1:Err1")) c2err2 := errors.Normalize("nothing", errors.RFCCodeText("TestErr2:Err2")) - c.Assert(c1err1.RFCCode(), Equals, errors.RFCErrorCode("TestErr1:Err1")) - c.Assert(c2err2.RFCCode(), Equals, errors.RFCErrorCode("TestErr2:Err2")) + s.Equal(errors.RFCErrorCode("TestErr1:Err1"), c1err1.RFCCode()) + s.Equal(errors.RFCErrorCode("TestErr2:Err2"), c2err2.RFCCode()) + berr := errors.Normalize("nothing", errors.RFCCodeText("Blank:B1")) - c.Assert(berr.RFCCode(), Equals, errors.RFCErrorCode("Blank:B1")) + s.Equal(errors.RFCErrorCode("Blank:B1"), berr.RFCCode()) } -func (*testTErrorSuite) TestLineAndFile(c *C) { +func (s *TErrorTestSuite) TestLineAndFile() { err := predefinedTextualErr.GenWithStackByArgs("everyday") _, f, l, _ := runtime.Caller(0) terr, ok := errors.Cause(err).(*errors.Error) - c.Assert(ok, IsTrue) + s.True(ok) + file, line := terr.Location() - c.Assert(file, Equals, f) - c.Assert(line, Equals, l-1) + s.Equal(f, file) + s.Equal(l -1 , line) err2 := predefinedTextualErr.GenWithStackByArgs("everyday and everywhere") _, f2, l2, _ := runtime.Caller(0) terr2, ok2 := errors.Cause(err2).(*errors.Error) - c.Assert(ok2, IsTrue) + s.True(ok2) file2, line2 := terr2.Location() - c.Assert(file2, Equals, f2) - c.Assert(line2, Equals, l2-1) + s.Equal(f2, file2) + s.Equal(l2 - 1, line2) } -func (*testTErrorSuite) TestWarpAndField(c *C) { - causeErr := errors.New("load from etcd meet error") - ErrGetLeader := errors.Normalize("fail to get leader", errors.RFCCodeText("member:ErrGetLeader")) - errWithWarpedCause := errors.Annotate(ErrGetLeader, causeErr.Error()) - c.Assert(errWithWarpedCause.Error(), Equals, "load from etcd meet error: [member:ErrGetLeader]fail to get leader") +func (s *TErrorTestSuite) TestWarpAndField() { + cause := errors.New("load from etcd meet error") + s.NotNil(cause) + + err := errors.Normalize("fail to get leader", errors.RFCCodeText("member:ErrGetLeader")) + errWithCause := errors.Annotate(err, cause.Error()) + s.NotNil(errWithCause) + + s.Equal("load from etcd meet error: [member:ErrGetLeader]fail to get leader", errWithCause.Error()) } + + +func TestExampleTestSuite(t *testing.T) { + suite.Run(t, new(TErrorTestSuite)) +} \ No newline at end of file From 3fb2c9973103d0f5e3f4edac10f19f7460f2dc37 Mon Sep 17 00:00:00 2001 From: tison Date: Sun, 25 Apr 2021 10:34:14 +0800 Subject: [PATCH 29/59] *: fix tests --- example_test.go | 3 +-- go.mod | 7 +------ go.sum | 15 ++------------- terror_test/terror_test.go | 2 +- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/example_test.go b/example_test.go index c1fc13e3..cc4edaa2 100644 --- a/example_test.go +++ b/example_test.go @@ -2,8 +2,7 @@ package errors_test import ( "fmt" - - "github.com/pkg/errors" + "github.com/pingcap/errors" ) func ExampleNew() { diff --git a/go.mod b/go.mod index d65ad1c4..25808af0 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,6 @@ module github.com/pingcap/errors go 1.14 require ( - github.com/kr/pretty v0.1.0 // indirect - github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 - github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.4.0 // indirect + github.com/stretchr/testify v1.4.0 go.uber.org/atomic v1.6.0 - golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index a559f53b..4eb62d56 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 h1:USx2/E1bX46VG32FIw034Au6seQ2fY9NEILmNh/UlQg= -github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ= -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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -27,12 +18,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 2d02c70f..7b5db2db 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -102,7 +102,7 @@ func (s *TErrorTestSuite) TestErrorEqual() { s.Equal(errors.Cause(e3), errors.Cause(e2)) e4 := errors.New("test error") - s.Equal(e1, errors.Cause(e4)) + s.NotEqual(e1, errors.Cause(e4)) e5 := errors.Errorf("test error") s.NotEqual(e1, errors.Cause(e5)) From 1a71e64624fea757f2e5f568503a01496993a786 Mon Sep 17 00:00:00 2001 From: tison Date: Sun, 25 Apr 2021 10:42:01 +0800 Subject: [PATCH 30/59] *: fix tarvis --- .travis.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 15e5a192..10b74d55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,12 @@ +arch: + - amd64 + language: go -go_import_path: github.com/pkg/errors +go_import_path: github.com/pingcap/errors go: - - 1.4.x - - 1.5.x - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - tip + - 1.14.x + - 1.15.x + - 1.16.x script: - go test -v ./... From 6b215cc7cf32685aad14949ddfc64214d345b133 Mon Sep 17 00:00:00 2001 From: tison Date: Sun, 25 Apr 2021 14:21:42 +0800 Subject: [PATCH 31/59] add new line --- terror_test/terror_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 7b5db2db..7620c88b 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -168,4 +168,4 @@ func (s *TErrorTestSuite) TestWarpAndField() { func TestExampleTestSuite(t *testing.T) { suite.Run(t, new(TErrorTestSuite)) -} \ No newline at end of file +} From a9b8bed0640fd4d573a3a1fe15bc98b7f56ea427 Mon Sep 17 00:00:00 2001 From: tison Date: Sun, 25 Apr 2021 21:38:41 +0800 Subject: [PATCH 32/59] ignore tests with break changes of stdlib --- compatible_shim.go | 1 - stack_test.go | 20 ++++++++++---------- terror_test/terror_test.go | 7 +++---- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/compatible_shim.go b/compatible_shim.go index ba446e08..0835a0cd 100644 --- a/compatible_shim.go +++ b/compatible_shim.go @@ -63,7 +63,6 @@ func init() { } } - // MarshalJSON implements json.Marshaler interface. // aware that this function cannot save a 'registered' status, // since we cannot access the registry when unmarshaling, diff --git a/stack_test.go b/stack_test.go index 595c7c6c..c4b3d2da 100644 --- a/stack_test.go +++ b/stack_test.go @@ -22,16 +22,16 @@ func TestFrameLine(t *testing.T) { return Frame(pc) }(), 21, - }, { - func() Frame { - var pc, _, _, _ = runtime.Caller(1) - return Frame(pc) - }(), - 24, - }, { - Frame(0), // invalid PC - 0, - }} + }, /* { // TODO stdlib `runtime` Behavior changed between 1.13 and 1.14 + func() Frame { + var pc, _, _, _ = runtime.Caller(1) + return Frame(pc) + }(), + 24, + }, */{ + Frame(0), // invalid PC + 0, + }} for _, tt := range tests { got := tt.Frame.line() diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 7620c88b..02a6036c 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -122,7 +122,7 @@ func (s *TErrorTestSuite) TestNewError() { today := time.Now().Weekday().String() err := predefinedTextualErr.GenWithStackByArgs(today) s.NotNil(err) - s.Equal("[executor:ExecutorAbsent]executor is taking vacation at " + today, err.Error()) + s.Equal("[executor:ExecutorAbsent]executor is taking vacation at "+today, err.Error()) } func (s *TErrorTestSuite) TestRFCCode() { @@ -143,7 +143,7 @@ func (s *TErrorTestSuite) TestLineAndFile() { file, line := terr.Location() s.Equal(f, file) - s.Equal(l -1 , line) + s.Equal(l-1, line) err2 := predefinedTextualErr.GenWithStackByArgs("everyday and everywhere") _, f2, l2, _ := runtime.Caller(0) @@ -151,7 +151,7 @@ func (s *TErrorTestSuite) TestLineAndFile() { s.True(ok2) file2, line2 := terr2.Location() s.Equal(f2, file2) - s.Equal(l2 - 1, line2) + s.Equal(l2-1, line2) } func (s *TErrorTestSuite) TestWarpAndField() { @@ -165,7 +165,6 @@ func (s *TErrorTestSuite) TestWarpAndField() { s.Equal("load from etcd meet error: [member:ErrGetLeader]fail to get leader", errWithCause.Error()) } - func TestExampleTestSuite(t *testing.T) { suite.Run(t, new(TErrorTestSuite)) } From 1d56e0b46311bcc0915d30ca9b48ed0236a3bc7c Mon Sep 17 00:00:00 2001 From: tison Date: Sun, 25 Apr 2021 21:39:58 +0800 Subject: [PATCH 33/59] Update .travis.yml --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10b74d55..eaaabc55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,8 @@ arch: language: go go_import_path: github.com/pingcap/errors go: - - 1.14.x - - 1.15.x - - 1.16.x + - 1.13.x + - stable script: - go test -v ./... From 40f9a1999b3bce21f1603b268f262a14bca7f74b Mon Sep 17 00:00:00 2001 From: disksing Date: Thu, 13 May 2021 09:46:40 +0800 Subject: [PATCH 34/59] make Error compatible with functions in standard package (#35) --- errors.go | 10 ++++++-- errors_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ normalize.go | 20 +++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/errors.go b/errors.go index 2e1d3f62..612f77d2 100644 --- a/errors.go +++ b/errors.go @@ -181,6 +181,9 @@ type withStack struct { func (w *withStack) Cause() error { return w.error } +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': @@ -260,8 +263,11 @@ type withMessage struct { causeHasStack bool } -func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } -func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withMessage) Unwrap() error { return w.cause } func (w *withMessage) HasStack() bool { return w.causeHasStack } func (w *withMessage) Format(s fmt.State, verb rune) { diff --git a/errors_test.go b/errors_test.go index 7f5e225f..3d69324c 100644 --- a/errors_test.go +++ b/errors_test.go @@ -369,3 +369,71 @@ func TestWalkDeep(t *testing.T) { t.Errorf("found not exists") } } + +type fooError int + +func (fooError) Error() string { + return "foo" +} + +func TestWorkWithStdErrors(t *testing.T) { + e1 := fooError(100) + e2 := Normalize("e2", RFCCodeText("e2")) + e3 := Normalize("e3", RFCCodeText("e3")) + e21 := e2.Wrap(e1) + e31 := e3.Wrap(e1) + e32 := e3.Wrap(e2) + e321 := e3.Wrap(e21) + + unwrapTbl := []struct { + x *Error // x.Unwrap() == y + y error + }{{e2, nil}, {e3, nil}, {e21, e1}, {e31, e1}, {e32, e2}, {e321, e21}} + for _, c := range unwrapTbl { + if c.x.Unwrap() != c.y { + t.Errorf("`%s`.Unwrap() != `%s`", c.x, c.y) + } + } + + isTbl := []struct { + x, y error // errors.Is(x, y) == b + b bool + }{ + {e1, e1, true}, {e2, e1, false}, {e3, e1, false}, {e21, e1, true}, {e321, e1, true}, + {e1, e2, false}, {e2, e2, true}, {e3, e2, false}, {e21, e2, true}, {e31, e2, false}, {e321, e2, true}, + {e2, e21, true}, {e21, e21, true}, {e31, e21, false}, {e321, e21, true}, + {e321, e321, true}, {e3, e321, true}, {e21, e321, false}, + } + for _, c := range isTbl { + if c.b && !errors.Is(c.x, c.y) { + t.Errorf("`%s` is not `%s`", c.x, c.y) + } + if !c.b && errors.Is(c.x, c.y) { + t.Errorf("`%s` is `%s`", c.x, c.y) + } + } + + var e1x fooError + if ok := errors.As(e21, &e1x); !ok { + t.Error("e21 cannot convert to e1") + } + if int(e1x) != 100 { + t.Error("e1x is not 100") + } + + var e2x *Error + if ok := errors.As(e21, &e2x); !ok { + t.Error("e21 cannot convert to e2") + } + if e2x.ID() != "e2" { + t.Error("err is not e2") + } + + e3x := e3.Wrap(e1) + if ok := errors.As(e21, &e3x); !ok { + t.Error("e21 cannot convert to e3") + } + if e3x.ID() != "e2" { + t.Error("err is not e2") + } +} diff --git a/normalize.go b/normalize.go index 40a6ec12..e6f26440 100644 --- a/normalize.go +++ b/normalize.go @@ -255,6 +255,26 @@ func (e *Error) Wrap(err error) *Error { return nil } +// Unwrap returns cause of the error. +// It allows Error to work with errors.Is() and errors.As() from the Go +// standard package. +func (e *Error) Unwrap() error { + if e == nil { + return nil + } + return e.cause +} + +// Is checks if e has the same error ID with other. +// It allows Error to work with errors.Is() from the Go standard package. +func (e *Error) Is(other error) bool { + err, ok := other.(*Error) + if !ok { + return false + } + return (e == nil && err == nil) || (e != nil && err != nil && e.ID() == err.ID()) +} + func (e *Error) Cause() error { root := Unwrap(e.cause) if root == nil { From 4577084519e85ba4e8a1788f4cbdf189a5535bee Mon Sep 17 00:00:00 2001 From: xhe Date: Fri, 8 Oct 2021 15:53:58 +0800 Subject: [PATCH 35/59] add ignore flag to errdoc-gen Signed-off-by: xhe --- errdoc-gen/main.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/errdoc-gen/main.go b/errdoc-gen/main.go index 21f17084..1d194e7f 100644 --- a/errdoc-gen/main.go +++ b/errdoc-gen/main.go @@ -30,6 +30,7 @@ var opt struct { source string module string output string + ignore string retainCode bool } @@ -37,6 +38,7 @@ func init() { flag.StringVar(&opt.source, "source", "", "The source directory of error documentation") flag.StringVar(&opt.module, "module", "", "The module name of target repository") flag.StringVar(&opt.output, "output", "", "The output path of error documentation file") + flag.StringVar(&opt.ignore, "ignore", "", "Directories to ignore, splitted by comma") flag.BoolVar(&opt.retainCode, "retain-code", false, "Retain the generated code when generator exit") } @@ -242,11 +244,20 @@ func errdoc(source, module string) ([]*errDecl, error) { dedup := map[string]*errDecl{} + ignored := strings.Split(opt.ignore, ",") + for i := range ignored { + ignored[i] = filepath.Join(source, ignored[i]) + } err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { + for i := range ignored { + if ignored[i] == path { + return filepath.SkipDir + } + } return nil } if !strings.HasSuffix(path, ".go") { From 3c0b8803d51dbc2bf28dc575febee0ede133eacf Mon Sep 17 00:00:00 2001 From: tangenta Date: Thu, 23 Dec 2021 21:14:22 +0800 Subject: [PATCH 36/59] normalize: expose the message arguments of the error --- normalize.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/normalize.go b/normalize.go index e6f26440..6860bf3d 100644 --- a/normalize.go +++ b/normalize.go @@ -106,6 +106,11 @@ func (e *Error) MessageTemplate() string { return e.message } +// Args returns the message arguments of this error. +func (e *Error) Args() []interface{} { + return e.args +} + // Error implements error interface. func (e *Error) Error() string { if e == nil { From 5d1522493a1ae1a44508740877702f2b4694a3a6 Mon Sep 17 00:00:00 2001 From: disksing Date: Tue, 22 Mar 2022 15:08:50 +0800 Subject: [PATCH 37/59] fix error format (#42) Signed-off-by: disksing --- errors.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 612f77d2..0621419d 100644 --- a/errors.go +++ b/errors.go @@ -279,8 +279,10 @@ func (w *withMessage) Format(s fmt.State, verb rune) { return } fallthrough - case 's', 'q': + case 's': io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) } } From efdec0445955e58910445dc9219263f695b8bf13 Mon Sep 17 00:00:00 2001 From: TennyZhuang Date: Tue, 22 Mar 2022 15:11:01 +0800 Subject: [PATCH 38/59] Fix StackTrace() usage comment in errors.go (#20) --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 0621419d..c3034333 100644 --- a/errors.go +++ b/errors.go @@ -77,7 +77,7 @@ // // if stacked := errors.GetStackTracer(err); stacked != nil { // for _, f := range stacked.StackTrace() { -// fmt.Printf("%+s:%d", f) +// fmt.Printf("%+s:%d\n", f, f) // } // } // From 518f63d66278bd4aac1e2d7dae0430b59417ff99 Mon Sep 17 00:00:00 2001 From: xhe Date: Fri, 29 Jul 2022 12:06:31 +0800 Subject: [PATCH 39/59] errdoc-gen: create files without exec perm (#64) Signed-off-by: xhe --- errdoc-gen/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/errdoc-gen/main.go b/errdoc-gen/main.go index 1d194e7f..2b4a63a6 100644 --- a/errdoc-gen/main.go +++ b/errdoc-gen/main.go @@ -188,7 +188,7 @@ func main() { } buffer.WriteString("\n") } - if err := ioutil.WriteFile(outpath, buffer.Bytes(), os.ModePerm); err != nil { + if err := ioutil.WriteFile(outpath, buffer.Bytes(), 0644); err != nil { panic(err) } } @@ -205,7 +205,7 @@ func main() { } outFile := filepath.Join(targetDir, entryFileName) - out, err := os.OpenFile(outFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, os.ModePerm) + out, err := os.OpenFile(outFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { fatal("Open %s failed: %+v", outFile, err) } From b66cddb77c327194ac131987d64f771e48a09d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=8E=E5=8D=8E?= Date: Sun, 9 Oct 2022 17:22:01 +0800 Subject: [PATCH 40/59] Update README.md (#65) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6483ba2a..b97656ac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Package errors provides simple error handling primitives. -`go get github.com/pkg/errors` +`go get github.com/pingcap/errors` The traditional error handling idiom in Go is roughly akin to ```go From f711732a1b32acb4fb53185700d4a5ad93abb046 Mon Sep 17 00:00:00 2001 From: AilinKid <314806019@qq.com> Date: Tue, 12 Dec 2023 17:57:48 +0800 Subject: [PATCH 41/59] add no_stack_errorf Signed-off-by: AilinKid <314806019@qq.com> --- juju_adaptor.go | 9 +++++++++ stack_test.go | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/juju_adaptor.go b/juju_adaptor.go index ece86cd6..5e000aad 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -66,6 +66,15 @@ func NewNoStackError(msg string) error { } } +// NewNoStackErrorf creates error with error stack and formats according +// to a format specifier and returns the string as a value that satisfies error. +func NewNoStackErrorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: &emptyStack, + } +} + // SuspendStack suspends stack generate for error. func SuspendStack(err error) error { if err == nil { diff --git a/stack_test.go b/stack_test.go index c4b3d2da..666af40a 100644 --- a/stack_test.go +++ b/stack_test.go @@ -299,6 +299,16 @@ func TestNewNoStackError(t *testing.T) { } } +func TestNewNoStackErrorf(t *testing.T) { + err := NewNoStackErrorf("test error %s", "yes") + err = Trace(err) + err = Trace(err) + result := fmt.Sprintf("%+v", err) + if result != "test error yes" { + t.Errorf("NewNoStackError(): want %s, got %v", "test error", result) + } +} + func TestSuspendError(t *testing.T) { err := io.EOF err = SuspendStack(err) From e056997136bbdcc00d2d7c229bc89592397b348e Mon Sep 17 00:00:00 2001 From: xhe Date: Mon, 11 Mar 2024 10:47:30 +0800 Subject: [PATCH 42/59] normalize: add log desensitization formatter (#67) Signed-off-by: xhe --- normalize.go | 62 +++++++++++++++++++++++++++++++++++++---------- normalize_test.go | 18 ++++++++++++++ 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/normalize.go b/normalize.go index 6860bf3d..34a47455 100644 --- a/normalize.go +++ b/normalize.go @@ -21,8 +21,16 @@ import ( "go.uber.org/atomic" ) +var _ fmt.Formatter = (*redactFormatter)(nil) + // RedactLogEnabled defines whether the arguments of Error need to be redacted. -var RedactLogEnabled atomic.Bool +var RedactLogEnabled atomic.String + +const ( + RedactLogEnable string = "ON" + RedactLogDisable = "OFF" + RedactLogMarker = "MARKER" +) // ErrCode represents a specific error type in a error class. // Same error code can be used in different error classes. @@ -39,19 +47,21 @@ type RFCErrorCode string // var ErrUnavailable = errors.Normalize("Region %d is unavailable", errors.RFCCodeText("Unavailable")) // // "throw" it at runtime: -// func Somewhat() error { -// ... -// if err != nil { -// // generate a stackful error use the message template at defining, -// // also see FastGen(it's stackless), GenWithStack(it uses custom message template). -// return ErrUnavailable.GenWithStackByArgs(region.ID) -// } -// } +// +// func Somewhat() error { +// ... +// if err != nil { +// // generate a stackful error use the message template at defining, +// // also see FastGen(it's stackless), GenWithStack(it uses custom message template). +// return ErrUnavailable.GenWithStackByArgs(region.ID) +// } +// } // // testing whether an error belongs to a prototype: -// if ErrUnavailable.Equal(err) { -// // handle this error. -// } +// +// if ErrUnavailable.Equal(err) { +// // handle this error. +// } type Error struct { code ErrCode // codeText is the textual describe of the error code @@ -63,6 +73,7 @@ type Error struct { // And it is controlled by the global var RedactLogEnabled. // For example, an original error is `Duplicate entry 'PRIMARY' for key 'key'`, // when RedactLogEnabled is ON and redactArgsPos is [0, 1], the error is `Duplicate entry '?' for key '?'`. + // when RedactLogEnabled is MARKER and redactArgsPos is [0, 1], the error is `Duplicate entry '‹..›' for key '‹..›'`. redactArgsPos []int // Cause is used to warp some third party error. cause error @@ -207,12 +218,19 @@ func (e *Error) NotEqual(err error) bool { // RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. func RedactErrorArg(args []interface{}, position []int) { - if RedactLogEnabled.Load() { + switch RedactLogEnabled.Load() { + case RedactLogEnable: for _, pos := range position { if len(args) > pos { args[pos] = "?" } } + case RedactLogMarker: + for _, pos := range position { + if len(args) > pos { + args[pos] = &redactFormatter{args[pos]} + } + } } } @@ -339,3 +357,21 @@ func Normalize(message string, opts ...NormalizeOption) *Error { } return e } + +type redactFormatter struct { + arg interface{} +} + +func (e *redactFormatter) Format(f fmt.State, verb rune) { + origin := fmt.Sprintf(fmt.FormatString(f, verb), e.arg) + fmt.Fprintf(f, "‹") + for _, c := range origin { + if c == '‹' || c == '›' { + fmt.Fprintf(f, "%c", c) + fmt.Fprintf(f, "%c", c) + } else { + fmt.Fprintf(f, "%c", c) + } + } + fmt.Fprintf(f, "›") +} diff --git a/normalize_test.go b/normalize_test.go index d1dd70b2..993fad5b 100644 --- a/normalize_test.go +++ b/normalize_test.go @@ -1,6 +1,7 @@ package errors import ( + "fmt" "regexp" "testing" ) @@ -31,3 +32,20 @@ func TestCauseInErrorMessage(t *testing.T) { notWrapped := errTest.GenWithStack("everything is alright") errorMatches(t, notWrapped, `^\[Internal:Test\]everything is alright$`) } + +func TestRedactFormatter(t *testing.T) { + rv := 34.03498 + v := &redactFormatter{rv} + for _, f := range []string{"%d", "%.2d"} { + a := fmt.Sprintf(f, v) + b := fmt.Sprintf("‹"+f+"›", rv) + if a != b { + t.Errorf("%s != %s", a, b) + } + } + + v = &redactFormatter{"‹"} + if a := fmt.Sprintf("%s", v); a != "‹‹‹›" { + t.Errorf("%s != <<<>", a) + } +} From f97970b88865112e5809c45fa79fd6b486aeb0bb Mon Sep 17 00:00:00 2001 From: Bin <49082129+songzhibin97@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:16:13 +0800 Subject: [PATCH 43/59] feat: add join error (#68) * feat: add join error --- join.go | 62 ++++++++++++++++++++++++++++++++++++++++ join_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 join.go create mode 100644 join_test.go diff --git a/join.go b/join.go new file mode 100644 index 00000000..af587b1e --- /dev/null +++ b/join.go @@ -0,0 +1,62 @@ +// Copyright 2024 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +// Join returns an error that wraps the given errors. +// Any nil error values are discarded. +// Join returns nil if every value in errs is nil. +// The error formats as the concatenation of the strings obtained +// by calling the Error method of each element of errs, with a newline +// between each string. +// +// A non-nil error returned by Join implements the Unwrap() []error method. +func Join(errs ...error) error { + n := 0 + for _, err := range errs { + if err != nil { + n++ + } + } + if n == 0 { + return nil + } + e := &joinError{ + errs: make([]error, 0, n), + } + for _, err := range errs { + if err != nil { + e.errs = append(e.errs, err) + } + } + return e +} + +type joinError struct { + errs []error +} + +func (e *joinError) Error() string { + var b []byte + for i, err := range e.errs { + if i > 0 { + b = append(b, '\n') + } + b = append(b, err.Error()...) + } + return string(b) +} + +func (e *joinError) Unwrap() []error { + return e.errs +} diff --git a/join_test.go b/join_test.go new file mode 100644 index 00000000..56a44160 --- /dev/null +++ b/join_test.go @@ -0,0 +1,80 @@ +// Copyright 2024 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "reflect" + "testing" +) + +func TestJoinReturnsNil(t *testing.T) { + if err := Join(); err != nil { + t.Errorf("Join() = %v, want nil", err) + } + if err := Join(nil); err != nil { + t.Errorf("Join(nil) = %v, want nil", err) + } + if err := Join(nil, nil); err != nil { + t.Errorf("Join(nil, nil) = %v, want nil", err) + } +} + +func TestJoin(t *testing.T) { + err1 := New("err1") + err2 := New("err2") + for _, test := range []struct { + errs []error + want []error + }{{ + errs: []error{err1}, + want: []error{err1}, + }, { + errs: []error{err1, err2}, + want: []error{err1, err2}, + }, { + errs: []error{err1, nil, err2}, + want: []error{err1, err2}, + }} { + got := Join(test.errs...).(interface{ Unwrap() []error }).Unwrap() + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Join(%v) = %v; want %v", test.errs, got, test.want) + } + if len(got) != cap(got) { + t.Errorf("Join(%v) returns errors with len=%v, cap=%v; want len==cap", test.errs, len(got), cap(got)) + } + } +} + +func TestJoinErrorMethod(t *testing.T) { + err1 := New("err1") + err2 := New("err2") + for _, test := range []struct { + errs []error + want string + }{{ + errs: []error{err1}, + want: "err1", + }, { + errs: []error{err1, err2}, + want: "err1\nerr2", + }, { + errs: []error{err1, nil, err2}, + want: "err1\nerr2", + }} { + got := Join(test.errs...).Error() + if got != test.want { + t.Errorf("Join(%v).Error() = %q; want %q", test.errs, got, test.want) + } + } +} From 79d59a0bc70fc595225ab35bbc932dc220ca685f Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Mon, 18 Mar 2024 14:33:02 +0800 Subject: [PATCH 44/59] change --- errors.go | 74 ++++++++++++++++++++++++++------------------------ errors_test.go | 11 ++++++++ stack.go | 27 +++++++++++------- 3 files changed, 67 insertions(+), 45 deletions(-) diff --git a/errors.go b/errors.go index c3034333..dcd834f1 100644 --- a/errors.go +++ b/errors.go @@ -2,84 +2,84 @@ // // The traditional error handling idiom in Go is roughly akin to // -// if err != nil { -// return err -// } +// if err != nil { +// return err +// } // // which applied recursively up the call stack results in error reports // without context or debugging information. The errors package allows // programmers to add context to the failure path in their code in a way // that does not destroy the original value of the error. // -// Adding context to an error +// # Adding context to an error // // The errors.Annotate function returns a new error that adds context to the // original error by recording a stack trace at the point Annotate is called, // and the supplied message. For example // -// _, err := ioutil.ReadAll(r) -// if err != nil { -// return errors.Annotate(err, "read failed") -// } +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Annotate(err, "read failed") +// } // // If additional control is required the errors.AddStack and errors.WithMessage // functions destructure errors.Annotate into its component operations of annotating // an error with a stack trace and an a message, respectively. // -// Retrieving the cause of an error +// # Retrieving the cause of an error // // Using errors.Annotate constructs a stack of errors, adding context to the // preceding error. Depending on the nature of the error it may be necessary // to reverse the operation of errors.Annotate to retrieve the original error // for inspection. Any error value which implements this interface // -// type causer interface { -// Cause() error -// } +// type causer interface { +// Cause() error +// } // // can be inspected by errors.Cause. errors.Cause will recursively retrieve // the topmost error which does not implement causer, which is assumed to be // the original cause. For example: // -// switch err := errors.Cause(err).(type) { -// case *MyError: -// // handle specifically -// default: -// // unknown error -// } +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } // // causer interface is not exported by this package, but is considered a part // of stable public API. // errors.Unwrap is also available: this will retrieve the next error in the chain. // -// Formatted printing of errors +// # Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can // be formatted by the fmt package. The following verbs are supported // -// %s print the error. If the error has a Cause it will be -// printed recursively -// %v see %s -// %+v extended format. Each Frame of the error's StackTrace will -// be printed in detail. +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. // -// Retrieving the stack trace of an error or wrapper +// # Retrieving the stack trace of an error or wrapper // // New, Errorf, Annotate, and Annotatef record a stack trace at the point they are invoked. // This information can be retrieved with the StackTracer interface that returns // a StackTrace. Where errors.StackTrace is defined as // -// type StackTrace []Frame +// type StackTrace []Frame // // The Frame type represents a call site in the stack trace. Frame supports // the fmt.Formatter interface that can be used for printing information about // the stack trace of this error. For example: // -// if stacked := errors.GetStackTracer(err); stacked != nil { -// for _, f := range stacked.StackTrace() { -// fmt.Printf("%+s:%d\n", f, f) -// } -// } +// if stacked := errors.GetStackTracer(err); stacked != nil { +// for _, f := range stacked.StackTrace() { +// fmt.Printf("%+s:%d\n", f, f) +// } +// } // // See the documentation for Frame.Format for more details. // @@ -122,7 +122,11 @@ func HasStack(err error) bool { if errWithStack, ok := err.(StackTraceAware); ok { return errWithStack.HasStack() } - return GetStackTracer(err) != nil + // Error.FastGenXXX or call SuspendStack directly will make an empty stack trace, + // which should be considered as no stack trace, to allow upper layer code to + // add stack trace with Trace. + stackTracer := GetStackTracer(err) + return stackTracer != nil && !stackTracer.Empty() } // fundamental is an error that has a message and a stack, but no caller. @@ -290,9 +294,9 @@ func (w *withMessage) Format(s fmt.State, verb rune) { // An error value has a cause if it implements the following // interface: // -// type causer interface { -// Cause() error -// } +// type causer interface { +// Cause() error +// } // // If the error does not implement Cause, the original error will // be returned. If the error is nil, nil will be returned without further diff --git a/errors_test.go b/errors_test.go index 3d69324c..04966f80 100644 --- a/errors_test.go +++ b/errors_test.go @@ -7,6 +7,8 @@ import ( "reflect" "strconv" "testing" + + "github.com/stretchr/testify/require" ) func TestNew(t *testing.T) { @@ -437,3 +439,12 @@ func TestWorkWithStdErrors(t *testing.T) { t.Error("err is not e2") } } + +func TestHasTrace(t *testing.T) { + targetErr := Normalize("test err") + require.False(t, HasStack(targetErr)) + require.False(t, HasStack(targetErr.FastGen("fast gen"))) + require.False(t, HasStack(targetErr.FastGenByArgs("fast gen arg"))) + require.True(t, HasStack(Trace(targetErr.FastGen("fast gen")))) + require.True(t, HasStack(targetErr.GenWithStack("gen"))) +} diff --git a/stack.go b/stack.go index bb1e6a84..a981f491 100644 --- a/stack.go +++ b/stack.go @@ -14,6 +14,9 @@ import ( // Generally you would want to use the GetStackTracer function to do that. type StackTracer interface { StackTrace() StackTrace + // Empty returns true if the stack trace is empty, StackTrace might clone the + // stack trace, add this method to avoid unnecessary clone. + Empty() bool } // GetStackTracer will return the first StackTracer in the causer chain. @@ -63,16 +66,16 @@ func (f Frame) line() int { // Format formats the frame according to the fmt.Formatter interface. // -// %s source file -// %d source line -// %n function name -// %v equivalent to %s:%d +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+s function name and path of source file relative to the compile time -// GOPATH separated by \n\t (\n\t) -// %+v equivalent to %+s:%d +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { f.format(s, s, verb) } @@ -113,12 +116,12 @@ type StackTrace []Frame // Format formats the stack of Frames according to the fmt.Formatter interface. // -// %s lists source files for each Frame in the stack -// %v lists the source file and line number for each Frame in the stack +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+v Prints filename, function, and line number for each Frame in the stack. +// %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { var b bytes.Buffer switch verb { @@ -192,6 +195,10 @@ func (s *stack) StackTrace() StackTrace { return f } +func (s *stack) Empty() bool { + return len(*s) == 0 +} + func callers() *stack { return callersSkip(4) } From b3526907a8728aec0ef16bfbea45847a4eb512d8 Mon Sep 17 00:00:00 2001 From: Oli Strik Date: Thu, 20 Jun 2024 05:51:50 +0200 Subject: [PATCH 45/59] fix: return new withStack directly so AddStack() is not added to the stack (#70) --- errors.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/errors.go b/errors.go index dcd834f1..f3d79d55 100644 --- a/errors.go +++ b/errors.go @@ -172,10 +172,14 @@ func WithStack(err error) error { // AddStack is similar to WithStack. // However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. func AddStack(err error) error { - if HasStack(err) { + if err == nil || HasStack(err) { return err } - return WithStack(err) + + return &withStack{ + err, + callers(), + } } type withStack struct { From 6b8c588c3122a195187a3e10f042c981be5351f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=B2=9A?= <36239017+YuJuncen@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:45:35 +0800 Subject: [PATCH 46/59] make `WalkDeep` recursively walk the full chain (#73) * make `WalkDeep` recursively walk the full chain --- errors_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ group.go | 15 +++++++++++---- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/errors_test.go b/errors_test.go index 04966f80..f7de4e52 100644 --- a/errors_test.go +++ b/errors_test.go @@ -372,6 +372,53 @@ func TestWalkDeep(t *testing.T) { } } +func TestWalkDeepNil(t *testing.T) { + require.False(t, WalkDeep(nil, func(err error) bool { return true })) +} + +func TestWalkDeepComplexTree(t *testing.T) { + err := &errWalkTest{v: 1, cause: &errWalkTest{ + sub: []error{ + &errWalkTest{ + v: 10, + cause: &errWalkTest{v: 11}, + }, + &errWalkTest{ + v: 20, + sub: []error{ + &errWalkTest{v: 21}, + &errWalkTest{v: 22}, + }, + }, + &errWalkTest{ + v: 30, + cause: &errWalkTest{v: 31}, + }, + }, + }} + + assertFind := func(v int, comment string) { + if !testFind(err, v) { + t.Errorf("%d not found in the error: %s", v, comment) + } + } + assertNotFind := func(v int, comment string) { + if testFind(err, v) { + t.Errorf("%d found in the error, but not expected: %s", v, comment) + } + } + + assertFind(1, "shallow search") + assertFind(11, "deep search A1") + assertFind(21, "deep search A2") + assertFind(22, "deep search B1") + assertNotFind(23, "deep search Neg") + assertFind(31, "deep search B2") + assertNotFind(32, "deep search Neg") + assertFind(30, "Tree node A") + assertFind(20, "Tree node with many children") +} + type fooError int func (fooError) Error() string { diff --git a/group.go b/group.go index e5a969ab..f07083bc 100644 --- a/group.go +++ b/group.go @@ -20,13 +20,20 @@ func Errors(err error) []error { // The visitor function can return true to end the traversal early // In that case, WalkDeep will return true, otherwise false. func WalkDeep(err error, visitor func(err error) bool) bool { + if err == nil { + return false + } + + if visitor(err) { + return true + } + // Go deep - unErr := err - for unErr != nil { - if done := visitor(unErr); done { + unErr := Unwrap(err) + if unErr != nil { + if WalkDeep(unErr, visitor) { return true } - unErr = Unwrap(unErr) } // Go wide From 6657a5023d8a3729d10d8659bce4df267186b585 Mon Sep 17 00:00:00 2001 From: tangenta Date: Mon, 17 Mar 2025 18:20:49 +0800 Subject: [PATCH 47/59] add redact util functions and upgrade go version (#75) Signed-off-by: tangenta --- go.mod | 8 +++- normalize.go | 49 ---------------------- redact.go | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 50 deletions(-) create mode 100644 redact.go diff --git a/go.mod b/go.mod index 25808af0..f71dac01 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,14 @@ module github.com/pingcap/errors -go 1.14 +go 1.23 require ( github.com/stretchr/testify v1.4.0 go.uber.org/atomic v1.6.0 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect +) diff --git a/normalize.go b/normalize.go index 34a47455..6c773a74 100644 --- a/normalize.go +++ b/normalize.go @@ -17,19 +17,6 @@ import ( "fmt" "runtime" "strconv" - - "go.uber.org/atomic" -) - -var _ fmt.Formatter = (*redactFormatter)(nil) - -// RedactLogEnabled defines whether the arguments of Error need to be redacted. -var RedactLogEnabled atomic.String - -const ( - RedactLogEnable string = "ON" - RedactLogDisable = "OFF" - RedactLogMarker = "MARKER" ) // ErrCode represents a specific error type in a error class. @@ -216,24 +203,6 @@ func (e *Error) NotEqual(err error) bool { return !e.Equal(err) } -// RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. -func RedactErrorArg(args []interface{}, position []int) { - switch RedactLogEnabled.Load() { - case RedactLogEnable: - for _, pos := range position { - if len(args) > pos { - args[pos] = "?" - } - } - case RedactLogMarker: - for _, pos := range position { - if len(args) > pos { - args[pos] = &redactFormatter{args[pos]} - } - } - } -} - // ErrorEqual returns a boolean indicating whether err1 is equal to err2. func ErrorEqual(err1, err2 error) bool { e1 := Cause(err1) @@ -357,21 +326,3 @@ func Normalize(message string, opts ...NormalizeOption) *Error { } return e } - -type redactFormatter struct { - arg interface{} -} - -func (e *redactFormatter) Format(f fmt.State, verb rune) { - origin := fmt.Sprintf(fmt.FormatString(f, verb), e.arg) - fmt.Fprintf(f, "‹") - for _, c := range origin { - if c == '‹' || c == '›' { - fmt.Fprintf(f, "%c", c) - fmt.Fprintf(f, "%c", c) - } else { - fmt.Fprintf(f, "%c", c) - } - } - fmt.Fprintf(f, "›") -} diff --git a/redact.go b/redact.go new file mode 100644 index 00000000..3262feb2 --- /dev/null +++ b/redact.go @@ -0,0 +1,115 @@ +package errors + +import ( + "encoding/hex" + "fmt" + "unsafe" + + "go.uber.org/atomic" +) + +var _ fmt.Formatter = (*redactFormatter)(nil) + +// RedactLogEnabled defines whether the arguments of Error need to be redacted. +var RedactLogEnabled atomic.String + +const ( + RedactLogEnable string = "ON" + RedactLogDisable string = "OFF" + RedactLogMarker string = "MARKER" +) + +// RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. +func RedactErrorArg(args []interface{}, position []int) { + switch RedactLogEnabled.Load() { + case RedactLogEnable: + for _, pos := range position { + if len(args) > pos { + args[pos] = "?" + } + } + case RedactLogMarker: + for _, pos := range position { + if len(args) > pos { + args[pos] = &redactFormatter{args[pos]} + } + } + } +} + +type redactFormatter struct { + arg interface{} +} + +func (e *redactFormatter) Format(f fmt.State, verb rune) { + origin := fmt.Sprintf(fmt.FormatString(f, verb), e.arg) + fmt.Fprintf(f, "‹") + for _, c := range origin { + if c == '‹' || c == '›' { + fmt.Fprintf(f, "%c", c) + fmt.Fprintf(f, "%c", c) + } else { + fmt.Fprintf(f, "%c", c) + } + } + fmt.Fprintf(f, "›") +} + +// NeedRedact returns whether to redact log +func NeedRedact() bool { + mode := RedactLogEnabled.Load() + return mode != RedactLogDisable && mode != "" +} + +// Key receives a key return omitted information if redact log enabled +func Key(key []byte) string { + if NeedRedact() { + return "?" + } + return String(ToUpperASCIIInplace(EncodeToString(key))) +} + +// KeyBytes receives a key return omitted information if redact log enabled +func KeyBytes(key []byte) []byte { + if NeedRedact() { + return []byte{'?'} + } + return ToUpperASCIIInplace(EncodeToString(key)) +} + +// String converts slice of bytes to string without copy. +func String(b []byte) (s string) { + if len(b) == 0 { + return "" + } + return unsafe.String(unsafe.SliceData(b), len(b)) +} + +// EncodeToString overrides hex.EncodeToString implementation. Difference: returns []byte, not string +func EncodeToString(src []byte) []byte { + dst := make([]byte, hex.EncodedLen(len(src))) + hex.Encode(dst, src) + return dst +} + +// ToUpperASCIIInplace bytes.ToUpper but zero-cost +func ToUpperASCIIInplace(s []byte) []byte { + hasLower := false + for i := 0; i < len(s); i++ { + c := s[i] + hasLower = hasLower || ('a' <= c && c <= 'z') + } + + if !hasLower { + return s + } + var c byte + for i := 0; i < len(s); i++ { + c = s[i] + if 'a' <= c && c <= 'z' { + c -= 'a' - 'A' + } + s[i] = c + } + return s +} From 8f80e5cb09ec98b9f73bf7b2d9d3561d1a194e6c Mon Sep 17 00:00:00 2001 From: tangenta Date: Tue, 18 Mar 2025 16:26:26 +0800 Subject: [PATCH 48/59] Revert "add redact util functions and upgrade go version (#75)" (#76) This reverts commit 6657a5023d8a3729d10d8659bce4df267186b585. --- go.mod | 8 +--- normalize.go | 49 ++++++++++++++++++++++ redact.go | 115 --------------------------------------------------- 3 files changed, 50 insertions(+), 122 deletions(-) delete mode 100644 redact.go diff --git a/go.mod b/go.mod index f71dac01..25808af0 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,8 @@ module github.com/pingcap/errors -go 1.23 +go 1.14 require ( github.com/stretchr/testify v1.4.0 go.uber.org/atomic v1.6.0 ) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v2 v2.2.2 // indirect -) diff --git a/normalize.go b/normalize.go index 6c773a74..34a47455 100644 --- a/normalize.go +++ b/normalize.go @@ -17,6 +17,19 @@ import ( "fmt" "runtime" "strconv" + + "go.uber.org/atomic" +) + +var _ fmt.Formatter = (*redactFormatter)(nil) + +// RedactLogEnabled defines whether the arguments of Error need to be redacted. +var RedactLogEnabled atomic.String + +const ( + RedactLogEnable string = "ON" + RedactLogDisable = "OFF" + RedactLogMarker = "MARKER" ) // ErrCode represents a specific error type in a error class. @@ -203,6 +216,24 @@ func (e *Error) NotEqual(err error) bool { return !e.Equal(err) } +// RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. +func RedactErrorArg(args []interface{}, position []int) { + switch RedactLogEnabled.Load() { + case RedactLogEnable: + for _, pos := range position { + if len(args) > pos { + args[pos] = "?" + } + } + case RedactLogMarker: + for _, pos := range position { + if len(args) > pos { + args[pos] = &redactFormatter{args[pos]} + } + } + } +} + // ErrorEqual returns a boolean indicating whether err1 is equal to err2. func ErrorEqual(err1, err2 error) bool { e1 := Cause(err1) @@ -326,3 +357,21 @@ func Normalize(message string, opts ...NormalizeOption) *Error { } return e } + +type redactFormatter struct { + arg interface{} +} + +func (e *redactFormatter) Format(f fmt.State, verb rune) { + origin := fmt.Sprintf(fmt.FormatString(f, verb), e.arg) + fmt.Fprintf(f, "‹") + for _, c := range origin { + if c == '‹' || c == '›' { + fmt.Fprintf(f, "%c", c) + fmt.Fprintf(f, "%c", c) + } else { + fmt.Fprintf(f, "%c", c) + } + } + fmt.Fprintf(f, "›") +} diff --git a/redact.go b/redact.go deleted file mode 100644 index 3262feb2..00000000 --- a/redact.go +++ /dev/null @@ -1,115 +0,0 @@ -package errors - -import ( - "encoding/hex" - "fmt" - "unsafe" - - "go.uber.org/atomic" -) - -var _ fmt.Formatter = (*redactFormatter)(nil) - -// RedactLogEnabled defines whether the arguments of Error need to be redacted. -var RedactLogEnabled atomic.String - -const ( - RedactLogEnable string = "ON" - RedactLogDisable string = "OFF" - RedactLogMarker string = "MARKER" -) - -// RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. -func RedactErrorArg(args []interface{}, position []int) { - switch RedactLogEnabled.Load() { - case RedactLogEnable: - for _, pos := range position { - if len(args) > pos { - args[pos] = "?" - } - } - case RedactLogMarker: - for _, pos := range position { - if len(args) > pos { - args[pos] = &redactFormatter{args[pos]} - } - } - } -} - -type redactFormatter struct { - arg interface{} -} - -func (e *redactFormatter) Format(f fmt.State, verb rune) { - origin := fmt.Sprintf(fmt.FormatString(f, verb), e.arg) - fmt.Fprintf(f, "‹") - for _, c := range origin { - if c == '‹' || c == '›' { - fmt.Fprintf(f, "%c", c) - fmt.Fprintf(f, "%c", c) - } else { - fmt.Fprintf(f, "%c", c) - } - } - fmt.Fprintf(f, "›") -} - -// NeedRedact returns whether to redact log -func NeedRedact() bool { - mode := RedactLogEnabled.Load() - return mode != RedactLogDisable && mode != "" -} - -// Key receives a key return omitted information if redact log enabled -func Key(key []byte) string { - if NeedRedact() { - return "?" - } - return String(ToUpperASCIIInplace(EncodeToString(key))) -} - -// KeyBytes receives a key return omitted information if redact log enabled -func KeyBytes(key []byte) []byte { - if NeedRedact() { - return []byte{'?'} - } - return ToUpperASCIIInplace(EncodeToString(key)) -} - -// String converts slice of bytes to string without copy. -func String(b []byte) (s string) { - if len(b) == 0 { - return "" - } - return unsafe.String(unsafe.SliceData(b), len(b)) -} - -// EncodeToString overrides hex.EncodeToString implementation. Difference: returns []byte, not string -func EncodeToString(src []byte) []byte { - dst := make([]byte, hex.EncodedLen(len(src))) - hex.Encode(dst, src) - return dst -} - -// ToUpperASCIIInplace bytes.ToUpper but zero-cost -func ToUpperASCIIInplace(s []byte) []byte { - hasLower := false - for i := 0; i < len(s); i++ { - c := s[i] - hasLower = hasLower || ('a' <= c && c <= 'z') - } - - if !hasLower { - return s - } - var c byte - for i := 0; i < len(s); i++ { - c = s[i] - if 'a' <= c && c <= 'z' { - c -= 'a' - 'A' - } - s[i] = c - } - return s -} From 74f78ae071ee39491527a1807b0fe9fc346c2b66 Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Fri, 23 May 2025 11:43:08 +0800 Subject: [PATCH 49/59] add GetSelfMsg to the error structs and GetErrStackMsg (#77) --- errors.go | 54 +++++++++++++++++++++++++++++++- errors_test.go | 27 ++++++++++++++++ format_test.go | 63 +++++++++++++++++++------------------- juju_adaptor.go | 5 +++ normalize.go | 10 +++--- stack_test.go | 48 ++++++++++++++++------------- terror_test/terror_test.go | 5 +-- 7 files changed, 152 insertions(+), 60 deletions(-) diff --git a/errors.go b/errors.go index f3d79d55..9a2ad45c 100644 --- a/errors.go +++ b/errors.go @@ -91,6 +91,12 @@ import ( "io" ) +// represent an error carries with message +type messenger interface { + // GetSelfMsg get its own message, the message of its cause error is NOT included. + GetSelfMsg() string +} + // New returns an error with the supplied message. // New also records the stack trace at the point it was called. func New(message string) error { @@ -135,8 +141,12 @@ type fundamental struct { *stack } +var _ messenger = (*fundamental)(nil) + func (f *fundamental) Error() string { return f.msg } +func (f *fundamental) GetSelfMsg() string { return f.msg } + func (f *fundamental) Format(s fmt.State, verb rune) { switch verb { case 'v': @@ -172,7 +182,7 @@ func WithStack(err error) error { // AddStack is similar to WithStack. // However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. func AddStack(err error) error { - if err == nil || HasStack(err) { + if err == nil || HasStack(err) { return err } @@ -187,8 +197,16 @@ type withStack struct { *stack } +var _ messenger = (*withStack)(nil) + func (w *withStack) Cause() error { return w.error } +func (w *withStack) GetSelfMsg() string { + // it doesn't have its own message, but we still need impl it to avoid calling + // err.Error() for its cause + return "" +} + // Unwrap provides compatibility for Go 1.13 error chains. func (w *withStack) Unwrap() error { return w.error } @@ -271,9 +289,13 @@ type withMessage struct { causeHasStack bool } +var _ messenger = (*withMessage)(nil) + func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) GetSelfMsg() string { return w.msg } + // Unwrap provides compatibility for Go 1.13 error chains. func (w *withMessage) Unwrap() error { return w.cause } func (w *withMessage) HasStack() bool { return w.causeHasStack } @@ -338,3 +360,33 @@ func Find(origErr error, test func(error) bool) error { }) return foundErr } + +// GetErrStackMsg get the concat error message the whole error stack. +// it's different from err.Error(), as pingcap/errors.Error will prepend the error +// code in the result of err.Error(), like below: +// +// [types:1292]Truncated incorrect +// +// and when there are multiple errors.Error in the chain, the err.Error() will +// return like this: +// +// [Lightning:Restore:ErrEncodeKV]encode kv error ... : [types:1292]Truncated incorrect DOUBLE value: 'a'" +// +// But sometimes we only want a single error code with pure message part. +func GetErrStackMsg(err error) string { + if err == nil { + return "" + } + m, ok := err.(messenger) + if ok { + msg := m.GetSelfMsg() + causeMsg := GetErrStackMsg(Unwrap(err)) + if msg == "" { + msg = causeMsg + } else if causeMsg != "" { + msg = msg + ": " + causeMsg + } + return msg + } + return err.Error() +} diff --git a/errors_test.go b/errors_test.go index f7de4e52..7e290eb1 100644 --- a/errors_test.go +++ b/errors_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "net/url" "reflect" "strconv" "testing" @@ -495,3 +496,29 @@ func TestHasTrace(t *testing.T) { require.True(t, HasStack(Trace(targetErr.FastGen("fast gen")))) require.True(t, HasStack(targetErr.GenWithStack("gen"))) } + +func TestGetErrStackMsg(t *testing.T) { + require.Equal(t, "", GetErrStackMsg(nil)) + + namedErr := Normalize("named err message", RFCCodeText("NamedError")) + require.False(t, HasStack(namedErr)) + require.Equal(t, "named err message", GetErrStackMsg(namedErr)) + tracedErr := Trace(namedErr) + require.Equal(t, "named err message", GetErrStackMsg(tracedErr)) + + annotatedErr := Annotate(tracedErr, "annotated message") + require.Equal(t, "annotated message: named err message", GetErrStackMsg(annotatedErr)) + + annotatedErr = Annotate(annotatedErr, "annotated message 2") + require.Equal(t, "annotated message 2: annotated message: named err message", GetErrStackMsg(annotatedErr)) + + fundErr := New("new fundamental error") + wrappedErr := namedErr.Wrap(fundErr) + require.Equal(t, "named err message: new fundamental error", GetErrStackMsg(wrappedErr)) + fastGen := wrappedErr.FastGen("fast gen") + require.Equal(t, "fast gen: new fundamental error", GetErrStackMsg(fastGen)) + + urlErr := &url.Error{Op: "GET", URL: "/url", Err: errors.New("internal golang err")} + fastGen = namedErr.Wrap(urlErr).FastGen("fast gen") + require.Equal(t, `fast gen: GET "/url": internal golang err`, GetErrStackMsg(fastGen)) +} diff --git a/format_test.go b/format_test.go index e8f6f4ff..a1af931a 100644 --- a/format_test.go +++ b/format_test.go @@ -27,7 +27,7 @@ func TestFormatNew(t *testing.T) { "%+v", "error\n" + "github.com/pingcap/errors.TestFormatNew\n" + - "\t.+/github.com/pingcap/errors/format_test.go:26", + "\t.+/pingcap/errors/format_test.go:26", }, { New("error"), "%q", @@ -57,7 +57,7 @@ func TestFormatErrorf(t *testing.T) { "%+v", "error\n" + "github.com/pingcap/errors.TestFormatErrorf\n" + - "\t.+/github.com/pingcap/errors/format_test.go:56", + "\t.+/pingcap/errors/format_test.go:56", }} for i, tt := range tests { @@ -83,7 +83,7 @@ func TestFormatWrap(t *testing.T) { "%+v", "error\n" + "github.com/pingcap/errors.TestFormatWrap\n" + - "\t.+/github.com/pingcap/errors/format_test.go:82", + "\t.+/pingcap/errors/format_test.go:82", }, { Annotate(io.EOF, "error"), "%s", @@ -98,14 +98,14 @@ func TestFormatWrap(t *testing.T) { "EOF\n" + "error\n" + "github.com/pingcap/errors.TestFormatWrap\n" + - "\t.+/github.com/pingcap/errors/format_test.go:96", + "\t.+/pingcap/errors/format_test.go:96", }, { Annotate(Annotate(io.EOF, "error1"), "error2"), "%+v", "EOF\n" + "error1\n" + "github.com/pingcap/errors.TestFormatWrap\n" + - "\t.+/github.com/pingcap/errors/format_test.go:103\n", + "\t.+/pingcap/errors/format_test.go:103\n", }, { Annotate(New("error with space"), "context"), "%q", @@ -136,7 +136,7 @@ func TestFormatWrapf(t *testing.T) { "EOF\n" + "error2\n" + "github.com/pingcap/errors.TestFormatWrapf\n" + - "\t.+/github.com/pingcap/errors/format_test.go:134", + "\t.+/pingcap/errors/format_test.go:134", }, { Annotatef(New("error"), "error%d", 2), "%s", @@ -150,7 +150,7 @@ func TestFormatWrapf(t *testing.T) { "%+v", "error\n" + "github.com/pingcap/errors.TestFormatWrapf\n" + - "\t.+/github.com/pingcap/errors/format_test.go:149", + "\t.+/pingcap/errors/format_test.go:149", }} for i, tt := range tests { @@ -176,7 +176,7 @@ func TestFormatWithStack(t *testing.T) { "%+v", []string{"EOF", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:175"}, + "\t.+/pingcap/errors/format_test.go:175"}, }, { WithStack(New("error")), "%s", @@ -190,36 +190,36 @@ func TestFormatWithStack(t *testing.T) { "%+v", []string{"error", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:189", + "\t.+/pingcap/errors/format_test.go:189", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:189"}, + "\t.+/pingcap/errors/format_test.go:189"}, }, { WithStack(WithStack(io.EOF)), "%+v", []string{"EOF", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:197", + "\t.+/pingcap/errors/format_test.go:197", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:197"}, + "\t.+/pingcap/errors/format_test.go:197"}, }, { WithStack(WithStack(Annotatef(io.EOF, "message"))), "%+v", []string{"EOF", "message", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:205", + "\t.+/pingcap/errors/format_test.go:205", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:205", + "\t.+/pingcap/errors/format_test.go:205", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:205"}, + "\t.+/pingcap/errors/format_test.go:205"}, }, { WithStack(Errorf("error%d", 1)), "%+v", []string{"error1", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:216", + "\t.+/pingcap/errors/format_test.go:216", "github.com/pingcap/errors.TestFormatWithStack\n" + - "\t.+/github.com/pingcap/errors/format_test.go:216"}, + "\t.+/pingcap/errors/format_test.go:216"}, }} for i, tt := range tests { @@ -246,7 +246,7 @@ func TestFormatWithMessage(t *testing.T) { []string{ "error", "github.com/pingcap/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pingcap/errors/format_test.go:244", + "\t.+/pingcap/errors/format_test.go:244", "error2"}, }, { WithMessage(io.EOF, "addition1"), @@ -273,13 +273,13 @@ func TestFormatWithMessage(t *testing.T) { "%+v", []string{"EOF", "error1", "error2", "github.com/pingcap/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pingcap/errors/format_test.go:272"}, + "\t.+/pingcap/errors/format_test.go:272"}, }, { WithMessage(Errorf("error%d", 1), "error2"), "%+v", []string{"error1", "github.com/pingcap/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pingcap/errors/format_test.go:278", + "\t.+/pingcap/errors/format_test.go:278", "error2"}, }, { WithMessage(WithStack(io.EOF), "error"), @@ -287,7 +287,7 @@ func TestFormatWithMessage(t *testing.T) { []string{ "EOF", "github.com/pingcap/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pingcap/errors/format_test.go:285", + "\t.+/pingcap/errors/format_test.go:285", "error"}, }, { WithMessage(Annotate(WithStack(io.EOF), "inside-error"), "outside-error"), @@ -295,7 +295,7 @@ func TestFormatWithMessage(t *testing.T) { []string{ "EOF", "github.com/pingcap/errors.TestFormatWithMessage\n" + - "\t.+/github.com/pingcap/errors/format_test.go:293", + "\t.+/pingcap/errors/format_test.go:293", "inside-error", "outside-error"}, }} @@ -383,22 +383,21 @@ func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) var stackLineR = regexp.MustCompile(`\.`) // parseBlocks parses input into a slice, where: -// - incase entry contains a newline, its a stacktrace -// - incase entry contains no newline, its a solo line. +// - incase entry contains a newline, its a stacktrace +// - incase entry contains no newline, its a solo line. // // Detecting stack boundaries only works incase the WithStack-calls are // to be found on the same line, thats why it is optionally here. // // Example use: // -// for _, e := range blocks { -// if strings.ContainsAny(e, "\n") { -// // Match as stack -// } else { -// // Match as line -// } -// } -// +// for _, e := range blocks { +// if strings.ContainsAny(e, "\n") { +// // Match as stack +// } else { +// // Match as line +// } +// } func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { var blocks []string diff --git a/juju_adaptor.go b/juju_adaptor.go index 5e000aad..3c2dd4ec 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -76,6 +76,11 @@ func NewNoStackErrorf(format string, args ...interface{}) error { } // SuspendStack suspends stack generate for error. +// Deprecated, it's semantic is to clear the stack inside, we still allow upper +// layer to add stack again by using Trace. +// Sometimes we have very deep calling stack, the lower layer calls SuspendStack, +// but the upper layer want to add stack to it, if we disable adding stack permanently +// for an error, it's very hard to diagnose certain issues. func SuspendStack(err error) error { if err == nil { return err diff --git a/normalize.go b/normalize.go index 34a47455..23ceb104 100644 --- a/normalize.go +++ b/normalize.go @@ -82,6 +82,8 @@ type Error struct { line int } +var _ messenger = (*Error)(nil) + // Code returns the numeric code of this error. // ID() will return textual error if there it is, // when you just want to get the purely numeric error @@ -127,10 +129,6 @@ func (e *Error) Error() string { if e == nil { return "" } - describe := e.codeText - if len(describe) == 0 { - describe = ErrCodeText(strconv.Itoa(int(e.code))) - } if e.cause != nil { return fmt.Sprintf("[%s]%s: %s", e.RFCCode(), e.GetMsg(), e.cause.Error()) } @@ -144,6 +142,10 @@ func (e *Error) GetMsg() string { return e.message } +func (e *Error) GetSelfMsg() string { + return e.GetMsg() +} + func (e *Error) fillLineAndFile(skip int) { // skip this _, file, line, ok := runtime.Caller(skip + 1) diff --git a/stack_test.go b/stack_test.go index 666af40a..d6d855ee 100644 --- a/stack_test.go +++ b/stack_test.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "runtime" + "strings" "testing" ) @@ -15,13 +16,13 @@ func TestFrameLine(t *testing.T) { want int }{{ Frame(initpc), - 10, + 11, }, { func() Frame { var pc, _, _, _ = runtime.Caller(0) return Frame(pc) }(), - 21, + 22, }, /* { // TODO stdlib `runtime` Behavior changed between 1.13 and 1.14 func() Frame { var pc, _, _, _ = runtime.Caller(1) @@ -67,7 +68,7 @@ func TestFrameFormat(t *testing.T) { Frame(initpc), "%+s", "github.com/pingcap/errors.init\n" + - "\t.+/github.com/pingcap/errors/stack_test.go", + "\t.+/pingcap/errors/stack_test.go", }, { Frame(0), "%s", @@ -79,7 +80,7 @@ func TestFrameFormat(t *testing.T) { }, { Frame(initpc), "%d", - "10", + "11", }, { Frame(0), "%d", @@ -109,12 +110,12 @@ func TestFrameFormat(t *testing.T) { }, { Frame(initpc), "%v", - "stack_test.go:10", + "stack_test.go:11", }, { Frame(initpc), "%+v", "github.com/pingcap/errors.init\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:10", + "\t.+/pingcap/errors/stack_test.go:11", }, { Frame(0), "%v", @@ -154,24 +155,24 @@ func TestStackTrace(t *testing.T) { }{{ New("ooh"), []string{ "github.com/pingcap/errors.TestStackTrace\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:155", + "\t.+/pingcap/errors/stack_test.go", }, }, { Annotate(New("ooh"), "ahh"), []string{ "github.com/pingcap/errors.TestStackTrace\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:160", // this is the stack of Wrap, not New + "\t.+/pingcap/errors/stack_test.go", // this is the stack of Wrap, not New }, }, { Cause(Annotate(New("ooh"), "ahh")), []string{ "github.com/pingcap/errors.TestStackTrace\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:165", // this is the stack of New + "\t.+/pingcap/errors/stack_test.go", // this is the stack of New }, }, { func() error { return New("ooh") }(), []string{ `github.com/pingcap/errors.(func·009|TestStackTrace.func1)` + - "\n\t.+/github.com/pingcap/errors/stack_test.go:170", // this is the stack of New + "\n\t.+/pingcap/errors/stack_test.go", // this is the stack of New "github.com/pingcap/errors.TestStackTrace\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:170", // this is the stack of New's caller + "\t.+/pingcap/errors/stack_test.go", // this is the stack of New's caller }, }, { Cause(func() error { @@ -179,12 +180,14 @@ func TestStackTrace(t *testing.T) { return Errorf("hello %s", fmt.Sprintf("world")) }() }()), []string{ - `github.com/pingcap/errors.(func·010|TestStackTrace.func2.1)` + - "\n\t.+/github.com/pingcap/errors/stack_test.go:179", // this is the stack of Errorf + // go 1.23 when Debug its suffix is "TestStackTrace.func2.1", it's + // "TestStackTrace.TestStackTrace.func2.func3" when Run + `github.com/pingcap/errors.(func·010|TestStackTrace.func2.1|TestStackTrace.TestStackTrace.func2.func3)` + + "\n\t.+/pingcap/errors/stack_test.go", // this is the stack of Errorf `github.com/pingcap/errors.(func·011|TestStackTrace.func2)` + - "\n\t.+/github.com/pingcap/errors/stack_test.go:180", // this is the stack of Errorf's caller + "\n\t.+/pingcap/errors/stack_test.go", // this is the stack of Errorf's caller "github.com/pingcap/errors.TestStackTrace\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:181", // this is the stack of Errorf's caller's caller + "\t.+/pingcap/errors/stack_test.go", // this is the stack of Errorf's caller's caller }, }} for i, tt := range tests { @@ -263,13 +266,13 @@ func TestStackTraceFormat(t *testing.T) { "%+v", "\n" + "github.com/pingcap/errors.stackTrace\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:211\n" + + "\t.+/pingcap/errors/stack_test.go:\\d+\n" + "github.com/pingcap/errors.TestStackTraceFormat\n" + - "\t.+/github.com/pingcap/errors/stack_test.go:262", + "\t.+/pingcap/errors/stack_test.go:\\d+", }, { stackTrace()[:2], "%#v", - `\[\]errors.Frame{stack_test.go:211, stack_test.go:270}`, + `\[\]errors.Frame{stack_test.go:\d+, stack_test.go:\d+}`, }} for i, tt := range tests { @@ -294,7 +297,8 @@ func TestNewNoStackError(t *testing.T) { err = Trace(err) err = Trace(err) result := fmt.Sprintf("%+v", err) - if result != "test error" { + if !strings.Contains(result, "test error") || + !strings.Contains(result, "pingcap/errors.TestNewNoStackError") { t.Errorf("NewNoStackError(): want %s, got %v", "test error", result) } } @@ -304,7 +308,8 @@ func TestNewNoStackErrorf(t *testing.T) { err = Trace(err) err = Trace(err) result := fmt.Sprintf("%+v", err) - if result != "test error yes" { + if !strings.Contains(result, "test error yes") || + !strings.Contains(result, "pingcap/errors.TestNewNoStackErrorf") { t.Errorf("NewNoStackError(): want %s, got %v", "test error", result) } } @@ -314,7 +319,8 @@ func TestSuspendError(t *testing.T) { err = SuspendStack(err) err = Trace(err) result := fmt.Sprintf("%+v", err) - if result != "EOF" { + if !strings.Contains(result, "EOF") || + !strings.Contains(result, "pingcap/errors.TestSuspendError") { t.Errorf("NewNoStackError(): want %s, got %v", "EOF", result) } if io.EOF != Cause(err) { diff --git a/terror_test/terror_test.go b/terror_test/terror_test.go index 02a6036c..86272fff 100644 --- a/terror_test/terror_test.go +++ b/terror_test/terror_test.go @@ -15,13 +15,14 @@ package terror_test import ( "encoding/json" - "github.com/stretchr/testify/suite" "os" "runtime" "strings" "testing" "time" + "github.com/stretchr/testify/suite" + "github.com/pingcap/errors" ) @@ -76,7 +77,7 @@ func (s *TErrorTestSuite) TestTraceAndLocation() { sysStack++ } } - s.Equalf(13, len(lines)-(2*sysStack), "stack = \n%s", stack) + s.Equalf(11, len(lines)-(2*sysStack), "stack = \n%s", stack) var containTerr bool for _, v := range lines { if strings.Contains(v, "terror_test.go") { From 8747b9df6c26560ce77178688975400984600698 Mon Sep 17 00:00:00 2001 From: panmuyun <82685759+panmuyun@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:54:55 +0800 Subject: [PATCH 50/59] build(deps): update go.uber.org/atomic from v1.6.0 to v1.11.0 (#78) --- go.mod | 2 +- go.sum | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 25808af0..87ce5d08 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.14 require ( github.com/stretchr/testify v1.4.0 - go.uber.org/atomic v1.6.0 + go.uber.org/atomic v1.11.0 ) diff --git a/go.sum b/go.sum index 4eb62d56..fcc78029 100644 --- a/go.sum +++ b/go.sum @@ -7,20 +7,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= From d18e03b1da261264ab5e55af8644f45276e5de3c Mon Sep 17 00:00:00 2001 From: panmuyun <82685759+panmuyun@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:58:59 +0800 Subject: [PATCH 51/59] build(deps): upgrade testify (#80) --- go.mod | 2 +- go.sum | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 87ce5d08..03be1e6e 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/pingcap/errors go 1.14 require ( - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.11.1 go.uber.org/atomic v1.11.0 ) diff --git a/go.sum b/go.sum index fcc78029..985ed063 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,19 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 8221bc87418bc80155f69c9ffac99a540dae39a1 Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Mon, 9 Mar 2026 23:15:31 +0800 Subject: [PATCH 52/59] normalize: freeze string args in Gen/FastGenByArgs --- bench_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ normalize.go | 18 ++++++++++++++++-- normalize_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/bench_test.go b/bench_test.go index 8d180397..537bc8ea 100644 --- a/bench_test.go +++ b/bench_test.go @@ -106,3 +106,44 @@ func BenchmarkStackFormatting(b *testing.B) { } GlobalE = stackStr } + +func BenchmarkByArgsArgFreeze(b *testing.B) { + errPrototype := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Bench")) + stringArg := "120120519090607" + + b.Run("FastGenByArgs-string", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.FastGenByArgs(stringArg) + } + GlobalE = err + }) + + b.Run("FastGenByArgs-int", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.FastGenByArgs(120120519090607) + } + GlobalE = err + }) + + b.Run("GenWithStackByArgs-string", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.GenWithStackByArgs(stringArg) + } + GlobalE = err + }) + + b.Run("GenWithStackByArgs-int", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.GenWithStackByArgs(120120519090607) + } + GlobalE = err + }) +} diff --git a/normalize.go b/normalize.go index 23ceb104..839983fc 100644 --- a/normalize.go +++ b/normalize.go @@ -142,6 +142,20 @@ func (e *Error) GetMsg() string { return e.message } +func freezeStringArgs(args []interface{}) []interface{} { + frozenArgs := make([]interface{}, len(args)) + copy(frozenArgs, args) + for i := range frozenArgs { + strArg, ok := frozenArgs[i].(string) + if !ok { + continue + } + // Freeze string args so delayed formatting can't observe later writes from zero-copy aliases. + frozenArgs[i] = string(append([]byte(nil), strArg...)) + } + return frozenArgs +} + func (e *Error) GetSelfMsg() string { return e.GetMsg() } @@ -172,7 +186,7 @@ func (e *Error) GenWithStack(format string, args ...interface{}) error { func (e *Error) GenWithStackByArgs(args ...interface{}) error { RedactErrorArg(args, e.redactArgsPos) err := *e - err.args = args + err.args = freezeStringArgs(args) err.fillLineAndFile(1) return AddStack(&err) } @@ -192,7 +206,7 @@ func (e *Error) FastGen(format string, args ...interface{}) error { func (e *Error) FastGenByArgs(args ...interface{}) error { RedactErrorArg(args, e.redactArgsPos) err := *e - err.args = args + err.args = freezeStringArgs(args) return SuspendStack(&err) } diff --git a/normalize_test.go b/normalize_test.go index 993fad5b..8bf090b8 100644 --- a/normalize_test.go +++ b/normalize_test.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "testing" + "unsafe" ) func errorMatches(t *testing.T, err error, re string) { @@ -49,3 +50,33 @@ func TestRedactFormatter(t *testing.T) { t.Errorf("%s != <<<>", a) } } + +func TestGenWithStackByArgsFreezeStringArg(t *testing.T) { + errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) + + origin := []byte("120120519090607") + arg := *(*string)(unsafe.Pointer(&origin)) + err := errTest.GenWithStackByArgs(arg) + + copy(origin, "1 1:1:1.0000027") + got := err.(*withStack).error.(*Error).GetMsg() + want := "Incorrect time value: '120120519090607'" + if got != want { + t.Fatalf("message changed after source bytes mutated, got %q, want %q", got, want) + } +} + +func TestFastGenByArgsFreezeStringArg(t *testing.T) { + errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) + + origin := []byte("120120519090607") + arg := *(*string)(unsafe.Pointer(&origin)) + err := errTest.FastGenByArgs(arg) + + copy(origin, "1 1:1:1.0000027") + got := err.(*withStack).error.(*Error).GetMsg() + want := "Incorrect time value: '120120519090607'" + if got != want { + t.Fatalf("message changed after source bytes mutated, got %q, want %q", got, want) + } +} From bf4271a1bc383370e676c74459f7c5de6c6d5806 Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Mon, 9 Mar 2026 23:22:25 +0800 Subject: [PATCH 53/59] normalize: freeze string args for all lazy message paths --- bench_test.go | 41 ++++++++++++++++++++++++++++++++ normalize.go | 14 +++++++---- normalize_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/bench_test.go b/bench_test.go index 537bc8ea..54dc48f4 100644 --- a/bench_test.go +++ b/bench_test.go @@ -147,3 +147,44 @@ func BenchmarkByArgsArgFreeze(b *testing.B) { GlobalE = err }) } + +func BenchmarkFormatArgFreeze(b *testing.B) { + errPrototype := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Bench")) + stringArg := "120120519090607" + + b.Run("FastGen-string", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.FastGen("Incorrect time value: '%s'", stringArg) + } + GlobalE = err + }) + + b.Run("FastGen-int", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.FastGen("Incorrect time value: '%d'", 120120519090607) + } + GlobalE = err + }) + + b.Run("GenWithStack-string", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.GenWithStack("Incorrect time value: '%s'", stringArg) + } + GlobalE = err + }) + + b.Run("GenWithStack-int", func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = errPrototype.GenWithStack("Incorrect time value: '%d'", 120120519090607) + } + GlobalE = err + }) +} diff --git a/normalize.go b/normalize.go index 839983fc..a2915ce9 100644 --- a/normalize.go +++ b/normalize.go @@ -150,7 +150,11 @@ func freezeStringArgs(args []interface{}) []interface{} { if !ok { continue } - // Freeze string args so delayed formatting can't observe later writes from zero-copy aliases. + // TiDB may pass unsafe zero-copy strings (for example from chunk buffers) as error args. + // Error message rendering is deferred until GetMsg/Error, so without freezing here we may + // observe later writes and print a different value than the one used when creating the error. + // Copying at every call site in TiDB is more expensive; freezing in this central path keeps + // the copy cost only on error construction. frozenArgs[i] = string(append([]byte(nil), strArg...)) } return frozenArgs @@ -177,7 +181,7 @@ func (e *Error) GenWithStack(format string, args ...interface{}) error { // TODO: RedactErrorArg err := *e err.message = format - err.args = args + err.args = freezeStringArgs(args) err.fillLineAndFile(1) return AddStack(&err) } @@ -197,7 +201,7 @@ func (e *Error) FastGen(format string, args ...interface{}) error { // TODO: RedactErrorArg err := *e err.message = format - err.args = args + err.args = freezeStringArgs(args) return SuspendStack(&err) } @@ -327,7 +331,7 @@ func (e *Error) FastGenWithCause(args ...interface{}) error { if e.cause != nil { err.message = e.cause.Error() } - err.args = args + err.args = freezeStringArgs(args) return SuspendStack(&err) } @@ -336,7 +340,7 @@ func (e *Error) GenWithStackByCause(args ...interface{}) error { if e.cause != nil { err.message = e.cause.Error() } - err.args = args + err.args = freezeStringArgs(args) err.fillLineAndFile(1) return AddStack(&err) } diff --git a/normalize_test.go b/normalize_test.go index 8bf090b8..8b9e46c0 100644 --- a/normalize_test.go +++ b/normalize_test.go @@ -80,3 +80,63 @@ func TestFastGenByArgsFreezeStringArg(t *testing.T) { t.Fatalf("message changed after source bytes mutated, got %q, want %q", got, want) } } + +func TestGenWithStackFreezeStringArg(t *testing.T) { + errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) + + origin := []byte("120120519090607") + arg := *(*string)(unsafe.Pointer(&origin)) + err := errTest.GenWithStack("Incorrect time value: '%s'", arg) + + copy(origin, "1 1:1:1.0000027") + got := err.(*withStack).error.(*Error).GetMsg() + want := "Incorrect time value: '120120519090607'" + if got != want { + t.Fatalf("message changed after source bytes mutated, got %q, want %q", got, want) + } +} + +func TestFastGenFreezeStringArg(t *testing.T) { + errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) + + origin := []byte("120120519090607") + arg := *(*string)(unsafe.Pointer(&origin)) + err := errTest.FastGen("Incorrect time value: '%s'", arg) + + copy(origin, "1 1:1:1.0000027") + got := err.(*withStack).error.(*Error).GetMsg() + want := "Incorrect time value: '120120519090607'" + if got != want { + t.Fatalf("message changed after source bytes mutated, got %q, want %q", got, want) + } +} + +func TestGenWithStackByCauseFreezeStringArg(t *testing.T) { + errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) + + origin := []byte("120120519090607") + arg := *(*string)(unsafe.Pointer(&origin)) + err := errTest.GenWithStackByCause(arg) + + copy(origin, "1 1:1:1.0000027") + got := err.(*withStack).error.(*Error).GetMsg() + want := "Incorrect time value: '120120519090607'" + if got != want { + t.Fatalf("message changed after source bytes mutated, got %q, want %q", got, want) + } +} + +func TestFastGenWithCauseFreezeStringArg(t *testing.T) { + errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) + + origin := []byte("120120519090607") + arg := *(*string)(unsafe.Pointer(&origin)) + err := errTest.FastGenWithCause(arg) + + copy(origin, "1 1:1:1.0000027") + got := err.(*withStack).error.(*Error).GetMsg() + want := "Incorrect time value: '120120519090607'" + if got != want { + t.Fatalf("message changed after source bytes mutated, got %q, want %q", got, want) + } +} From 8c2f8cf2be21488c02961b72fe582640bdc681ee Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Mon, 9 Mar 2026 23:24:29 +0800 Subject: [PATCH 54/59] normalize: mutate args in place when freezing strings --- normalize.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/normalize.go b/normalize.go index a2915ce9..d556c1b3 100644 --- a/normalize.go +++ b/normalize.go @@ -143,10 +143,11 @@ func (e *Error) GetMsg() string { } func freezeStringArgs(args []interface{}) []interface{} { - frozenArgs := make([]interface{}, len(args)) - copy(frozenArgs, args) - for i := range frozenArgs { - strArg, ok := frozenArgs[i].(string) + // This helper intentionally mutates the input slice in place. + // It is only used inside error-construction paths where args are internal and should + // not be reused by callers after passing into Gen*/FastGen* APIs. + for i := range args { + strArg, ok := args[i].(string) if !ok { continue } @@ -155,9 +156,9 @@ func freezeStringArgs(args []interface{}) []interface{} { // observe later writes and print a different value than the one used when creating the error. // Copying at every call site in TiDB is more expensive; freezing in this central path keeps // the copy cost only on error construction. - frozenArgs[i] = string(append([]byte(nil), strArg...)) + args[i] = string(append([]byte(nil), strArg...)) } - return frozenArgs + return args } func (e *Error) GetSelfMsg() string { From 5f3a71c9b81d0927b2dd1f7775ad3455d5aaac3f Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Tue, 10 Mar 2026 11:32:07 +0800 Subject: [PATCH 55/59] change --- bench_test.go | 244 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 166 insertions(+), 78 deletions(-) diff --git a/bench_test.go b/bench_test.go index 54dc48f4..b8864fee 100644 --- a/bench_test.go +++ b/bench_test.go @@ -1,10 +1,10 @@ package errors import ( + stderrors "errors" "fmt" + "strings" "testing" - - stderrors "errors" ) func noErrors(at, depth int) error { @@ -107,84 +107,172 @@ func BenchmarkStackFormatting(b *testing.B) { GlobalE = stackStr } -func BenchmarkByArgsArgFreeze(b *testing.B) { - errPrototype := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Bench")) - stringArg := "120120519090607" - - b.Run("FastGenByArgs-string", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.FastGenByArgs(stringArg) - } - GlobalE = err - }) - - b.Run("FastGenByArgs-int", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.FastGenByArgs(120120519090607) - } - GlobalE = err - }) - - b.Run("GenWithStackByArgs-string", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.GenWithStackByArgs(stringArg) - } - GlobalE = err - }) - - b.Run("GenWithStackByArgs-int", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.GenWithStackByArgs(120120519090607) +type argsProfile struct { + name string + containsString bool + build func(count, stringLen int) []interface{} +} + +func buildStringArgs(count, stringLen int) []interface{} { + arg := strings.Repeat("x", stringLen) + args := make([]interface{}, count) + for i := range args { + args[i] = arg + } + return args +} + +func buildIntArgs(count, _ int) []interface{} { + args := make([]interface{}, count) + for i := range args { + args[i] = i + } + return args +} + +func buildMixedArgs(count, stringLen int) []interface{} { + strArg := strings.Repeat("x", stringLen) + args := make([]interface{}, count) + for i := range args { + switch i % 4 { + case 0: + args[i] = strArg + case 1: + args[i] = i + case 2: + args[i] = i%2 == 0 + default: + args[i] = float64(i) + 0.25 } - GlobalE = err - }) + } + return args +} + +func benchmarkFormat(count int) string { + if count <= 0 { + return "" + } + format := "%v" + for i := 1; i < count; i++ { + format += " %v" + } + return format +} + +func BenchmarkByArgsArgFreeze(b *testing.B) { + errPrototype := Normalize("bench", RFCCodeText("Internal:Bench")) + + apiCases := []struct { + name string + call func(errPrototype *Error, args []interface{}) error + }{ + { + name: "FastGenByArgs", + call: func(errPrototype *Error, args []interface{}) error { + return errPrototype.FastGenByArgs(args...) + }, + }, + } + profiles := []argsProfile{ + {name: "string", containsString: true, build: buildStringArgs}, + {name: "int", containsString: false, build: buildIntArgs}, + } + argCounts := []int{1, 4, 8} + stringLens := []int{16, 1024, 4096} + + for _, apiCase := range apiCases { + apiCase := apiCase + b.Run(apiCase.name, func(b *testing.B) { + for _, profile := range profiles { + profile := profile + lens := []int{0} + if profile.containsString { + lens = stringLens + } + for _, argCount := range argCounts { + argCount := argCount + for _, strLen := range lens { + strLen := strLen + args := profile.build(argCount, strLen) + caseName := fmt.Sprintf("type-%s/count-%d", profile.name, argCount) + if profile.containsString { + caseName = fmt.Sprintf("%s/strlen-%d", caseName, strLen) + } + + b.Run(caseName, func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = apiCase.call(errPrototype, args) + } + GlobalE = err + }) + } + } + } + }) + } } func BenchmarkFormatArgFreeze(b *testing.B) { - errPrototype := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Bench")) - stringArg := "120120519090607" - - b.Run("FastGen-string", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.FastGen("Incorrect time value: '%s'", stringArg) - } - GlobalE = err - }) - - b.Run("FastGen-int", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.FastGen("Incorrect time value: '%d'", 120120519090607) - } - GlobalE = err - }) - - b.Run("GenWithStack-string", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.GenWithStack("Incorrect time value: '%s'", stringArg) - } - GlobalE = err - }) - - b.Run("GenWithStack-int", func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = errPrototype.GenWithStack("Incorrect time value: '%d'", 120120519090607) - } - GlobalE = err - }) + errPrototype := Normalize("bench", RFCCodeText("Internal:Bench")) + + apiCases := []struct { + name string + call func(errPrototype *Error, format string, args []interface{}) error + }{ + { + name: "FastGen", + call: func(errPrototype *Error, format string, args []interface{}) error { + return errPrototype.FastGen(format, args...) + }, + }, + { + name: "GenWithStack", + call: func(errPrototype *Error, format string, args []interface{}) error { + return errPrototype.GenWithStack(format, args...) + }, + }, + } + profiles := []argsProfile{ + {name: "string", containsString: true, build: buildStringArgs}, + {name: "int", containsString: false, build: buildIntArgs}, + {name: "mixed", containsString: true, build: buildMixedArgs}, + } + argCounts := []int{1, 4, 8} + stringLens := []int{16, 1024, 8192} + + for _, apiCase := range apiCases { + apiCase := apiCase + b.Run(apiCase.name, func(b *testing.B) { + for _, profile := range profiles { + profile := profile + lens := []int{0} + if profile.containsString { + lens = stringLens + } + for _, argCount := range argCounts { + argCount := argCount + format := benchmarkFormat(argCount) + for _, strLen := range lens { + strLen := strLen + args := profile.build(argCount, strLen) + caseName := fmt.Sprintf("type-%s/count-%d", profile.name, argCount) + if profile.containsString { + caseName = fmt.Sprintf("%s/strlen-%d", caseName, strLen) + } + + b.Run(caseName, func(b *testing.B) { + var err error + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = apiCase.call(errPrototype, format, args) + } + GlobalE = err + }) + } + } + } + }) + } } From 9fe26b1b0b1f428bf8d7bbf4c81d006c081b74fe Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Tue, 10 Mar 2026 12:35:25 +0800 Subject: [PATCH 56/59] change --- bench_test.go | 111 +++++++--------------------------------------- normalize.go | 30 ++++++++----- normalize_test.go | 45 ++++++++++++++----- 3 files changed, 70 insertions(+), 116 deletions(-) diff --git a/bench_test.go b/bench_test.go index b8864fee..61909336 100644 --- a/bench_test.go +++ b/bench_test.go @@ -113,8 +113,14 @@ type argsProfile struct { build func(count, stringLen int) []interface{} } -func buildStringArgs(count, stringLen int) []interface{} { - arg := strings.Repeat("x", stringLen) +type benchmarkHackedStr string + +func (s benchmarkHackedStr) FreezeStr() string { + return string(append([]byte(nil), s...)) +} + +func buildHackedStringArgs(count, stringLen int) []interface{} { + arg := benchmarkHackedStr(strings.Repeat("x", stringLen)) args := make([]interface{}, count) for i := range args { args[i] = arg @@ -122,44 +128,24 @@ func buildStringArgs(count, stringLen int) []interface{} { return args } -func buildIntArgs(count, _ int) []interface{} { +func buildPlainStringArgs(count, stringLen int) []interface{} { + arg := strings.Repeat("x", stringLen) args := make([]interface{}, count) for i := range args { - args[i] = i + args[i] = arg } return args } -func buildMixedArgs(count, stringLen int) []interface{} { - strArg := strings.Repeat("x", stringLen) +func buildIntArgs(count, _ int) []interface{} { args := make([]interface{}, count) for i := range args { - switch i % 4 { - case 0: - args[i] = strArg - case 1: - args[i] = i - case 2: - args[i] = i%2 == 0 - default: - args[i] = float64(i) + 0.25 - } + args[i] = i } return args } -func benchmarkFormat(count int) string { - if count <= 0 { - return "" - } - format := "%v" - for i := 1; i < count; i++ { - format += " %v" - } - return format -} - -func BenchmarkByArgsArgFreeze(b *testing.B) { +func BenchmarkByArgsHackedStrFreeze(b *testing.B) { errPrototype := Normalize("bench", RFCCodeText("Internal:Bench")) apiCases := []struct { @@ -174,10 +160,10 @@ func BenchmarkByArgsArgFreeze(b *testing.B) { }, } profiles := []argsProfile{ - {name: "string", containsString: true, build: buildStringArgs}, - {name: "int", containsString: false, build: buildIntArgs}, + {name: "plain", containsString: true, build: buildPlainStringArgs}, + {name: "hacked", containsString: true, build: buildHackedStringArgs}, } - argCounts := []int{1, 4, 8} + argCounts := []int{4} stringLens := []int{16, 1024, 4096} for _, apiCase := range apiCases { @@ -213,66 +199,3 @@ func BenchmarkByArgsArgFreeze(b *testing.B) { }) } } - -func BenchmarkFormatArgFreeze(b *testing.B) { - errPrototype := Normalize("bench", RFCCodeText("Internal:Bench")) - - apiCases := []struct { - name string - call func(errPrototype *Error, format string, args []interface{}) error - }{ - { - name: "FastGen", - call: func(errPrototype *Error, format string, args []interface{}) error { - return errPrototype.FastGen(format, args...) - }, - }, - { - name: "GenWithStack", - call: func(errPrototype *Error, format string, args []interface{}) error { - return errPrototype.GenWithStack(format, args...) - }, - }, - } - profiles := []argsProfile{ - {name: "string", containsString: true, build: buildStringArgs}, - {name: "int", containsString: false, build: buildIntArgs}, - {name: "mixed", containsString: true, build: buildMixedArgs}, - } - argCounts := []int{1, 4, 8} - stringLens := []int{16, 1024, 8192} - - for _, apiCase := range apiCases { - apiCase := apiCase - b.Run(apiCase.name, func(b *testing.B) { - for _, profile := range profiles { - profile := profile - lens := []int{0} - if profile.containsString { - lens = stringLens - } - for _, argCount := range argCounts { - argCount := argCount - format := benchmarkFormat(argCount) - for _, strLen := range lens { - strLen := strLen - args := profile.build(argCount, strLen) - caseName := fmt.Sprintf("type-%s/count-%d", profile.name, argCount) - if profile.containsString { - caseName = fmt.Sprintf("%s/strlen-%d", caseName, strLen) - } - - b.Run(caseName, func(b *testing.B) { - var err error - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err = apiCase.call(errPrototype, format, args) - } - GlobalE = err - }) - } - } - } - }) - } -} diff --git a/normalize.go b/normalize.go index d556c1b3..af67386c 100644 --- a/normalize.go +++ b/normalize.go @@ -42,6 +42,15 @@ type ErrCodeText string type ErrorID string type RFCErrorCode string +// HackedStr can provide a stable string snapshot for unsafe/ephemeral string sources. +// The method name intentionally uses FreezeStr instead of Clone so normal clone-style +// types are not accidentally treated as error-format arguments. +// During error construction, arguments implementing this interface are replaced +// with FreezeStr() so deferred error formatting does not observe later mutations. +type HackedStr interface { + FreezeStr() string +} + // Error is the 'prototype' of a type of errors. // Use DefineError to make a *Error: // var ErrUnavailable = errors.Normalize("Region %d is unavailable", errors.RFCCodeText("Unavailable")) @@ -142,21 +151,20 @@ func (e *Error) GetMsg() string { return e.message } -func freezeStringArgs(args []interface{}) []interface{} { +func freezeHackedStringArgs(args []interface{}) []interface{} { // This helper intentionally mutates the input slice in place. // It is only used inside error-construction paths where args are internal and should // not be reused by callers after passing into Gen*/FastGen* APIs. for i := range args { - strArg, ok := args[i].(string) + hackedArg, ok := args[i].(HackedStr) if !ok { continue } // TiDB may pass unsafe zero-copy strings (for example from chunk buffers) as error args. // Error message rendering is deferred until GetMsg/Error, so without freezing here we may // observe later writes and print a different value than the one used when creating the error. - // Copying at every call site in TiDB is more expensive; freezing in this central path keeps - // the copy cost only on error construction. - args[i] = string(append([]byte(nil), strArg...)) + // Freezing in this central path keeps the copy cost only on error construction. + args[i] = hackedArg.FreezeStr() } return args } @@ -182,7 +190,7 @@ func (e *Error) GenWithStack(format string, args ...interface{}) error { // TODO: RedactErrorArg err := *e err.message = format - err.args = freezeStringArgs(args) + err.args = freezeHackedStringArgs(args) err.fillLineAndFile(1) return AddStack(&err) } @@ -191,7 +199,7 @@ func (e *Error) GenWithStack(format string, args ...interface{}) error { func (e *Error) GenWithStackByArgs(args ...interface{}) error { RedactErrorArg(args, e.redactArgsPos) err := *e - err.args = freezeStringArgs(args) + err.args = freezeHackedStringArgs(args) err.fillLineAndFile(1) return AddStack(&err) } @@ -202,7 +210,7 @@ func (e *Error) FastGen(format string, args ...interface{}) error { // TODO: RedactErrorArg err := *e err.message = format - err.args = freezeStringArgs(args) + err.args = freezeHackedStringArgs(args) return SuspendStack(&err) } @@ -211,7 +219,7 @@ func (e *Error) FastGen(format string, args ...interface{}) error { func (e *Error) FastGenByArgs(args ...interface{}) error { RedactErrorArg(args, e.redactArgsPos) err := *e - err.args = freezeStringArgs(args) + err.args = freezeHackedStringArgs(args) return SuspendStack(&err) } @@ -332,7 +340,7 @@ func (e *Error) FastGenWithCause(args ...interface{}) error { if e.cause != nil { err.message = e.cause.Error() } - err.args = freezeStringArgs(args) + err.args = freezeHackedStringArgs(args) return SuspendStack(&err) } @@ -341,7 +349,7 @@ func (e *Error) GenWithStackByCause(args ...interface{}) error { if e.cause != nil { err.message = e.cause.Error() } - err.args = freezeStringArgs(args) + err.args = freezeHackedStringArgs(args) err.fillLineAndFile(1) return AddStack(&err) } diff --git a/normalize_test.go b/normalize_test.go index 8b9e46c0..2abe6874 100644 --- a/normalize_test.go +++ b/normalize_test.go @@ -7,6 +7,14 @@ import ( "unsafe" ) +type hackedStringArg struct { + raw []byte +} + +func (h hackedStringArg) FreezeStr() string { + return string(append([]byte(nil), h.raw...)) +} + func errorMatches(t *testing.T, err error, re string) { if err == nil && re != "" { t.Errorf("nil error doesn't match %s", re) @@ -51,13 +59,28 @@ func TestRedactFormatter(t *testing.T) { } } -func TestGenWithStackByArgsFreezeStringArg(t *testing.T) { +func TestGenWithStackByArgsNoCloneByDefault(t *testing.T) { errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) origin := []byte("120120519090607") arg := *(*string)(unsafe.Pointer(&origin)) err := errTest.GenWithStackByArgs(arg) + copy(origin, "1 1:1:1.0000027") + got := err.(*withStack).error.(*Error).GetMsg() + want := "Incorrect time value: '1 1:1:1.0000027'" + if got != want { + t.Fatalf("message should track source bytes by default, got %q, want %q", got, want) + } +} + +func TestGenWithStackByArgsFreezeHackedStringArg(t *testing.T) { + errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) + + origin := []byte("120120519090607") + arg := hackedStringArg{raw: origin} + err := errTest.GenWithStackByArgs(arg) + copy(origin, "1 1:1:1.0000027") got := err.(*withStack).error.(*Error).GetMsg() want := "Incorrect time value: '120120519090607'" @@ -66,11 +89,11 @@ func TestGenWithStackByArgsFreezeStringArg(t *testing.T) { } } -func TestFastGenByArgsFreezeStringArg(t *testing.T) { +func TestFastGenByArgsFreezeHackedStringArg(t *testing.T) { errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) origin := []byte("120120519090607") - arg := *(*string)(unsafe.Pointer(&origin)) + arg := hackedStringArg{raw: origin} err := errTest.FastGenByArgs(arg) copy(origin, "1 1:1:1.0000027") @@ -81,11 +104,11 @@ func TestFastGenByArgsFreezeStringArg(t *testing.T) { } } -func TestGenWithStackFreezeStringArg(t *testing.T) { +func TestGenWithStackFreezeHackedStringArg(t *testing.T) { errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) origin := []byte("120120519090607") - arg := *(*string)(unsafe.Pointer(&origin)) + arg := hackedStringArg{raw: origin} err := errTest.GenWithStack("Incorrect time value: '%s'", arg) copy(origin, "1 1:1:1.0000027") @@ -96,11 +119,11 @@ func TestGenWithStackFreezeStringArg(t *testing.T) { } } -func TestFastGenFreezeStringArg(t *testing.T) { +func TestFastGenFreezeHackedStringArg(t *testing.T) { errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) origin := []byte("120120519090607") - arg := *(*string)(unsafe.Pointer(&origin)) + arg := hackedStringArg{raw: origin} err := errTest.FastGen("Incorrect time value: '%s'", arg) copy(origin, "1 1:1:1.0000027") @@ -111,11 +134,11 @@ func TestFastGenFreezeStringArg(t *testing.T) { } } -func TestGenWithStackByCauseFreezeStringArg(t *testing.T) { +func TestGenWithStackByCauseFreezeHackedStringArg(t *testing.T) { errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) origin := []byte("120120519090607") - arg := *(*string)(unsafe.Pointer(&origin)) + arg := hackedStringArg{raw: origin} err := errTest.GenWithStackByCause(arg) copy(origin, "1 1:1:1.0000027") @@ -126,11 +149,11 @@ func TestGenWithStackByCauseFreezeStringArg(t *testing.T) { } } -func TestFastGenWithCauseFreezeStringArg(t *testing.T) { +func TestFastGenWithCauseFreezeHackedStringArg(t *testing.T) { errTest := Normalize("Incorrect time value: '%s'", RFCCodeText("Internal:Test")) origin := []byte("120120519090607") - arg := *(*string)(unsafe.Pointer(&origin)) + arg := hackedStringArg{raw: origin} err := errTest.FastGenWithCause(arg) copy(origin, "1 1:1:1.0000027") From de63e653604a6df54f918d026deec266abae7bd4 Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Tue, 10 Mar 2026 12:50:15 +0800 Subject: [PATCH 57/59] change --- bench_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bench_test.go b/bench_test.go index 61909336..c8372760 100644 --- a/bench_test.go +++ b/bench_test.go @@ -163,8 +163,8 @@ func BenchmarkByArgsHackedStrFreeze(b *testing.B) { {name: "plain", containsString: true, build: buildPlainStringArgs}, {name: "hacked", containsString: true, build: buildHackedStringArgs}, } - argCounts := []int{4} - stringLens := []int{16, 1024, 4096} + argCounts := []int{1, 4, 8} + stringLens := []int{16, 1024} for _, apiCase := range apiCases { apiCase := apiCase @@ -179,7 +179,7 @@ func BenchmarkByArgsHackedStrFreeze(b *testing.B) { argCount := argCount for _, strLen := range lens { strLen := strLen - args := profile.build(argCount, strLen) + templateArgs := profile.build(argCount, strLen) caseName := fmt.Sprintf("type-%s/count-%d", profile.name, argCount) if profile.containsString { caseName = fmt.Sprintf("%s/strlen-%d", caseName, strLen) @@ -187,8 +187,10 @@ func BenchmarkByArgsHackedStrFreeze(b *testing.B) { b.Run(caseName, func(b *testing.B) { var err error + args := make([]interface{}, len(templateArgs)) b.ReportAllocs() for i := 0; i < b.N; i++ { + copy(args, templateArgs) err = apiCase.call(errPrototype, args) } GlobalE = err From 306e305bcf414e6b2ff3247bee1ec7f682aec022 Mon Sep 17 00:00:00 2001 From: D3Hunter Date: Fri, 8 May 2026 13:47:01 +0800 Subject: [PATCH 58/59] fix named error verbose stack formatting (#83) --- normalize.go | 21 +++++++++++++++++++++ normalize_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/normalize.go b/normalize.go index af67386c..e5d83560 100644 --- a/normalize.go +++ b/normalize.go @@ -92,6 +92,7 @@ type Error struct { } var _ messenger = (*Error)(nil) +var _ fmt.Formatter = (*Error)(nil) // Code returns the numeric code of this error. // ID() will return textual error if there it is, @@ -144,6 +145,26 @@ func (e *Error) Error() string { return fmt.Sprintf("[%s]%s", e.RFCCode(), e.GetMsg()) } +func (e *Error) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + if e != nil && e.cause != nil { + fmt.Fprintf(s, "%+v\n", e.cause) + fmt.Fprintf(s, "[%s]%s", e.RFCCode(), e.GetMsg()) + return + } + fmt.Fprint(s, e.Error()) + return + } + fallthrough + case 's': + fmt.Fprint(s, e.Error()) + case 'q': + fmt.Fprintf(s, "%q", e.Error()) + } +} + func (e *Error) GetMsg() string { if len(e.args) > 0 { return fmt.Sprintf(e.message, e.args...) diff --git a/normalize_test.go b/normalize_test.go index 2abe6874..89557c1c 100644 --- a/normalize_test.go +++ b/normalize_test.go @@ -1,8 +1,10 @@ package errors import ( + stderrors "errors" "fmt" "regexp" + "strings" "testing" "unsafe" ) @@ -42,6 +44,39 @@ func TestCauseInErrorMessage(t *testing.T) { errorMatches(t, notWrapped, `^\[Internal:Test\]everything is alright$`) } +func TestWrappedNamedErrorGenWithStackByArgsFormatsCauseStack(t *testing.T) { + errTest := Normalize("named error: %s", RFCCodeText("Internal:Test")) + + err := errTest.Wrap(New("cause error")).GenWithStackByArgs("wrapped") + + if _, ok := err.(fmt.Formatter); !ok { + t.Fatalf("zap requires fmt.Formatter to emit errorVerbose for stackful errors, got %T", err) + } + + formatted := fmt.Sprintf("%+v", err) + if !strings.Contains(formatted, "github.com/pingcap/errors.TestWrappedNamedErrorGenWithStackByArgsFormatsCauseStack") { + t.Fatalf("formatted error does not contain the wrapped cause stack:\n%s", formatted) + } + if !strings.Contains(formatted, "[Internal:Test]named error: wrapped") { + t.Fatalf("formatted error does not contain named error context:\n%s", formatted) + } +} + +func TestWrappedNamedErrorFormatsStacklessCause(t *testing.T) { + errTest := Normalize("named error: %s", RFCCodeText("Internal:Test")) + + err := errTest.Wrap(stderrors.New("plain cause")).GenWithStackByArgs("wrapped") + + formatted := fmt.Sprintf("%+v", err) + wantPrefix := "plain cause\n[Internal:Test]named error: wrapped\n" + if !strings.HasPrefix(formatted, wantPrefix) { + t.Fatalf("unexpected formatted error prefix:\ngot: %q\nwant prefix: %q", formatted, wantPrefix) + } + if !strings.Contains(formatted, "github.com/pingcap/errors.TestWrappedNamedErrorFormatsStacklessCause") { + t.Fatalf("formatted error does not contain the generated stack:\n%s", formatted) + } +} + func TestRedactFormatter(t *testing.T) { rv := 34.03498 v := &redactFormatter{rv} From 3697ad564b4342c5cbd0b95fc4032178aead9fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Eeden?= Date: Sat, 23 May 2026 03:31:11 +0300 Subject: [PATCH 59/59] Update go.mod (#71) * Update go.mod * Run go fix for Go 1.25 * fixup --- bench_test.go | 26 +++++++++++--------------- errdoc-gen/main.go | 11 +++++------ errors.go | 4 ++-- errors_test.go | 2 +- format_test.go | 6 +++--- go.mod | 8 +++++++- go.sum | 10 ---------- juju_adaptor.go | 14 +++++++------- normalize.go | 22 +++++++++++----------- stack.go | 2 +- 10 files changed, 48 insertions(+), 57 deletions(-) diff --git a/bench_test.go b/bench_test.go index c8372760..b8ad7a02 100644 --- a/bench_test.go +++ b/bench_test.go @@ -23,7 +23,7 @@ func yesErrors(at, depth int) error { // GlobalE is an exported global to store the result of benchmark results, // preventing the compiler from optimising the benchmark functions away. -var GlobalE interface{} +var GlobalE any func BenchmarkErrors(b *testing.B) { type run struct { @@ -110,7 +110,7 @@ func BenchmarkStackFormatting(b *testing.B) { type argsProfile struct { name string containsString bool - build func(count, stringLen int) []interface{} + build func(count, stringLen int) []any } type benchmarkHackedStr string @@ -119,26 +119,26 @@ func (s benchmarkHackedStr) FreezeStr() string { return string(append([]byte(nil), s...)) } -func buildHackedStringArgs(count, stringLen int) []interface{} { +func buildHackedStringArgs(count, stringLen int) []any { arg := benchmarkHackedStr(strings.Repeat("x", stringLen)) - args := make([]interface{}, count) + args := make([]any, count) for i := range args { args[i] = arg } return args } -func buildPlainStringArgs(count, stringLen int) []interface{} { +func buildPlainStringArgs(count, stringLen int) []any { arg := strings.Repeat("x", stringLen) - args := make([]interface{}, count) + args := make([]any, count) for i := range args { args[i] = arg } return args } -func buildIntArgs(count, _ int) []interface{} { - args := make([]interface{}, count) +func buildIntArgs(count, _ int) []any { + args := make([]any, count) for i := range args { args[i] = i } @@ -150,11 +150,11 @@ func BenchmarkByArgsHackedStrFreeze(b *testing.B) { apiCases := []struct { name string - call func(errPrototype *Error, args []interface{}) error + call func(errPrototype *Error, args []any) error }{ { name: "FastGenByArgs", - call: func(errPrototype *Error, args []interface{}) error { + call: func(errPrototype *Error, args []any) error { return errPrototype.FastGenByArgs(args...) }, }, @@ -167,18 +167,14 @@ func BenchmarkByArgsHackedStrFreeze(b *testing.B) { stringLens := []int{16, 1024} for _, apiCase := range apiCases { - apiCase := apiCase b.Run(apiCase.name, func(b *testing.B) { for _, profile := range profiles { - profile := profile lens := []int{0} if profile.containsString { lens = stringLens } for _, argCount := range argCounts { - argCount := argCount for _, strLen := range lens { - strLen := strLen templateArgs := profile.build(argCount, strLen) caseName := fmt.Sprintf("type-%s/count-%d", profile.name, argCount) if profile.containsString { @@ -187,7 +183,7 @@ func BenchmarkByArgsHackedStrFreeze(b *testing.B) { b.Run(caseName, func(b *testing.B) { var err error - args := make([]interface{}, len(templateArgs)) + args := make([]any, len(templateArgs)) b.ReportAllocs() for i := 0; i < b.N; i++ { copy(args, templateArgs) diff --git a/errdoc-gen/main.go b/errdoc-gen/main.go index 2b4a63a6..a5e4df33 100644 --- a/errdoc-gen/main.go +++ b/errdoc-gen/main.go @@ -22,6 +22,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "strings" "text/template" ) @@ -42,11 +43,11 @@ func init() { flag.BoolVar(&opt.retainCode, "retain-code", false, "Retain the generated code when generator exit") } -func log(format string, args ...interface{}) { +func log(format string, args ...any) { fmt.Println(fmt.Sprintf(format, args...)) } -func fatal(format string, args ...interface{}) { +func fatal(format string, args ...any) { log(format, args...) os.Exit(1) } @@ -253,10 +254,8 @@ func errdoc(source, module string) ([]*errDecl, error) { return err } if info.IsDir() { - for i := range ignored { - if ignored[i] == path { - return filepath.SkipDir - } + if slices.Contains(ignored, path) { + return filepath.SkipDir } return nil } diff --git a/errors.go b/errors.go index 9a2ad45c..2ac469b4 100644 --- a/errors.go +++ b/errors.go @@ -109,7 +109,7 @@ func New(message string) error { // Errorf formats according to a format specifier and returns the string // as a value that satisfies error. // Errorf also records the stack trace at the point it was called. -func Errorf(format string, args ...interface{}) error { +func Errorf(format string, args ...any) error { return &fundamental{ msg: fmt.Sprintf(format, args...), stack: callers(), @@ -254,7 +254,7 @@ func Wrap(err error, message string) error { // // For most use cases this is deprecated in favor of Annotatef. // Annotatef avoids creating duplicate stack traces. -func Wrapf(err error, format string, args ...interface{}) error { +func Wrapf(err error, format string, args ...any) error { if err == nil { return nil } diff --git a/errors_test.go b/errors_test.go index 7e290eb1..7a26dc8f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -135,7 +135,7 @@ func TestWrapf(t *testing.T) { } for _, tt := range tests { - got := Annotatef(tt.err, tt.message).Error() + got := Annotatef(tt.err, "%s", tt.message).Error() if got != tt.want { t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) } diff --git a/format_test.go b/format_test.go index a1af931a..10c9b155 100644 --- a/format_test.go +++ b/format_test.go @@ -358,7 +358,7 @@ func TestFormatWithMessage(t *testing.T) { } }*/ -func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { +func testFormatRegexp(t *testing.T, n int, arg any, format, want string) { t.Helper() got := fmt.Sprintf(format, arg) gotLines := strings.SplitN(got, "\n", -1) @@ -405,7 +405,7 @@ func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { wasStack := false lines := map[string]bool{} // already found lines - for _, l := range strings.Split(input, "\n") { + for l := range strings.SplitSeq(input, "\n") { isStackLine := stackLineR.MatchString(l) switch { @@ -451,7 +451,7 @@ func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { return blocks, nil } -func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) { +func testFormatCompleteCompare(t *testing.T, n int, arg any, format string, want []string, detectStackBoundaries bool) { gotStr := fmt.Sprintf(format, arg) got, err := parseBlocks(gotStr, detectStackBoundaries) diff --git a/go.mod b/go.mod index 03be1e6e..8423b18f 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,14 @@ module github.com/pingcap/errors -go 1.14 +go 1.25 require ( github.com/stretchr/testify v1.11.1 go.uber.org/atomic v1.11.0 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 985ed063..8d2f3332 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,12 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/juju_adaptor.go b/juju_adaptor.go index 3c2dd4ec..aa20182a 100644 --- a/juju_adaptor.go +++ b/juju_adaptor.go @@ -36,7 +36,7 @@ func Annotate(err error, message string) error { } // Annotatef adds a message and ensures there is a stack trace. -func Annotatef(err error, format string, args ...interface{}) error { +func Annotatef(err error, format string, args ...any) error { if err == nil { return nil } @@ -68,7 +68,7 @@ func NewNoStackError(msg string) error { // NewNoStackErrorf creates error with error stack and formats according // to a format specifier and returns the string as a value that satisfies error. -func NewNoStackErrorf(format string, args ...interface{}) error { +func NewNoStackErrorf(format string, args ...any) error { return &fundamental{ msg: fmt.Sprintf(format, args...), stack: &emptyStack, @@ -127,22 +127,22 @@ func IsNotFound(err error) bool { } // NotFoundf represents an error with not found message. -func NotFoundf(format string, args ...interface{}) error { +func NotFoundf(format string, args ...any) error { return Errorf(format+" not found", args...) } // BadRequestf represents an error with bad request message. -func BadRequestf(format string, args ...interface{}) error { +func BadRequestf(format string, args ...any) error { return Errorf(format+" bad request", args...) } // NotSupportedf represents an error with not supported message. -func NotSupportedf(format string, args ...interface{}) error { +func NotSupportedf(format string, args ...any) error { return Errorf(format+" not supported", args...) } // NotValidf represents an error with not valid message. -func NotValidf(format string, args ...interface{}) error { +func NotValidf(format string, args ...any) error { return Errorf(format+" not valid", args...) } @@ -152,7 +152,7 @@ func IsAlreadyExists(err error) bool { } // AlreadyExistsf represents an error with already exists message. -func AlreadyExistsf(format string, args ...interface{}) error { +func AlreadyExistsf(format string, args ...any) error { return Errorf(format+" already exists", args...) } diff --git a/normalize.go b/normalize.go index e5d83560..31f69426 100644 --- a/normalize.go +++ b/normalize.go @@ -86,7 +86,7 @@ type Error struct { redactArgsPos []int // Cause is used to warp some third party error. cause error - args []interface{} + args []any file string line int } @@ -130,7 +130,7 @@ func (e *Error) MessageTemplate() string { } // Args returns the message arguments of this error. -func (e *Error) Args() []interface{} { +func (e *Error) Args() []any { return e.args } @@ -172,7 +172,7 @@ func (e *Error) GetMsg() string { return e.message } -func freezeHackedStringArgs(args []interface{}) []interface{} { +func freezeHackedStringArgs(args []any) []any { // This helper intentionally mutates the input slice in place. // It is only used inside error-construction paths where args are internal and should // not be reused by callers after passing into Gen*/FastGen* APIs. @@ -207,7 +207,7 @@ func (e *Error) fillLineAndFile(skip int) { } // GenWithStack generates a new *Error with the same class and code, and a new formatted message. -func (e *Error) GenWithStack(format string, args ...interface{}) error { +func (e *Error) GenWithStack(format string, args ...any) error { // TODO: RedactErrorArg err := *e err.message = format @@ -217,7 +217,7 @@ func (e *Error) GenWithStack(format string, args ...interface{}) error { } // GenWithStackByArgs generates a new *Error with the same class and code, and new arguments. -func (e *Error) GenWithStackByArgs(args ...interface{}) error { +func (e *Error) GenWithStackByArgs(args ...any) error { RedactErrorArg(args, e.redactArgsPos) err := *e err.args = freezeHackedStringArgs(args) @@ -227,7 +227,7 @@ func (e *Error) GenWithStackByArgs(args ...interface{}) error { // FastGen generates a new *Error with the same class and code, and a new formatted message. // This will not call runtime.Caller to get file and line. -func (e *Error) FastGen(format string, args ...interface{}) error { +func (e *Error) FastGen(format string, args ...any) error { // TODO: RedactErrorArg err := *e err.message = format @@ -237,7 +237,7 @@ func (e *Error) FastGen(format string, args ...interface{}) error { // FastGen generates a new *Error with the same class and code, and a new arguments. // This will not call runtime.Caller to get file and line. -func (e *Error) FastGenByArgs(args ...interface{}) error { +func (e *Error) FastGenByArgs(args ...any) error { RedactErrorArg(args, e.redactArgsPos) err := *e err.args = freezeHackedStringArgs(args) @@ -267,7 +267,7 @@ func (e *Error) NotEqual(err error) bool { } // RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. -func RedactErrorArg(args []interface{}, position []int) { +func RedactErrorArg(args []any, position []int) { switch RedactLogEnabled.Load() { case RedactLogEnable: for _, pos := range position { @@ -356,7 +356,7 @@ func (e *Error) Cause() error { return root } -func (e *Error) FastGenWithCause(args ...interface{}) error { +func (e *Error) FastGenWithCause(args ...any) error { err := *e if e.cause != nil { err.message = e.cause.Error() @@ -365,7 +365,7 @@ func (e *Error) FastGenWithCause(args ...interface{}) error { return SuspendStack(&err) } -func (e *Error) GenWithStackByCause(args ...interface{}) error { +func (e *Error) GenWithStackByCause(args ...any) error { err := *e if e.cause != nil { err.message = e.cause.Error() @@ -409,7 +409,7 @@ func Normalize(message string, opts ...NormalizeOption) *Error { } type redactFormatter struct { - arg interface{} + arg any } func (e *redactFormatter) Format(f fmt.State, verb rune) { diff --git a/stack.go b/stack.go index a981f491..2263ba37 100644 --- a/stack.go +++ b/stack.go @@ -189,7 +189,7 @@ func (s *stack) Format(st fmt.State, verb rune) { func (s *stack) StackTrace() StackTrace { f := make([]Frame, len(*s)) - for i := 0; i < len(f); i++ { + for i := range f { f[i] = Frame((*s)[i]) } return f