Coder Social home page Coder Social logo

errors's Introduction

go-errors/errors

Build Status

Package errors adds stacktrace support to errors in go.

This is particularly useful when you want to understand the state of execution when an error was returned unexpectedly.

It provides the type *Error which implements the standard golang error interface, so you can use this library interchangeably with code that is expecting a normal error return.

Usage

Full documentation is available on godoc, but here's a simple example:

package crashy

import "github.com/go-errors/errors"

var Crashed = errors.Errorf("oh dear")

func Crash() error {
    return errors.New(Crashed)
}

This can be called as follows:

package main

import (
    "crashy"
    "fmt"
    "github.com/go-errors/errors"
)

func main() {
    err := crashy.Crash()
    if err != nil {
        if errors.Is(err, crashy.Crashed) {
            fmt.Println(err.(*errors.Error).ErrorStack())
        } else {
            panic(err)
        }
    }
}

Meta-fu

This package was original written to allow reporting to Bugsnag from bugsnag-go, but after I found similar packages by Facebook and Dropbox, it was moved to one canonical location so everyone can benefit.

This package is licensed under the MIT license, see LICENSE.MIT for details.

Changelog

  • v1.1.0 updated to use go1.13's standard-library errors.Is method instead of == in errors.Is
  • v1.2.0 added errors.As from the standard library.
  • v1.3.0 BREAKING updated error methods to return error instead of *Error.

Code that needs access to the underlying *Error can use the new errors.AsError(e)

  // before
  errors.New(err).ErrorStack()
  // after
.  errors.AsError(errors.Wrap(err)).ErrorStack()
  • v1.4.0 BREAKING v1.4.0 reverted all changes from v1.3.0 and is identical to v1.2.0
  • v1.4.1 no code change, but now without an unnecessary cover.out file.
  • v1.4.2 performance improvement to ErrorStack() to avoid unnecessary work #40
  • v1.5.0 add errors.Join() and errors.Unwrap() copying the stdlib #40
  • v1.5.1 fix build on go1.13..go1.19 (broken by adding Join and Unwrap with wrong build constraints)

errors's People

Contributors

adrienkohlbecker avatar conradirwin avatar gabrielf avatar kishaningithub avatar kishoresenji avatar lovromazgon avatar meetme2meat avatar stevenpelley avatar testwill avatar thiht avatar titouanfreville avatar waschik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

errors's Issues

Design flaw: Error is a struct, but error is an interface

Because Error is a struct, your library heavily encourages passing *Error around. However, a nil *Error can be assigned to an error and the interface value will test as non-nil, leading to "no error" being false detected as an error condition, when a *Error has thoughtlessly been coerced to an error.

Making this problem worse is the fact that most golang libraries still use the built-in error interface, and in a given block, I often call a mixture of other peoples' libraries and my own code (that is using Error extensively). Because of the err := convention that pervades Go, if I'm not very careful, I can accidentally change the type identity of an *Error variable to error by := adding code.

See the example code below for an illustration of the issue. In older versions of go-errors this panics half the time because I try to call .Error() on a nil *Error, and in newer versions it prints <nil> half the time -- which is possibly even more confusing.

I know that none of this is your fault, but it seems to me that these issues all stem from Error being a struct; if it were an interface instead, this whole class of bugs would be eliminated.

Thoughts?

package main

import "fmt"
import "math/rand"
import "strconv"
import "time"
import "github.com/go-errors/errors"

func internal() *errors.Error {
    if rand.Intn(2) > 0 {
      return errors.New("That's the way the cookie crumbles")
    } else {
      return nil
    }
}

func main() {
    rand.Seed(time.Now().UnixNano())

    // Image this code was written on Wednesday
    i, err := strconv.Atoi("10")
    if err != nil {
    fmt.Println("Oh noes", err.Error())
    return
    }

    // Imagine this code was written on Monday
    err = internal()
    if err != nil {
    fmt.Println("Oh noes", err.Error())
    return
    }

    fmt.Println("i =", i)
}

PR #33 broke compatibility

Yeah, so Wrap() used to return *Error and now returns error. This will break a lot of people. Regardless of the merits of this change, in principal, this was a backwards-incompatible change that should not have been made. Now, your package is breaking all of my packages upon upgrade, and all of the outside projects that are consuming them, and doubtless more well beyond just my scope.

2ae1e44

Error output (with a replace clause in the go.mod):

../go-logging/v2/log.go:356:14: cannot use "github.com/go-errors/errors".Wrap(err, 2) (type error) as type *"github.com/go-errors/errors".Error in assignment: need type assertion
../go-logging/v2/log.go:483:20: cannot use "github.com/go-errors/errors".Wrap(err, 1) (type error) as type *"github.com/go-errors/errors".Error in return argument: need type assertion
../go-logging/v2/log.go:489:20: cannot use "github.com/go-errors/errors".Wrap(err, 1) (type error) as type *"github.com/go-errors/errors".Error in return argument: need type assertion

I would request that you revert this change, and move this feature, if still required, to the next major release in order to maintain API compatibility with all current integrations (per the Go Compatibility Promise).

Panics aren't handled correctly

The docs for runtime.Callers() ( https://golang.org/pkg/runtime/#Callers ) says:

To look up the file and line number of the call itself, use pc[i]-1. As an exception to this rule, if pc[i-1] corresponds to the function runtime.sigpanic, then pc[i] is the program counter of a faulting instruction and should be used without any subtraction.

It doesn't look like this library implements the second part, since it only converts a frame one at a time.

If I get some time, I can do a pull request. I'd like to add an argument to NewStackFrame(), like bool triggeredPanic or something, but I don't know how much code actually calls that directly.

Add Unwrap?

Would it make sense to add an Unwrap() function to this library? I have a usecase where I need to check the type of the underlying error, which (AFAICS) Is() cannot do. I can access the Err field of an errors.Error, but that requires having a *Error instead of an error, which AFAIU isn't the intended way to return (wrapped) errors.

Such a function could simply look like:

func Unwrap(err error) error {
        switch e := err.(type) {
                case *errors.Error:
                        return e.Err
                default:
                        return err
        }
}

Tests Are Failing

drasko@Lenin:~/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors$ go test -v ./...
=== RUN   TestStackFormatMatches
--- FAIL: TestStackFormatMatches (0.00s)
    error_test.go:26: Stack didn't match
    error_test.go:27:   TestStackFormatMatches.func1: bs := [][]byte{Errorf("hi").Stack(), debug.Stack()}
        /usr/lib/go-1.7/src/runtime/asm_amd64.s:479 (0x454e7c)
            call32: CALLFN(·call32, 32)
        /usr/lib/go-1.7/src/runtime/panic.go:458 (0x428883)
            gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:237 (0x4730a0)
            c: panic('a')
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:233 (0x473034)
            b: c()
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:228 (0x472ffa)
            a: b(5)
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:32 (0x471ac8)
            TestStackFormatMatches: a()
        /usr/lib/go-1.7/src/testing/testing.go:610 (0x46a3d1)
            tRunner: fn(t)
        /usr/lib/go-1.7/src/runtime/asm_amd64.s:2086 (0x457971)
            goexit: BYTE    $0x90   // NOP
    error_test.go:28: runtime/debug.Stack(0xc4200160a0, 0xc42010a000, 0x442)
            /usr/lib/go-1.7/src/runtime/debug/stack.go:24 +0x79
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.TestStackFormatMatches.func1(0xc420020240)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:19 +0x170
        panic(0x4f0b40, 0xc42000e5e0)
            /usr/lib/go-1.7/src/runtime/panic.go:458 +0x243
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.c()
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:237 +0x60
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.b(0x5)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:233 +0x14
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.a(0x8, 0x52a660)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:228 +0x2a
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.TestStackFormatMatches(0xc420020240)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:32 +0x48
        testing.tRunner(0xc420020240, 0x52a668)
            /usr/lib/go-1.7/src/testing/testing.go:610 +0x81
        created by testing.(*T).Run
            /usr/lib/go-1.7/src/testing/testing.go:646 +0x2ec
=== RUN   TestSkipWorks
--- FAIL: TestSkipWorks (0.00s)
    error_test.go:49: Stack didn't match
    error_test.go:50: /usr/lib/go-1.7/src/runtime/panic.go:458 (0x428883)
            gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:237 (0x4730a0)
            c: panic('a')
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:233 (0x473034)
            b: c()
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:228 (0x472ffa)
            a: b(5)
        /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:55 (0x471b38)
            TestSkipWorks: a()
        /usr/lib/go-1.7/src/testing/testing.go:610 (0x46a3d1)
            tRunner: fn(t)
        /usr/lib/go-1.7/src/runtime/asm_amd64.s:2086 (0x457971)
            goexit: BYTE    $0x90   // NOP
    error_test.go:51:   /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:43 +0x19e
        panic(0x4f0b40, 0xc42000e908)
            /usr/lib/go-1.7/src/runtime/panic.go:458 +0x243
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.c()
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:237 +0x60
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.b(0x5)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:233 +0x14
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.a(0x8, 0x52a650)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:228 +0x2a
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.TestSkipWorks(0xc4200203c0)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:55 +0x48
        testing.tRunner(0xc4200203c0, 0x52a658)
            /usr/lib/go-1.7/src/testing/testing.go:610 +0x81
        created by testing.(*T).Run
            /usr/lib/go-1.7/src/testing/testing.go:646 +0x2ec
=== RUN   TestNew
--- FAIL: TestNew (0.00s)
    error_test.go:79: Stack didn't match
    error_test.go:80:   TestNew: bs := [][]byte{New("foo").Stack(), debug.Stack()}
        /usr/lib/go-1.7/src/testing/testing.go:610 (0x46a3d1)
            tRunner: fn(t)
        /usr/lib/go-1.7/src/runtime/asm_amd64.s:2086 (0x457971)
            goexit: BYTE    $0x90   // NOP
    error_test.go:81: runtime/debug.Stack(0xc4200162d0, 0xc420090b60, 0x145)
            /usr/lib/go-1.7/src/runtime/debug/stack.go:24 +0x79
        github.com/mainflux/mainflux/vendor/github.com/go-errors/errors.TestNew(0xc420020540)
            /home/drasko/go/src/github.com/mainflux/mainflux/vendor/github.com/go-errors/errors/error_test.go:72 +0x261
        testing.tRunner(0xc420020540, 0x52a640)
            /usr/lib/go-1.7/src/testing/testing.go:610 +0x81
        created by testing.(*T).Run
            /usr/lib/go-1.7/src/testing/testing.go:646 +0x2ec
=== RUN   TestIs
--- PASS: TestIs (0.00s)
=== RUN   TestWrapError
--- PASS: TestWrapError (0.00s)
=== RUN   TestWrapPrefixError
prefix: hi
--- PASS: TestWrapPrefixError (0.00s)
=== RUN   TestParsePanic
--- PASS: TestParsePanic (0.00s)
FAIL
exit status 1
FAIL    github.com/mainflux/mainflux/vendor/github.com/go-errors/errors 0.007s

Output example

Would be nice to get an example of output somewhere in the docs:

*errors.Error oh dear
E:/go/src/github.com/go-errors/errors/34234/crashy/crashy.go:8 (0x44bd0e)
        Crash: return errors.New(Crashed)
E:/go/src/github.com/go-errors/errors/34234/main.go:10 (0x40105e)
        main: err := crashy.Crash()
C:/Program Files/Go/src/runtime/proc.go:188 (0x4284f4)
        main: main_main()
C:/Program Files/Go/src/runtime/asm_386.s:1585 (0x44af41)
        goexit: BYTE    $0x90   // NOP

Test fails due to stack traces

Hi, tests are failing again since go 1.11.

Maybe @gabrielf could come up again with a solution for this? :P

Thanks a lot.

$ go version
go version go1.11.5 linux/amd64

$ go test -vet=off -v -p 4 github.com/go-errors/errors
=== RUN   TestStackFormat
--- FAIL: TestStackFormat (0.00s)
    error_test.go:33: Stack trace does not contain source line: 'a: b(5)'
    error_test.go:34: /build/golang-github-go-errors-errors-1.0.1/obj-x86_64-linux-gnu/src/github.com/go-errors/errors/error_test.go:21 (0x4f5f35)
        	TestStackFormat.func1: e, expected := Errorf("hi"), callers()
        /usr/lib/go-1.11/src/runtime/asm_amd64.s:522 (0x455b3b)
        	call32: CALLFN(·call32, 32)
        /usr/lib/go-1.11/src/runtime/panic.go:513 (0x429df9)
        	gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
        /build/golang-github-go-errors-errors-1.0.1/obj-x86_64-linux-gnu/src/github.com/go-errors/errors/error_test.go:245 (0x4f38ff)
        	TestStackFormat: panic('a')
        /usr/lib/go-1.11/src/testing/testing.go:827 (0x4b573f)
        	tRunner: fn(t)
        /usr/lib/go-1.11/src/runtime/asm_amd64.s:1333 (0x4576d1)
        	goexit: BYTE	$0x90	// NOP

Originally reported in Debian [1] by Adrian Bunk.

Passing a nil into Wrap should return nil

Consider the following code:

package main

import (
	"fmt"
	"github.com/go-errors/errors"
)

func main() {
	var err error
	fmt.Println("err:", err)
	if err != nil {
		fmt.Println("err not nil")
	} else {
		fmt.Println("err nil")
	}

	err = errors.Wrap(err, 0)

	fmt.Println("err:", err)
	if err != nil {
		fmt.Println("goerr not nil")
	} else {
		fmt.Println("goerr nil")
	}
}

Expected output:

err: <nil>
err nil
err: <nil>
goerr nil # <-- This is expected

Actual output:

err: <nil>
err nil
err: <nil>
goerr not nil # <-- Subtle bug waiting to happen that returns "<nil>" when converted to a string

TestStackFormat fails on Go 1.14 beta1

Fedora Rawhide with Go 1.14 beta1:

--- FAIL: TestStackFormat (0.00s)
    error_test.go:26: Stack didn't match
    error_test.go:27: First entry PC diff to large (-41)
        Actual stack trace:
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:21 (0x56027342d9a2)
        	github.com/go-errors/errors.TestStackFormat.func1
        /usr/lib/golang/src/runtime/panic.go:967 (0x56027335c670)
        	runtime.gopanic
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:245 (0x56027342b4e5)
        	github.com/go-errors/errors.c
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:241 (0x56027342b4c9)
        	github.com/go-errors/errors.b
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:236 (0x56027342b4c8)
        	github.com/go-errors/errors.a
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:42 (0x56027342b4c7)
        	github.com/go-errors/errors.TestStackFormat
        /usr/lib/golang/src/testing/testing.go:954 (0x5602733ee0ed)
        	testing.tRunner
        /usr/lib/golang/src/runtime/asm_amd64.s:1375 (0x56027338ca00)
        	runtime.goexit
        
        Expected stack trace:
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:21 (0x56027342d979)
        	github.com/go-errors/errors.TestStackFormat.func1
        /usr/lib/golang/src/runtime/panic.go:967 (0x56027335c670)
        	runtime.gopanic
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:245 (0x56027342b4e5)
        	github.com/go-errors/errors.c
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:241 (0x56027342b4c9)
        	github.com/go-errors/errors.b
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:236 (0x56027342b4c8)
        	github.com/go-errors/errors.a
        /builddir/build/BUILD/errors-1.0.1/_build/src/github.com/go-errors/errors/error_test.go:42 (0x56027342b4c7)
        	github.com/go-errors/errors.TestStackFormat
        /usr/lib/golang/src/testing/testing.go:954 (0x5602733ee0ed)
        	testing.tRunner
        /usr/lib/golang/src/runtime/asm_amd64.s:1375 (0x56027338ca00)
        	runtime.goexit

No tag for v1.5.1

Could you add a tag for v1.5.1 please so dependabot picks up the newer version? Right now it's updating to 1.5.0 and CI is failing for my go < 1.20 projects.

Thanks!

Test failed when run go test in GitHub actions CI

Everything works fine on local enviroment, but can not run go test in GitHub actions CI.

My GitHub action config:

name: Build and test Go
on: [ push, pull_request ]
jobs:
  test:
    strategy:
      matrix:
        go-version: [ 1.15.x ]
        os: [ ubuntu-latest ]
        mongodb-version: [4.2]
    runs-on: ${{ matrix.os }}
    steps:
      - name: setup env
        shell: bash
        run: |
          echo "::set-env name=GOPATH::${{ github.workspace }}/go"
          echo "::add-path::${{ github.workspace }}/go/bin"

      - name: Install Go
        uses: actions/setup-go@v2
        with:
          go-version: ${{ matrix.go-version }}

      - name: Start MongoDB
        uses: supercharge/[email protected]
        with:
          mongodb-version: ${{ matrix.mongodb-version }}

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Build
        env:
          GOPROXY: "https://proxy.golang.org"
        run: go build .

      - name: Test
        run: go test ./...

Some GitHub action logs:

ok  	github.com/klauspost/compress/zstd	30.515s
ok  	github.com/klauspost/compress/zstd/internal/xxhash	0.016s
--- FAIL: TestFormatNew (0.00s)
    format_test.go:38: test 3: line 3: fmt.Sprintf("%+v", err):
         got: "error\ngithub.com/pkg/errors.TestFormatNew\n\t/home/runner/work/coinsmart-api/coinsmart-api/go/pkg/mod/github.com/pkg/[email protected]/format_test.go:26\ntesting.tRunner\n\t/opt/hostedtoolcache/go/1.15.0/x64/src/testing/testing.go:1108\nruntime.goexit\n\t/opt/hostedtoolcache/go/1.15.0/x64/src/runtime/asm_amd64.s:1374"
        want: "error\ngithub.com/pkg/errors.TestFormatNew\n\t.+/github.com/pkg/errors/format_test.go:26"
--- FAIL: TestFormatErrorf (0.00s)
    format_test.go:64: test 3: line 3: fmt.Sprintf("%+v", err):
         got: "error\ngithub.com/pkg/errors.TestFormatErrorf\n\t/home/runner/work/coinsmart-api/coinsmart-api/go/pkg/mod/github.com/pkg/[email protected]/format_test.go:56\ntesting.tRunner\n\t/opt/hostedtoolcache/go/1.15.0/x64/src/testing/testing.go:1108\nruntime.goexit\n\t/opt/hostedtoolcache/go/1.15.0/x64/src/runtime/asm_amd64.s:1374"
        want: "error\ngithub.com/pkg/errors.TestFormatErrorf\n\t.+/github.com/pkg/errors/format_test.go:56"

It seems errors can not pass the tests. But I don't know why.

Why keep cover.out file in the repo?

The file cover.out seems a test coverage file.

Don't know why it keeps in the repo.

But it might be some trouble when people vendor this package and with a .gitignore file in which the *.out file is ignored.

nil error always fail at err != nil check

Following is the sample code the output would say a lot here. I tried debugging this myself but unable to find why the behavior is such.

func main() {
	err := DBErr()
	if err != nil {
		fmt.Printf("Main err was not nil %T %v\n", err, err)
	} else {
		fmt.Println("Main", "err was nil")
	}
}

func DBErr() error {
	e := errors.Wrap(nil, 1)
	fmt.Println("DBErr() err is nil", e == nil)
	return e
}

Output:

DBErr() err is nil true
Main err was not nil *errors.Error <nil>

running go version

go version go1.16 darwin/amd64

following is my go.mod definition

module test-errors

go 1.16

require github.com/go-errors/errors v1.2.0

Incomplete Stacktrace

https://go.dev/play/p/ThypuXRoewr

*fmt.wrapError FATAL: failed to do stuff: failed to do conditional1 stuff: file not found
/tmp/sandbox1705693913/prog.go:15 (0x48331d)
/usr/local/go-faketime/src/runtime/internal/atomic/types.go:194 (0x4338b2)
/usr/local/go-faketime/src/runtime/asm_amd64.s:1650 (0x45d141)

This only seems to print the last part of the stack trace. Am I doing something wrong? If so, maybe someone can show me what I'm doing wrong, and I can submit a PR to update the documentation. Thanks for your time!

Is() is not safe as replacement to errors.Is

Hello,
While working around go errors, I had an issue with the Is function.
It does not use the default errors.Is and only compare for strict equality between base errors witch is the default comportment of original Is but not its full implementation.
Basically, we are using a custom error type witch reimplement Is and I expected it to be called on goerrors.Is but it is not the cased.
It also mean that current goerrors.Is will not have expected behaviour on wrapped errors.

//
// Is unwraps its first argument sequentially looking for an error that matches the
// second. It reports whether it finds a match. It should be used in preference to
// simple equality checks:
//
// if errors.Is(err, os.ErrExist)
//
// is preferable to
//
// if err == os.ErrExist
//
// because the former will succeed if err wraps os.ErrExist.
(errors/errors.go)

`go test` fails on Windows

E:\go\src\github.com\go-errors\errors>go version
go version go1.6.2 windows/386
E:\go\src\github.com\go-errors\errors>go test
--- FAIL: TestStackFormatMatches (0.00s)
    error_test.go:26: Stack didn't match
    error_test.go:27:   TestStackFormatMatches.func1: bs := [][]byte{Errorf("hi").Stack(), debug.Stack()}
        C:/Program Files/Go/src/runtime/asm_386.s:488 (0x44ca9a)
            call16: CALLFN(·call16, 16)
        C:/Program Files/Go/src/runtime/panic.go:443 (0x428a6d)
            gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
        E:/go/src/github.com/go-errors/errors/error_test.go:237 (0x473a4f)
            c: panic('a')
        E:/go/src/github.com/go-errors/errors/error_test.go:233 (0x4739f7)
            b: c()
        E:/go/src/github.com/go-errors/errors/error_test.go:228 (0x4739cb)
            a: b(5)
        E:/go/src/github.com/go-errors/errors/error_test.go:32 (0x47230c)
            TestStackFormatMatches: a()
        C:/Program Files/Go/src/testing/testing.go:473 (0x4697ff)
            tRunner: test.F(t)
        C:/Program Files/Go/src/runtime/asm_386.s:1585 (0x44e881)
            goexit: BYTE    $0x90   // NOP
    error_test.go:28: runtime/debug.Stack(0x0, 0x0, 0x0)
            C:/Program Files/Go/src/runtime/debug/stack.go:24 +0x80
        github.com/go-errors/errors.TestStackFormatMatches.func1(0x10d32120)
            E:/go/src/github.com/go-errors/errors/error_test.go:19 +0xdb
        panic(0x50a1c0, 0x10d021c8)
            C:/Program Files/Go/src/runtime/panic.go:443 +0x3fd
        github.com/go-errors/errors.c()
            E:/go/src/github.com/go-errors/errors/error_test.go:237 +0x4f
        github.com/go-errors/errors.b(0x5)
            E:/go/src/github.com/go-errors/errors/error_test.go:233 +0x17
        github.com/go-errors/errors.a(0x0, 0x0)
            E:/go/src/github.com/go-errors/errors/error_test.go:228 +0x2b
        github.com/go-errors/errors.TestStackFormatMatches(0x10d32120)
            E:/go/src/github.com/go-errors/errors/error_test.go:32 +0x3c
        testing.tRunner(0x10d32120, 0x5fb4c0)
            C:/Program Files/Go/src/testing/testing.go:473 +0x8f
        created by testing.RunTests
            C:/Program Files/Go/src/testing/testing.go:582 +0x6f2
--- FAIL: TestSkipWorks (0.00s)
    error_test.go:49: Stack didn't match
    error_test.go:50: C:/Program Files/Go/src/runtime/panic.go:443 (0x428a6d)
            gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
        E:/go/src/github.com/go-errors/errors/error_test.go:237 (0x473a4f)
            c: panic('a')
        E:/go/src/github.com/go-errors/errors/error_test.go:233 (0x4739f7)
            b: c()
        E:/go/src/github.com/go-errors/errors/error_test.go:228 (0x4739cb)
            a: b(5)
        E:/go/src/github.com/go-errors/errors/error_test.go:55 (0x47236c)
            TestSkipWorks: a()
        C:/Program Files/Go/src/testing/testing.go:473 (0x4697ff)
            tRunner: test.F(t)
        C:/Program Files/Go/src/runtime/asm_386.s:1585 (0x44e881)
            goexit: BYTE    $0x90   // NOP
    error_test.go:51:   E:/go/src/github.com/go-errors/errors/error_test.go:43 +0xfd
        panic(0x50a1c0, 0x10d02400)
            C:/Program Files/Go/src/runtime/panic.go:443 +0x3fd
        github.com/go-errors/errors.c()
            E:/go/src/github.com/go-errors/errors/error_test.go:237 +0x4f
        github.com/go-errors/errors.b(0x5)
            E:/go/src/github.com/go-errors/errors/error_test.go:233 +0x17
        github.com/go-errors/errors.a(0x0, 0x0)
            E:/go/src/github.com/go-errors/errors/error_test.go:228 +0x2b
        github.com/go-errors/errors.TestSkipWorks(0x10d327e0)
            E:/go/src/github.com/go-errors/errors/error_test.go:55 +0x3c
        testing.tRunner(0x10d327e0, 0x5fb4cc)
            C:/Program Files/Go/src/testing/testing.go:473 +0x8f
        created by testing.RunTests
            C:/Program Files/Go/src/testing/testing.go:582 +0x6f2
--- FAIL: TestNew (0.00s)
    error_test.go:79: Stack didn't match
    error_test.go:80:   TestNew: bs := [][]byte{New("foo").Stack(), debug.Stack()}
        C:/Program Files/Go/src/testing/testing.go:473 (0x4697ff)
            tRunner: test.F(t)
        C:/Program Files/Go/src/runtime/asm_386.s:1585 (0x44e881)
            goexit: BYTE    $0x90   // NOP
    error_test.go:81: runtime/debug.Stack(0x0, 0x0, 0x0)
            C:/Program Files/Go/src/runtime/debug/stack.go:24 +0x80
        github.com/go-errors/errors.TestNew(0x10d32e40)
            E:/go/src/github.com/go-errors/errors/error_test.go:72 +0x209
        testing.tRunner(0x10d32e40, 0x5fb4d8)
            C:/Program Files/Go/src/testing/testing.go:473 +0x8f
        created by testing.RunTests
            C:/Program Files/Go/src/testing/testing.go:582 +0x6f2
prefix: hi
FAIL
exit status 1
FAIL    github.com/go-errors/errors 0.056s

Create annotated tags for releases

Hi

It would be great if you tagged the current HEAD with v1.0.0, and in the future, occasionally bump the version and make new tags with new releases. I suggest using the semver.org versioning scheme, where you have major.minor.patch, and you bump the major for incompatible changes and the minor for compatible changes to the API.

Functions Join and Unwrap

Similar to how this library already provides As and Is, it would be nice also to provide Join and Unwrap, simply by delegating to the stdlib implementations. This way there would no longer be a need to import stdlib errors and we could avoid the naming conflict when both packages are imported.

Let me know if you would consider adding this, I can open a PR.

error is not nil when it should be

Hello,

I'm having a weird issue with the package. It may be my own inexperience with go though, but I thought I'd ask anyway.
If you run the following program, myError not nil gets printed.
Any idea?

package main

import "fmt"
import "github.com/go-errors/errors"

func goError() error {
    return nil
}

func myError() *errors.Error {
    return nil
}

func main() {

    err := goError()
    if err != nil {
        fmt.Println("goError not nil")
    }

    err = myError()
    if err != nil {
        fmt.Println("myError not nil")
    }

}

Wrap() doc does not indicate that *Error argument is returned unchanged

Wrap() does one of four things depending on its input "e":

  1. nil -> returns nil
  2. *Error -> returns it
  3. error -> wraps in *Error with new stack trace
  4. default -> wraps fmt.Errorf("%v", e) in *Error with new stack trace

(3) and (4) are clearly stated in the doc. I think it's fair to say that (1) is implicit.

(2) is not documented, and this has implications for Wrap's use. In many cases I want it to do (2), but in some cases I want to additionally wrap with a new stack trace:

  • When propagating an error where I want to ensure it has a stack trace (e.g., the incoming error was returned from a library) I will call wrap. If the incoming error happens to already be *Error (perhaps the library itself uses go-errors) it will just be returned. Great.
  • When an error passes between goroutines I want to additionally Wrap it to produce a "return trace" spanning all goroutines. As an error is taken from a channel it will be wrapped. Using Wrap() does not produce the desired error. I believe errors.Errorf does provide this.

All of this applies to WrapPrefix() as well.

I recommend changing the doc from:

Wrap makes an Error from the given value. If that value is already an error then it will be used directly, if not, it will be passed to fmt.Errorf("%v"). The skip parameter indicates how far up the stack to start the stacktrace. 0 is from the current call, 1 from its caller, etc.

to:

Wrap makes an Error from the given value. If that value is already an error then it will be used directly, if not, it will be passed to fmt.Errorf("%v"). If that value is already an *Error it will not be wrapped and instead will be directly returned. To explicitly wrap an *Error use Errorf. The skip parameter indicates how far up the stack to start the stacktrace. 0 is from the current call, 1 from its caller, etc.

I'm happy to write the PR but I wanted to discuss first.

nil check when wrapping errors fails for custom error types

nil check when wrapping errors fails for custom error types. nil error get wrapped and - with pointers - it even results in a panic.

Here's a UT that demonstrates the behavior:

package go_err

import (
	"testing"

	"github.com/go-errors/errors"
)

type MyError1 struct {
	message string
}

func (me1 MyError1) Error() string{
	return me1.message
}

type MyError2 struct {
	message string
}

func (me2 *MyError2) Error() string{
	return me2.message
}

func TestNilErr(t *testing.T) {
	var (
		err error
		myErr1 MyError1
		myErr2 *MyError2
	)

	err = errors.WrapPrefix(err, "blah message", 0)
	t.Log("base error", "value", err)

	// Shouldn't be wrapped, but it is
	err = errors.WrapPrefix(myErr1, "blah message", 0)
	t.Log("my error 1", "value", err)

	// Shouldn't be wrapped, but it panics
	err = errors.WrapPrefix(myErr2, "blah message", 0)
	t.Log("my error 2", "value", err)
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.