| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- package errors
- import (
- "bytes"
- "fmt"
- "io"
- "reflect"
- "runtime"
- "strings"
- "testing"
- )
- func BenchmarkStackFormat(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- func() {
- defer func() {
- err := recover()
- if err != 'a' {
- b.Fatal(err)
- }
- e := Errorf("hi")
- _ = string(e.Stack())
- }()
- a()
- }()
- }
- }
- func TestStackFormat(t *testing.T) {
- defer func() {
- err := recover()
- if err != 'a' {
- t.Fatal(err)
- }
- e, expected := Errorf("hi"), callers()
- bs := [][]uintptr{e.stack, expected}
- if err := compareStacks(bs[0], bs[1]); err != nil {
- t.Errorf("Stack didn't match")
- t.Errorf(err.Error())
- }
- stack := string(e.Stack())
- if !strings.Contains(stack, "a: b(5)") {
- t.Errorf("Stack trace does not contain source line: 'a: b(5)'")
- t.Errorf(stack)
- }
- if !strings.Contains(stack, "error_test.go:") {
- t.Errorf("Stack trace does not contain file name: 'error_test.go:'")
- t.Errorf(stack)
- }
- }()
- a()
- }
- func TestSkipWorks(t *testing.T) {
- defer func() {
- err := recover()
- if err != 'a' {
- t.Fatal(err)
- }
- bs := [][]uintptr{Wrap("hi", 2).stack, callersSkip(2)}
- if err := compareStacks(bs[0], bs[1]); err != nil {
- t.Errorf("Stack didn't match")
- t.Errorf(err.Error())
- }
- }()
- a()
- }
- func TestNew(t *testing.T) {
- err := New("foo")
- if err.Error() != "foo" {
- t.Errorf("Wrong message")
- }
- err = New(fmt.Errorf("foo"))
- if err.Error() != "foo" {
- t.Errorf("Wrong message")
- }
- bs := [][]uintptr{New("foo").stack, callers()}
- if err := compareStacks(bs[0], bs[1]); err != nil {
- t.Errorf("Stack didn't match")
- t.Errorf(err.Error())
- }
- if err.ErrorStack() != err.TypeName()+" "+err.Error()+"\n"+string(err.Stack()) {
- t.Errorf("ErrorStack is in the wrong format")
- }
- }
- // This test should work for any go version
- func TestIs(t *testing.T) {
- if Is(nil, io.EOF) {
- t.Errorf("nil is an error")
- }
- if !Is(io.EOF, io.EOF) {
- t.Errorf("io.EOF is not io.EOF")
- }
- if !Is(io.EOF, New(io.EOF)) {
- t.Errorf("io.EOF is not New(io.EOF)")
- }
- if !Is(New(io.EOF), New(io.EOF)) {
- t.Errorf("New(io.EOF) is not New(io.EOF)")
- }
- if Is(io.EOF, fmt.Errorf("io.EOF")) {
- t.Errorf("io.EOF is fmt.Errorf")
- }
- }
- func TestWrapError(t *testing.T) {
- e := func() error {
- return Wrap("hi", 1)
- }()
- if e.Error() != "hi" {
- t.Errorf("Constructor with a string failed")
- }
- if Wrap(fmt.Errorf("yo"), 0).Error() != "yo" {
- t.Errorf("Constructor with an error failed")
- }
- if Wrap(e, 0) != e {
- t.Errorf("Constructor with an Error failed")
- }
- if Wrap(nil, 0) != nil {
- t.Errorf("Constructor with nil failed")
- }
- }
- func TestWrapPrefixError(t *testing.T) {
- e := func() error {
- return WrapPrefix("hi", "prefix", 1)
- }()
- if e.Error() != "prefix: hi" {
- t.Errorf("Constructor with a string failed")
- }
- if WrapPrefix(fmt.Errorf("yo"), "prefix", 0).Error() != "prefix: yo" {
- t.Errorf("Constructor with an error failed")
- }
- prefixed := WrapPrefix(e, "prefix", 0)
- original := e.(*Error)
- if prefixed.Err != original.Err || !reflect.DeepEqual(prefixed.stack, original.stack) || !reflect.DeepEqual(prefixed.frames, original.frames) || prefixed.Error() != "prefix: prefix: hi" {
- t.Errorf("Constructor with an Error failed")
- }
- if original.Error() == prefixed.Error() {
- t.Errorf("WrapPrefix changed the original error")
- }
- if WrapPrefix(nil, "prefix", 0) != nil {
- t.Errorf("Constructor with nil failed")
- }
- if !strings.HasSuffix(original.StackFrames()[0].File, "error_test.go") || strings.HasSuffix(original.StackFrames()[1].File, "error_test.go") {
- t.Errorf("Skip failed")
- }
- }
- func ExampleErrorf(x int) (int, error) {
- if x%2 == 1 {
- return 0, Errorf("can only halve even numbers, got %d", x)
- }
- return x / 2, nil
- }
- func ExampleWrapError() (error, error) {
- // Wrap io.EOF with the current stack-trace and return it
- return nil, Wrap(io.EOF, 0)
- }
- func ExampleWrapError_skip() {
- defer func() {
- if err := recover(); err != nil {
- // skip 1 frame (the deferred function) and then return the wrapped err
- err = Wrap(err, 1)
- }
- }()
- }
- func ExampleIs(reader io.Reader, buff []byte) {
- _, err := reader.Read(buff)
- if Is(err, io.EOF) {
- return
- }
- }
- func ExampleNew(UnexpectedEOF error) error {
- // calling New attaches the current stacktrace to the existing UnexpectedEOF error
- return New(UnexpectedEOF)
- }
- func ExampleWrap() error {
- if err := recover(); err != nil {
- return Wrap(err, 1)
- }
- return a()
- }
- func ExampleError_Error(err error) {
- fmt.Println(err.Error())
- }
- func ExampleError_ErrorStack(err error) {
- fmt.Println(err.(*Error).ErrorStack())
- }
- func ExampleError_Stack(err *Error) {
- fmt.Println(err.Stack())
- }
- func ExampleError_TypeName(err *Error) {
- fmt.Println(err.TypeName(), err.Error())
- }
- func ExampleError_StackFrames(err *Error) {
- for _, frame := range err.StackFrames() {
- fmt.Println(frame.File, frame.LineNumber, frame.Package, frame.Name)
- }
- }
- func a() error {
- b(5)
- return nil
- }
- func b(i int) {
- c()
- }
- func c() {
- panic('a')
- }
- // compareStacks will compare a stack created using the errors package (actual)
- // with a reference stack created with the callers function (expected). The
- // first entry is not compared since the actual and expected stacks cannot
- // be created at the exact same program counter position so the first entry
- // will always differ somewhat. Returns nil if the stacks are equal enough and
- // an error containing a detailed error message otherwise.
- func compareStacks(actual, expected []uintptr) error {
- if len(actual) != len(expected) {
- return stackCompareError("Stacks does not have equal length", actual, expected)
- }
- for i, pc := range actual {
- if i != 0 && pc != expected[i] {
- return stackCompareError(fmt.Sprintf("Stacks does not match entry %d (and maybe others)", i), actual, expected)
- }
- }
- return nil
- }
- func stackCompareError(msg string, actual, expected []uintptr) error {
- return fmt.Errorf("%s\nActual stack trace:\n%s\nExpected stack trace:\n%s", msg, readableStackTrace(actual), readableStackTrace(expected))
- }
- func callers() []uintptr {
- return callersSkip(1)
- }
- func callersSkip(skip int) []uintptr {
- callers := make([]uintptr, MaxStackDepth)
- length := runtime.Callers(skip+2, callers[:])
- return callers[:length]
- }
- func readableStackTrace(callers []uintptr) string {
- var result bytes.Buffer
- frames := callersToFrames(callers)
- for _, frame := range frames {
- result.WriteString(fmt.Sprintf("%s:%d (%#x)\n\t%s\n", frame.File, frame.Line, frame.PC, frame.Function))
- }
- return result.String()
- }
- func callersToFrames(callers []uintptr) []runtime.Frame {
- frames := make([]runtime.Frame, 0, len(callers))
- framesPtr := runtime.CallersFrames(callers)
- for {
- frame, more := framesPtr.Next()
- frames = append(frames, frame)
- if !more {
- return frames
- }
- }
- }
|