First commit
This commit is contained in:
commit
37e2e39ff8
|
@ -0,0 +1,24 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
|
@ -0,0 +1,26 @@
|
||||||
|
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Modifications of the code made by by Friends of Go
|
||||||
|
Copyright (c) 2019, Friends of Go <contact@friendsofgo.tech>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,21 @@
|
||||||
|
# general
|
||||||
|
WORKDIR = $(PWD)
|
||||||
|
|
||||||
|
# coverage
|
||||||
|
COVERAGE_REPORT = coverage.txt
|
||||||
|
COVERAGE_PROFILE = profile.out
|
||||||
|
COVERAGE_MODE = atomic
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
@cd $(WORKDIR); \
|
||||||
|
echo "" > $(COVERAGE_REPORT); \
|
||||||
|
for dir in `find . -name "*.go" | grep -o '.*/' | sort | uniq`; do \
|
||||||
|
go test -v -race $$dir -coverprofile=$(COVERAGE_PROFILE) -covermode=$(COVERAGE_MODE); \
|
||||||
|
if [ $$? != 0 ]; then \
|
||||||
|
exit 2; \
|
||||||
|
fi; \
|
||||||
|
if [ -f $(COVERAGE_PROFILE) ]; then \
|
||||||
|
cat $(COVERAGE_PROFILE) >> $(COVERAGE_REPORT); \
|
||||||
|
rm $(COVERAGE_PROFILE); \
|
||||||
|
fi; \
|
||||||
|
done; \
|
|
@ -0,0 +1,102 @@
|
||||||
|
[![FriendsOfGo](https://img.shields.io/badge/powered%20by-Friends%20of%20Go-73D7E2.svg)](https://friendsofgo.tech)
|
||||||
|
[![CircleCI](https://circleci.com/gh/friendsofgo/errors.svg?style=svg)](https://circleci.com/gh/friendsofgo/errors)
|
||||||
|
[![Build status](https://ci.appveyor.com/api/projects/status/phjkr6de4mnb19kq?svg=true)](https://ci.appveyor.com/project/aperezg/errors)
|
||||||
|
[![Version](https://img.shields.io/github/release/friendsofgo/errors.svg?style=flat-square)](https://github.com/friendsofgo/errors/releases/latest)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/friendsofgo/errors)](https://goreportcard.com/report/github.com/friendsofgo/errors)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/friendsofgo/errors?status.svg)](https://godoc.org/github.com/friendsofgo/errors)
|
||||||
|
|
||||||
|
# errors
|
||||||
|
|
||||||
|
This package is a fork from [github.com/pkg/errors](https://github.com/pkg/errors) package created by
|
||||||
|
[Dave Cheney](https://github.com/davecheney). The original package has no longer accepting proposals for new functionality.
|
||||||
|
|
||||||
|
With the new errors on [go 1.13](https://godoc.org/errors), the way to using the errors on Go has some
|
||||||
|
changes that can be applied into Dave Cheney library. We want to offer one way to migrate your code to new
|
||||||
|
errors, but with the minimum refactor, for that we've created this package.
|
||||||
|
|
||||||
|
This package provide the same interface that the original library have, but using new [go 1.13](https://godoc.org/errors)
|
||||||
|
errors, or in previous version [golang.org/x/xerrors](https://golang.org/x/xerrors) package.
|
||||||
|
|
||||||
|
## How to start using friendsofgo/errors
|
||||||
|
|
||||||
|
If you previously was using the package [github.com/pkg/errors](https://github.com/pkg/errors), you only need
|
||||||
|
change your imports for **github.com/friendsofgo/errors**, with this simple change now you're capable to use
|
||||||
|
[go 1.13](https://godoc.org/errors) in your code, and use the new methods `As` and `Is` if you want.
|
||||||
|
|
||||||
|
Furthermore the method `Wrap` `Wrapf become compatible with `Unwrap` interface of new [go 1.13](https://godoc.org/errors) errors.
|
||||||
|
|
||||||
|
## Adding context to an error
|
||||||
|
|
||||||
|
With the original package [go 1.13](https://godoc.org/errors) if you want add context, ergo wrap your error you need to create
|
||||||
|
a new error and using the new verb `"%w" like that:
|
||||||
|
|
||||||
|
```go
|
||||||
|
_, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read failed: %w", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Using our library you can do that forgetting to the new verb:
|
||||||
|
|
||||||
|
```go
|
||||||
|
_, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "read failed")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Retrieving the cause of an error
|
||||||
|
|
||||||
|
We want to keep the compatibility with the [github.com/pkg/errors](https://github.com/pkg/errors) package, for that
|
||||||
|
our package provides a `Cause` method, but this method is not longer needed, because we can use the new methods `Is` or `As`
|
||||||
|
that provides the official package.
|
||||||
|
|
||||||
|
So previously if you needed to check an error cause, your error must be implemented the `causer` inteface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`errors.Cause` will recursively retrieve the topmost error which does not implement causer, which is assumed to be the original cause. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
switch err := errors.Cause(err).(type) {
|
||||||
|
case *MyError:
|
||||||
|
// handle specifically
|
||||||
|
default:
|
||||||
|
// unknown error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
But now you can do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var target *MyError
|
||||||
|
if errors.As(err, &target) {
|
||||||
|
// handle specifically
|
||||||
|
} else {
|
||||||
|
// unknown error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you uses a sentinel error:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var ErrMyError = errors.New("my sentinel error")
|
||||||
|
if errors.Is(err, ErrMyError) {
|
||||||
|
// handle specifically
|
||||||
|
} else {
|
||||||
|
// unknown error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
This package was created to using with go 1.13 version however if you uses this package with a previous version, the methods
|
||||||
|
`As`, `Is`, `Wrap` and `Wrapf` will be using [golang.org/x/xerrors](https://golang.org/x/xerrors) package.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
[Contributions](https://github.com/friendsofgo/errors/issues?q=is%3Aissue+is%3Aopen) are more than welcome, if you are interested please fork this repo and send your Pull Request.
|
|
@ -0,0 +1,33 @@
|
||||||
|
version: build-{build}.{branch}
|
||||||
|
|
||||||
|
clone_folder: C:\gopath\src\github.com\friendsofgo\errors
|
||||||
|
shallow_clone: true # for startup speed
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOPATH: C:\gopath
|
||||||
|
GO111MODULE: on
|
||||||
|
|
||||||
|
stack: go 1.13
|
||||||
|
|
||||||
|
platform:
|
||||||
|
- x64
|
||||||
|
|
||||||
|
# http://www.appveyor.com/docs/installed-software
|
||||||
|
install:
|
||||||
|
- go version
|
||||||
|
- go env
|
||||||
|
- set PATH=C:\msys64\mingw64\bin;%GOPATH%\bin;c:\go\bin;%PATH%
|
||||||
|
- go mod tidy
|
||||||
|
- 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
|
|
@ -0,0 +1,110 @@
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
stderrors "errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func noErrors(at, depth int) error {
|
||||||
|
if at >= depth {
|
||||||
|
return stderrors.New("no error")
|
||||||
|
}
|
||||||
|
return noErrors(at+1, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func yesErrors(at, depth int) error {
|
||||||
|
if at >= depth {
|
||||||
|
return New("ye error")
|
||||||
|
}
|
||||||
|
return yesErrors(at+1, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalE is an exported global to store the result of benchmark results,
|
||||||
|
// preventing the compiler from optimising the benchmark functions away.
|
||||||
|
var GlobalE interface{}
|
||||||
|
|
||||||
|
func BenchmarkErrors(b *testing.B) {
|
||||||
|
type run struct {
|
||||||
|
stack int
|
||||||
|
std bool
|
||||||
|
}
|
||||||
|
runs := []run{
|
||||||
|
{10, false},
|
||||||
|
{10, true},
|
||||||
|
{100, false},
|
||||||
|
{100, true},
|
||||||
|
{1000, false},
|
||||||
|
{1000, true},
|
||||||
|
}
|
||||||
|
for _, r := range runs {
|
||||||
|
part := "friendsofgo/errors"
|
||||||
|
if r.std {
|
||||||
|
part = "errors"
|
||||||
|
}
|
||||||
|
name := fmt.Sprintf("%s-stack-%d", part, r.stack)
|
||||||
|
b.Run(name, func(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
f := yesErrors
|
||||||
|
if r.std {
|
||||||
|
f = noErrors
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err = f(0, r.stack)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
GlobalE = err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,368 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
// Original package created by Dave Cheney
|
||||||
|
// Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||||
|
//
|
||||||
|
// Modifications of the original package by Friends of Go
|
||||||
|
// Copyright (c) 2019, Friends of Go <contact@friendsofgo.tech>
|
||||||
|
//
|
||||||
|
// Package errors provides simple error handling primitives.
|
||||||
|
//
|
||||||
|
// The traditional error handling idiom in Go is roughly akin to
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// which when 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
|
||||||
|
//
|
||||||
|
// 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,
|
||||||
|
// together with the supplied message. For example
|
||||||
|
//
|
||||||
|
// _, err := ioutil.ReadAll(r)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.Wrap(err, "read failed")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If additional control is required, the errors.WithStack and
|
||||||
|
// errors.WithMessage functions destructure errors.Wrap into its component
|
||||||
|
// operations: annotating an error with a stack trace and with a message,
|
||||||
|
// respectively.
|
||||||
|
//
|
||||||
|
// Retrieving the cause of an error
|
||||||
|
//
|
||||||
|
// Using errors.Wrap 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
|
||||||
|
// for inspection. Any error value which implements this interface
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||||
|
// the topmost error that 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
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Although the causer interface is not exported by this package, it is
|
||||||
|
// considered a part of its stable public interface.
|
||||||
|
//
|
||||||
|
// With the new standard package error we have two new ways to figure what is the cause of
|
||||||
|
// our error:
|
||||||
|
//
|
||||||
|
// var target *MyError
|
||||||
|
// if errors.As(err, &target) {
|
||||||
|
// // handle specifically
|
||||||
|
// } else {
|
||||||
|
// // unknown error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// or even with sentinel errors:
|
||||||
|
//
|
||||||
|
// var ErrMyError = errors.New("my sentinel error")
|
||||||
|
// if errors.Is(err, ErrMyError) {
|
||||||
|
// // handle specifically
|
||||||
|
// } else {
|
||||||
|
// // unknown error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The returned errors.StackTrace type is defined as
|
||||||
|
//
|
||||||
|
// 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 err, ok := err.(stackTracer); ok {
|
||||||
|
// for _, f := range err.StackTrace() {
|
||||||
|
// fmt.Printf("%+s:%d\n", f, f)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Although the stackTracer interface is not exported by this package, it is
|
||||||
|
// considered a part of its stable public interface.
|
||||||
|
//
|
||||||
|
// See the documentation for Frame.Format for more details.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return &fundamental{
|
||||||
|
msg: message,
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return &fundamental{
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fundamental is an error that has a message and a stack, but no caller.
|
||||||
|
type fundamental struct {
|
||||||
|
msg string
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fundamental) Error() string { return f.msg }
|
||||||
|
|
||||||
|
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
f.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.Flag('-') {
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
f.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", f.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||||
|
// If err is nil, WithStack returns nil.
|
||||||
|
func WithStack(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withStack struct {
|
||||||
|
error
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withStack) Cause() error { return w.error }
|
||||||
|
func (w *withStack) Unwrap() error { return w.error }
|
||||||
|
|
||||||
|
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v", w.Cause())
|
||||||
|
w.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.Flag('-') {
|
||||||
|
fmt.Fprintf(s, "%-v", w.Cause())
|
||||||
|
w.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func Wrap(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrapf is called, and the format specifier.
|
||||||
|
// If err is nil, Wrapf returns nil.
|
||||||
|
func Wrapf(err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is reports whether any error in err's chain matches target.
|
||||||
|
//
|
||||||
|
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||||
|
// repeatedly calling Unwrap.
|
||||||
|
//
|
||||||
|
// An error is considered to match a target if it is equal to that target or if
|
||||||
|
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||||
|
func Is(err error, target error) bool {
|
||||||
|
return errors.Is(err, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As finds the first error in err's chain that matches target, and if so, sets
|
||||||
|
// target to that error value and returns true.
|
||||||
|
//
|
||||||
|
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||||
|
// repeatedly calling Unwrap.
|
||||||
|
//
|
||||||
|
// An error matches target if the error's concrete value is assignable to the value
|
||||||
|
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
||||||
|
// As(target) returns true. In the latter case, the As method is responsible for
|
||||||
|
// setting target.
|
||||||
|
//
|
||||||
|
// As will panic if target is not a non-nil pointer to either a type that implements
|
||||||
|
// error, or to any interface type. As returns false if err is nil.
|
||||||
|
func As(err error, target interface{}) bool {
|
||||||
|
return errors.As(err, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessage annotates err with a new message.
|
||||||
|
// If err is nil, WithMessage returns nil.
|
||||||
|
func WithMessage(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessagef annotates err with the format specifier.
|
||||||
|
// If err is nil, WithMessagef returns nil.
|
||||||
|
func WithMessagef(err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withMessage struct {
|
||||||
|
cause error
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||||
|
func (w *withMessage) Cause() error { return w.cause }
|
||||||
|
func (w *withMessage) Unwrap() error { return w.cause }
|
||||||
|
|
||||||
|
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||||
|
io.WriteString(s, w.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.Flag('-') {
|
||||||
|
fmt.Fprintf(s, "%-v ; ", w.Cause())
|
||||||
|
io.WriteString(s, w.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's', 'q':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cause returns the underlying cause of the error, if possible.
|
||||||
|
// An error value has a cause if it implements the following
|
||||||
|
// interface:
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// investigation.
|
||||||
|
func Cause(err error) error {
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
|
||||||
|
for err != nil {
|
||||||
|
var c causer
|
||||||
|
if !As(err, &c) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = c.Cause()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatPanicDebugStack(msg string) string {
|
||||||
|
msg = strings.ReplaceAll(msg, "\n", " < ")
|
||||||
|
msg = strings.ReplaceAll(msg, ") <", ") at")
|
||||||
|
idx := strings.Index(msg, "panic.go:")
|
||||||
|
if idx != -1 {
|
||||||
|
msg = msg[idx:]
|
||||||
|
}
|
||||||
|
if len(msg) > 2000 {
|
||||||
|
msg = msg[:2000] + "..."
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
stdlib_errors "errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err string
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
{"", fmt.Errorf("")},
|
||||||
|
{"foo", fmt.Errorf("foo")},
|
||||||
|
{"foo", New("foo")},
|
||||||
|
{"string with format specifiers: %v", errors.New("string with format specifiers: %v")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := New(tt.err)
|
||||||
|
if got.Error() != tt.want.Error() {
|
||||||
|
t.Errorf("New.Error(): got: %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapNil(t *testing.T) {
|
||||||
|
got := Wrap(nil, "no error")
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrap(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
message string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{io.EOF, "read error", "read error: EOF"},
|
||||||
|
{Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := Wrap(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nilError struct{}
|
||||||
|
|
||||||
|
func (nilError) Error() string { return "nil error" }
|
||||||
|
|
||||||
|
func TestCause(t *testing.T) {
|
||||||
|
x := New("error")
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
want error
|
||||||
|
}{{
|
||||||
|
// nil error is nil
|
||||||
|
err: nil,
|
||||||
|
want: nil,
|
||||||
|
}, {
|
||||||
|
// explicit nil error is nil
|
||||||
|
err: (error)(nil),
|
||||||
|
want: nil,
|
||||||
|
}, {
|
||||||
|
// typed nil is nil
|
||||||
|
err: (*nilError)(nil),
|
||||||
|
want: (*nilError)(nil),
|
||||||
|
}, {
|
||||||
|
// uncaused error is unaffected
|
||||||
|
err: io.EOF,
|
||||||
|
want: io.EOF,
|
||||||
|
}, {
|
||||||
|
// caused error returns cause
|
||||||
|
err: Wrap(io.EOF, "ignored"),
|
||||||
|
want: io.EOF,
|
||||||
|
}, {
|
||||||
|
err: x, // return from errors.New
|
||||||
|
want: x,
|
||||||
|
}, {
|
||||||
|
WithMessage(nil, "whoops"),
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
WithMessage(io.EOF, "whoops"),
|
||||||
|
io.EOF,
|
||||||
|
}, {
|
||||||
|
WithStack(nil),
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
WithStack(io.EOF),
|
||||||
|
io.EOF,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := Cause(tt.err)
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapfNil(t *testing.T) {
|
||||||
|
got := Wrapf(nil, "no error")
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapf(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
message string
|
||||||
|
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"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := Wrapf(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorf(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{Errorf("read error without format specifiers"), "read error without format specifiers"},
|
||||||
|
{Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := tt.err.Error()
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithStackNil(t *testing.T) {
|
||||||
|
got := WithStack(nil)
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("WithStack(nil): got %#v, expected nil", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithStack(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{io.EOF, "EOF"},
|
||||||
|
{WithStack(io.EOF), "EOF"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := WithStack(tt.err).Error()
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithMessageNil(t *testing.T) {
|
||||||
|
got := WithMessage(nil, "no error")
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithMessage(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
message string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{io.EOF, "read error", "read error: EOF"},
|
||||||
|
{WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := WithMessage(tt.err, tt.message).Error()
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithMessagefNil(t *testing.T) {
|
||||||
|
got := WithMessagef(nil, "no error")
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithMessagef(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
message string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{io.EOF, "read error", "read error: EOF"},
|
||||||
|
{WithMessagef(io.EOF, "read error without format specifier"), "client error", "client error: read error without format specifier: EOF"},
|
||||||
|
{WithMessagef(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 := WithMessagef(tt.err, tt.message).Error()
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errors.New, etc values are not expected to be compared by value
|
||||||
|
// but the change in errors#27 made them incomparable. Assert that
|
||||||
|
// various kinds of errors have a functional equality operator, even
|
||||||
|
// if the result of that equality is always false.
|
||||||
|
func TestErrorEquality(t *testing.T) {
|
||||||
|
vals := []error{
|
||||||
|
nil,
|
||||||
|
io.EOF,
|
||||||
|
errors.New("EOF"),
|
||||||
|
New("EOF"),
|
||||||
|
Errorf("EOF"),
|
||||||
|
Wrap(io.EOF, "EOF"),
|
||||||
|
Wrapf(io.EOF, "EOF%d", 2),
|
||||||
|
WithMessage(nil, "whoops"),
|
||||||
|
WithMessage(io.EOF, "whoops"),
|
||||||
|
WithStack(io.EOF),
|
||||||
|
WithStack(nil),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range vals {
|
||||||
|
for j := range vals {
|
||||||
|
_ = vals[i] == vals[j] // mustn't panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIs(t *testing.T) {
|
||||||
|
sentinelError := stdlib_errors.New("sentinel error")
|
||||||
|
wrap := Wrap(sentinelError, "wrap error")
|
||||||
|
if !Is(wrap, sentinelError) {
|
||||||
|
t.Errorf("Expected that '%v' error and the '%v' error should be of the same type", sentinelError, wrap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAs(t *testing.T) {
|
||||||
|
type myError struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
err := &myError{stdlib_errors.New("error")}
|
||||||
|
wrap := Wrap(err, "wrap error")
|
||||||
|
|
||||||
|
var tt *myError
|
||||||
|
if !As(wrap, &tt) {
|
||||||
|
t.Errorf("Expected that '%v' error and the '%v' error should be of the same type", err, wrap)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.bit5.ru/backend/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNew() {
|
||||||
|
err := errors.New("whoops")
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// Output: whoops
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNew_printf() {
|
||||||
|
err := errors.New("whoops")
|
||||||
|
fmt.Printf("%+v", err)
|
||||||
|
|
||||||
|
// Example output:
|
||||||
|
// whoops
|
||||||
|
// github.com/friendsofgo/errors_test.ExampleNew_printf
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:17
|
||||||
|
// testing.runExample
|
||||||
|
// /home/dfc/go/src/testing/example.go:114
|
||||||
|
// testing.RunExamples
|
||||||
|
// /home/dfc/go/src/testing/example.go:38
|
||||||
|
// testing.(*M).Run
|
||||||
|
// /home/dfc/go/src/testing/testing.go:744
|
||||||
|
// main.main
|
||||||
|
// /github.com/friendsofgo/errors/_test/_testmain.go:106
|
||||||
|
// runtime.main
|
||||||
|
// /home/dfc/go/src/runtime/proc.go:183
|
||||||
|
// runtime.goexit
|
||||||
|
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWithMessage() {
|
||||||
|
cause := errors.New("whoops")
|
||||||
|
err := errors.WithMessage(cause, "oh noes")
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// Output: oh noes: whoops
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWithStack() {
|
||||||
|
cause := errors.New("whoops")
|
||||||
|
err := errors.WithStack(cause)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// Output: whoops
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWithStack_printf() {
|
||||||
|
cause := errors.New("whoops")
|
||||||
|
err := errors.WithStack(cause)
|
||||||
|
fmt.Printf("%+v", err)
|
||||||
|
|
||||||
|
// Example Output:
|
||||||
|
// whoops
|
||||||
|
// github.com/friendsofgo/errors_test.ExampleWithStack_printf
|
||||||
|
// /home/fabstu/go/src/github.com/friendsofgo/errors/example_test.go:55
|
||||||
|
// testing.runExample
|
||||||
|
// /usr/lib/go/src/testing/example.go:114
|
||||||
|
// testing.RunExamples
|
||||||
|
// /usr/lib/go/src/testing/example.go:38
|
||||||
|
// testing.(*M).Run
|
||||||
|
// /usr/lib/go/src/testing/testing.go:744
|
||||||
|
// main.main
|
||||||
|
// github.com/friendsofgo/errors/_test/_testmain.go:106
|
||||||
|
// runtime.main
|
||||||
|
// /usr/lib/go/src/runtime/proc.go:183
|
||||||
|
// runtime.goexit
|
||||||
|
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
||||||
|
// github.com/friendsofgo/errors_test.ExampleWithStack_printf
|
||||||
|
// /home/fabstu/go/src/github.com/friendsofgo/errors/example_test.go:56
|
||||||
|
// testing.runExample
|
||||||
|
// /usr/lib/go/src/testing/example.go:114
|
||||||
|
// testing.RunExamples
|
||||||
|
// /usr/lib/go/src/testing/example.go:38
|
||||||
|
// testing.(*M).Run
|
||||||
|
// /usr/lib/go/src/testing/testing.go:744
|
||||||
|
// main.main
|
||||||
|
// github.com/friendsofgo/errors/_test/_testmain.go:106
|
||||||
|
// runtime.main
|
||||||
|
// /usr/lib/go/src/runtime/proc.go:183
|
||||||
|
// runtime.goexit
|
||||||
|
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWrap() {
|
||||||
|
cause := errors.New("whoops")
|
||||||
|
err := errors.Wrap(cause, "oh noes")
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// Output: oh noes: whoops
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn() error {
|
||||||
|
e1 := errors.New("error")
|
||||||
|
e2 := errors.Wrap(e1, "inner")
|
||||||
|
e3 := errors.Wrap(e2, "middle")
|
||||||
|
return errors.Wrap(e3, "outer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCause() {
|
||||||
|
err := fn()
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println(errors.Cause(err))
|
||||||
|
|
||||||
|
// Output: outer: middle: inner: error
|
||||||
|
// error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWrap_extended() {
|
||||||
|
err := fn()
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
|
||||||
|
// Example output:
|
||||||
|
// error
|
||||||
|
// github.com/friendsofgo/errors_test.fn
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:47
|
||||||
|
// github.com/friendsofgo/errors_test.ExampleCause_printf
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:63
|
||||||
|
// testing.runExample
|
||||||
|
// /home/dfc/go/src/testing/example.go:114
|
||||||
|
// testing.RunExamples
|
||||||
|
// /home/dfc/go/src/testing/example.go:38
|
||||||
|
// testing.(*M).Run
|
||||||
|
// /home/dfc/go/src/testing/testing.go:744
|
||||||
|
// main.main
|
||||||
|
// /github.com/friendsofgo/errors/_test/_testmain.go:104
|
||||||
|
// runtime.main
|
||||||
|
// /home/dfc/go/src/runtime/proc.go:183
|
||||||
|
// runtime.goexit
|
||||||
|
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
||||||
|
// github.com/friendsofgo/errors_test.fn
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:48: inner
|
||||||
|
// github.com/friendsofgo/errors_test.fn
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:49: middle
|
||||||
|
// github.com/friendsofgo/errors_test.fn
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:50: outer
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWrapf() {
|
||||||
|
cause := errors.New("whoops")
|
||||||
|
err := errors.Wrapf(cause, "oh noes #%d", 2)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
// Output: oh noes #2: whoops
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleErrorf_extended() {
|
||||||
|
err := errors.Errorf("whoops: %s", "foo")
|
||||||
|
fmt.Printf("%+v", err)
|
||||||
|
|
||||||
|
// Example output:
|
||||||
|
// whoops: foo
|
||||||
|
// github.com/friendsofgo/errors_test.ExampleErrorf
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:101
|
||||||
|
// testing.runExample
|
||||||
|
// /home/dfc/go/src/testing/example.go:114
|
||||||
|
// testing.RunExamples
|
||||||
|
// /home/dfc/go/src/testing/example.go:38
|
||||||
|
// testing.(*M).Run
|
||||||
|
// /home/dfc/go/src/testing/testing.go:744
|
||||||
|
// main.main
|
||||||
|
// /github.com/friendsofgo/errors/_test/_testmain.go:102
|
||||||
|
// runtime.main
|
||||||
|
// /home/dfc/go/src/runtime/proc.go:183
|
||||||
|
// runtime.goexit
|
||||||
|
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_stackTrace() {
|
||||||
|
type stackTracer interface {
|
||||||
|
StackTrace() errors.StackTrace
|
||||||
|
}
|
||||||
|
|
||||||
|
err, ok := errors.Cause(fn()).(stackTracer)
|
||||||
|
if !ok {
|
||||||
|
panic("oops, err does not implement stackTracer")
|
||||||
|
}
|
||||||
|
|
||||||
|
st := err.StackTrace()
|
||||||
|
fmt.Printf("%+v", st[0:2]) // top two frames
|
||||||
|
|
||||||
|
// Example output:
|
||||||
|
// github.com/friendsofgo/errors_test.fn
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:47
|
||||||
|
// github.com/friendsofgo/errors_test.Example_stackTrace
|
||||||
|
// /home/dfc/src/github.com/friendsofgo/errors/example_test.go:127
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCause_printf() {
|
||||||
|
err := errors.Wrap(func() error {
|
||||||
|
return func() error {
|
||||||
|
return errors.Errorf("hello %s", fmt.Sprintf("world"))
|
||||||
|
}()
|
||||||
|
}(), "failed")
|
||||||
|
|
||||||
|
fmt.Printf("%v", err)
|
||||||
|
|
||||||
|
// Output: failed: hello world
|
||||||
|
}
|
|
@ -0,0 +1,560 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
//import (
|
||||||
|
// "errors"
|
||||||
|
// "fmt"
|
||||||
|
// "io"
|
||||||
|
// "regexp"
|
||||||
|
// "strings"
|
||||||
|
// "testing"
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//func TestFormatNew(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// error
|
||||||
|
// format string
|
||||||
|
// want string
|
||||||
|
// }{{
|
||||||
|
// New("error"),
|
||||||
|
// "%s",
|
||||||
|
// "error",
|
||||||
|
// }, {
|
||||||
|
// New("error"),
|
||||||
|
// "%v",
|
||||||
|
// "error",
|
||||||
|
// }, {
|
||||||
|
// New("error"),
|
||||||
|
// "%+v",
|
||||||
|
// "error\n" +
|
||||||
|
// "game/errors.TestFormatNew\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:26",
|
||||||
|
// }, {
|
||||||
|
// New("error"),
|
||||||
|
// "%q",
|
||||||
|
// `"error"`,
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFormatErrorf(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// error
|
||||||
|
// format string
|
||||||
|
// want string
|
||||||
|
// }{{
|
||||||
|
// Errorf("%s", "error"),
|
||||||
|
// "%s",
|
||||||
|
// "error",
|
||||||
|
// }, {
|
||||||
|
// Errorf("%s", "error"),
|
||||||
|
// "%v",
|
||||||
|
// "error",
|
||||||
|
// }, {
|
||||||
|
// Errorf("%s", "error"),
|
||||||
|
// "%+v",
|
||||||
|
// "error\n" +
|
||||||
|
// "game/errors.TestFormatErrorf\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:56",
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFormatWrap(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// error
|
||||||
|
// format string
|
||||||
|
// want string
|
||||||
|
// }{{
|
||||||
|
// Wrap(New("error"), "error2"),
|
||||||
|
// "%s",
|
||||||
|
// "error2: error",
|
||||||
|
// }, {
|
||||||
|
// Wrap(New("error"), "error2"),
|
||||||
|
// "%v",
|
||||||
|
// "error2: error",
|
||||||
|
// }, {
|
||||||
|
// Wrap(New("error"), "error2"),
|
||||||
|
// "%+v",
|
||||||
|
// "error\n" +
|
||||||
|
// "game/errors.TestFormatWrap\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:82",
|
||||||
|
// }, {
|
||||||
|
// Wrap(io.EOF, "error"),
|
||||||
|
// "%s",
|
||||||
|
// "error: EOF",
|
||||||
|
// }, {
|
||||||
|
// Wrap(io.EOF, "error"),
|
||||||
|
// "%v",
|
||||||
|
// "error: EOF",
|
||||||
|
// }, {
|
||||||
|
// Wrap(io.EOF, "error"),
|
||||||
|
// "%+v",
|
||||||
|
// "EOF\n" +
|
||||||
|
// "error\n" +
|
||||||
|
// "game/errors.TestFormatWrap\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:96",
|
||||||
|
// }, {
|
||||||
|
// Wrap(Wrap(io.EOF, "error1"), "error2"),
|
||||||
|
// "%+v",
|
||||||
|
// "EOF\n" +
|
||||||
|
// "error1\n" +
|
||||||
|
// "game/errors.TestFormatWrap\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:103\n",
|
||||||
|
// }, {
|
||||||
|
// Wrap(New("error with space"), "context"),
|
||||||
|
// "%q",
|
||||||
|
// `"context: error with space"`,
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFormatWrapf(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// error
|
||||||
|
// format string
|
||||||
|
// want string
|
||||||
|
// }{{
|
||||||
|
// Wrapf(io.EOF, "error%d", 2),
|
||||||
|
// "%s",
|
||||||
|
// "error2: EOF",
|
||||||
|
// }, {
|
||||||
|
// Wrapf(io.EOF, "error%d", 2),
|
||||||
|
// "%v",
|
||||||
|
// "error2: EOF",
|
||||||
|
// }, {
|
||||||
|
// Wrapf(io.EOF, "error%d", 2),
|
||||||
|
// "%+v",
|
||||||
|
// "EOF\n" +
|
||||||
|
// "error2\n" +
|
||||||
|
// "game/errors.TestFormatWrapf\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:134",
|
||||||
|
// }, {
|
||||||
|
// Wrapf(New("error"), "error%d", 2),
|
||||||
|
// "%s",
|
||||||
|
// "error2: error",
|
||||||
|
// }, {
|
||||||
|
// Wrapf(New("error"), "error%d", 2),
|
||||||
|
// "%v",
|
||||||
|
// "error2: error",
|
||||||
|
// }, {
|
||||||
|
// Wrapf(New("error"), "error%d", 2),
|
||||||
|
// "%+v",
|
||||||
|
// "error\n" +
|
||||||
|
// "game/errors.TestFormatWrapf\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:149",
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFormatWithStack(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// error
|
||||||
|
// format string
|
||||||
|
// want []string
|
||||||
|
// }{{
|
||||||
|
// WithStack(io.EOF),
|
||||||
|
// "%s",
|
||||||
|
// []string{"EOF"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(io.EOF),
|
||||||
|
// "%v",
|
||||||
|
// []string{"EOF"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(io.EOF),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"EOF",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:175"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(New("error")),
|
||||||
|
// "%s",
|
||||||
|
// []string{"error"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(New("error")),
|
||||||
|
// "%v",
|
||||||
|
// []string{"error"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(New("error")),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"error",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:189",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:189"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(WithStack(io.EOF)),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"EOF",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:197",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:197"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(WithStack(Wrapf(io.EOF, "message"))),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"EOF",
|
||||||
|
// "message",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:205",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:205",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:205"},
|
||||||
|
// }, {
|
||||||
|
// WithStack(Errorf("error%d", 1)),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"error1",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:216",
|
||||||
|
// "game/errors.TestFormatWithStack\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:216"},
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFormatWithMessage(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// error
|
||||||
|
// format string
|
||||||
|
// want []string
|
||||||
|
// }{{
|
||||||
|
// WithMessage(New("error"), "error2"),
|
||||||
|
// "%s",
|
||||||
|
// []string{"error2: error"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(New("error"), "error2"),
|
||||||
|
// "%v",
|
||||||
|
// []string{"error2: error"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(New("error"), "error2"),
|
||||||
|
// "%+v",
|
||||||
|
// []string{
|
||||||
|
// "error",
|
||||||
|
// "game/errors.TestFormatWithMessage\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:244",
|
||||||
|
// "error2"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(io.EOF, "addition1"),
|
||||||
|
// "%s",
|
||||||
|
// []string{"addition1: EOF"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(io.EOF, "addition1"),
|
||||||
|
// "%v",
|
||||||
|
// []string{"addition1: EOF"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(io.EOF, "addition1"),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"EOF", "addition1"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
||||||
|
// "%v",
|
||||||
|
// []string{"addition2: addition1: EOF"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"EOF", "addition1", "addition2"},
|
||||||
|
// }, {
|
||||||
|
// Wrap(WithMessage(io.EOF, "error1"), "error2"),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"EOF", "error1", "error2",
|
||||||
|
// "game/errors.TestFormatWithMessage\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:272"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(Errorf("error%d", 1), "error2"),
|
||||||
|
// "%+v",
|
||||||
|
// []string{"error1",
|
||||||
|
// "game/errors.TestFormatWithMessage\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:278",
|
||||||
|
// "error2"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(WithStack(io.EOF), "error"),
|
||||||
|
// "%+v",
|
||||||
|
// []string{
|
||||||
|
// "EOF",
|
||||||
|
// "game/errors.TestFormatWithMessage\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:285",
|
||||||
|
// "error"},
|
||||||
|
// }, {
|
||||||
|
// WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
|
||||||
|
// "%+v",
|
||||||
|
// []string{
|
||||||
|
// "EOF",
|
||||||
|
// "game/errors.TestFormatWithMessage\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:293",
|
||||||
|
// "inside-error",
|
||||||
|
// "game/errors.TestFormatWithMessage\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:293",
|
||||||
|
// "outside-error"},
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFormatGeneric(t *testing.T) {
|
||||||
|
// starts := []struct {
|
||||||
|
// err error
|
||||||
|
// want []string
|
||||||
|
// }{
|
||||||
|
// {New("new-error"), []string{
|
||||||
|
// "new-error",
|
||||||
|
// "game/errors.TestFormatGeneric\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:315"},
|
||||||
|
// }, {Errorf("errorf-error"), []string{
|
||||||
|
// "errorf-error",
|
||||||
|
// "game/errors.TestFormatGeneric\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:319"},
|
||||||
|
// }, {errors.New("errors-new-error"), []string{
|
||||||
|
// "errors-new-error"},
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// wrappers := []wrapper{
|
||||||
|
// {
|
||||||
|
// func(err error) error { return WithMessage(err, "with-message") },
|
||||||
|
// []string{"with-message"},
|
||||||
|
// }, {
|
||||||
|
// func(err error) error { return WithStack(err) },
|
||||||
|
// []string{
|
||||||
|
// "game/errors.(func·002|TestFormatGeneric.func2)\n\t" +
|
||||||
|
// ".+game/errors/errors/format_test.go:333",
|
||||||
|
// },
|
||||||
|
// }, {
|
||||||
|
// func(err error) error { return Wrap(err, "wrap-error") },
|
||||||
|
// []string{
|
||||||
|
// "wrap-error",
|
||||||
|
// "game/errors.(func·003|TestFormatGeneric.func3)\n\t" +
|
||||||
|
// ".+game/errors/errors/format_test.go:339",
|
||||||
|
// },
|
||||||
|
// }, {
|
||||||
|
// func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
|
||||||
|
// []string{
|
||||||
|
// "wrapf-error1",
|
||||||
|
// "game/errors.(func·004|TestFormatGeneric.func4)\n\t" +
|
||||||
|
// ".+game/errors/errors/format_test.go:346",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for s := range starts {
|
||||||
|
// err := starts[s].err
|
||||||
|
// want := starts[s].want
|
||||||
|
// testFormatCompleteCompare(t, s, err, "%+v", want, false)
|
||||||
|
// testGenericRecursive(t, err, want, wrappers, 3)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func wrappedNew(message string) error { // This function will be mid-stack inlined in go 1.12+
|
||||||
|
// return New(message)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFormatWrappedNew(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// error
|
||||||
|
// format string
|
||||||
|
// want string
|
||||||
|
// }{{
|
||||||
|
// wrappedNew("error"),
|
||||||
|
// "%+v",
|
||||||
|
// "error\n" +
|
||||||
|
// "game/errors.wrappedNew\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:364\n" +
|
||||||
|
// "game/errors.TestFormatWrappedNew\n" +
|
||||||
|
// "\t.+game/errors/errors/format_test.go:373",
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//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)
|
||||||
|
//
|
||||||
|
// if len(wantLines) > len(gotLines) {
|
||||||
|
// t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i, w := range wantLines {
|
||||||
|
// match, err := regexp.MatchString(w, gotLines[i])
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
// if !match {
|
||||||
|
// t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//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.
|
||||||
|
////
|
||||||
|
//// 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
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
|
||||||
|
// var blocks []string
|
||||||
|
//
|
||||||
|
// stack := ""
|
||||||
|
// wasStack := false
|
||||||
|
// lines := map[string]bool{} // already found lines
|
||||||
|
//
|
||||||
|
// for _, l := range strings.Split(input, "\n") {
|
||||||
|
// isStackLine := stackLineR.MatchString(l)
|
||||||
|
//
|
||||||
|
// switch {
|
||||||
|
// case !isStackLine && wasStack:
|
||||||
|
// blocks = append(blocks, stack, l)
|
||||||
|
// stack = ""
|
||||||
|
// lines = map[string]bool{}
|
||||||
|
// case isStackLine:
|
||||||
|
// if wasStack {
|
||||||
|
// // Detecting two stacks after another, possible cause lines match in
|
||||||
|
// // our tests due to WithStack(WithStack(io.EOF)) on same line.
|
||||||
|
// if detectStackboundaries {
|
||||||
|
// if lines[l] {
|
||||||
|
// if len(stack) == 0 {
|
||||||
|
// return nil, errors.New("len of block must not be zero here")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// blocks = append(blocks, stack)
|
||||||
|
// stack = l
|
||||||
|
// lines = map[string]bool{l: true}
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// stack = stack + "\n" + l
|
||||||
|
// } else {
|
||||||
|
// stack = l
|
||||||
|
// }
|
||||||
|
// lines[l] = true
|
||||||
|
// case !isStackLine && !wasStack:
|
||||||
|
// blocks = append(blocks, l)
|
||||||
|
// default:
|
||||||
|
// return nil, errors.New("must not happen")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// wasStack = isStackLine
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Use up stack
|
||||||
|
// if stack != "" {
|
||||||
|
// blocks = append(blocks, stack)
|
||||||
|
// }
|
||||||
|
// return blocks, nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
|
||||||
|
// gotStr := fmt.Sprintf(format, arg)
|
||||||
|
//
|
||||||
|
// got, err := parseBlocks(gotStr, detectStackBoundaries)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if len(got) != len(want) {
|
||||||
|
// t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
|
||||||
|
// n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := range got {
|
||||||
|
// if strings.ContainsAny(want[i], "\n") {
|
||||||
|
// // Match as stack
|
||||||
|
// match, err := regexp.MatchString(want[i], got[i])
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
// if !match {
|
||||||
|
// t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
|
||||||
|
// n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // Match as message
|
||||||
|
// if got[i] != want[i] {
|
||||||
|
// t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//type wrapper struct {
|
||||||
|
// wrap func(err error) error
|
||||||
|
// want []string
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func prettyBlocks(blocks []string) string {
|
||||||
|
// var out []string
|
||||||
|
//
|
||||||
|
// for _, b := range blocks {
|
||||||
|
// out = append(out, fmt.Sprintf("%v", b))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return " " + strings.Join(out, "\n ")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
|
||||||
|
// if len(beforeWant) == 0 {
|
||||||
|
// panic("beforeWant must not be empty")
|
||||||
|
// }
|
||||||
|
// for _, w := range list {
|
||||||
|
// if len(w.want) == 0 {
|
||||||
|
// panic("want must not be empty")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// err := w.wrap(beforeErr)
|
||||||
|
//
|
||||||
|
// // Copy required cause append(beforeWant, ..) modified beforeWant subtly.
|
||||||
|
// beforeCopy := make([]string, len(beforeWant))
|
||||||
|
// copy(beforeCopy, beforeWant)
|
||||||
|
//
|
||||||
|
// beforeWant := beforeCopy
|
||||||
|
// last := len(beforeWant) - 1
|
||||||
|
// var want []string
|
||||||
|
//
|
||||||
|
// // Merge two stacks behind each other.
|
||||||
|
// if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
|
||||||
|
// want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
|
||||||
|
// } else {
|
||||||
|
// want = append(beforeWant, w.want...)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
|
||||||
|
// if maxDepth > 0 {
|
||||||
|
// testGenericRecursive(t, err, want, list, maxDepth-1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFrameMarshalText(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
Frame
|
||||||
|
want string
|
||||||
|
}{{
|
||||||
|
initpc,
|
||||||
|
`^game/errors\.init(\.ializers)? .+/game/errors/stack_test.go:\d+$`,
|
||||||
|
}, {
|
||||||
|
0,
|
||||||
|
`^unknown$`,
|
||||||
|
}}
|
||||||
|
for i, tt := range tests {
|
||||||
|
got, err := tt.Frame.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !regexp.MustCompile(tt.want).Match(got) {
|
||||||
|
t.Errorf("test %d: MarshalJSON:\n got %q\n want %q", i+1, string(got), tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrameMarshalJSON(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
Frame
|
||||||
|
want string
|
||||||
|
}{{
|
||||||
|
initpc,
|
||||||
|
`^"game/errors\.init(\.ializers)? .+/game/errors/stack_test.go:\d+"$`,
|
||||||
|
}, {
|
||||||
|
0,
|
||||||
|
`^"unknown"$`,
|
||||||
|
}}
|
||||||
|
for i, tt := range tests {
|
||||||
|
got, err := json.Marshal(tt.Frame)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !regexp.MustCompile(tt.want).Match(got) {
|
||||||
|
t.Errorf("test %d: MarshalJSON:\n got %q\n want %q", i+1, string(got), tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frame represents a program counter inside a stack frame.
|
||||||
|
// For historical reasons if Frame is interpreted as a uintptr
|
||||||
|
// its value represents the program counter + 1.
|
||||||
|
type Frame uintptr
|
||||||
|
|
||||||
|
// pc returns the program counter for this frame;
|
||||||
|
// multiple frames may have the same PC value.
|
||||||
|
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||||
|
|
||||||
|
// file returns the full path to the file that contains the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) file() string {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
file, _ := fn.FileLine(f.pc())
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// line returns the line number of source code of the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) line() int {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
_, line := fn.FileLine(f.pc())
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// name returns the name of this function, if known.
|
||||||
|
func (f Frame) name() string {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return fn.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats the frame according to the fmt.Formatter interface.
|
||||||
|
//
|
||||||
|
// %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 (<funcname>\n\t<path>)
|
||||||
|
// %+v equivalent to %+s:%d
|
||||||
|
func (f Frame) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 's':
|
||||||
|
switch {
|
||||||
|
case s.Flag('-'):
|
||||||
|
io.WriteString(s, path.Ext(f.name()))
|
||||||
|
io.WriteString(s, " ")
|
||||||
|
dir, file := path.Split(f.file())
|
||||||
|
io.WriteString(s, path.Join(path.Base(path.Dir(dir)), path.Base(dir), file))
|
||||||
|
case s.Flag('+'):
|
||||||
|
io.WriteString(s, f.name())
|
||||||
|
io.WriteString(s, "\n\t")
|
||||||
|
io.WriteString(s, f.file())
|
||||||
|
default:
|
||||||
|
io.WriteString(s, path.Base(f.file()))
|
||||||
|
}
|
||||||
|
case 'd':
|
||||||
|
io.WriteString(s, strconv.Itoa(f.line()))
|
||||||
|
case 'n':
|
||||||
|
io.WriteString(s, funcname(f.name()))
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('-') {
|
||||||
|
io.WriteString(s, "[")
|
||||||
|
}
|
||||||
|
f.Format(s, 's')
|
||||||
|
io.WriteString(s, ":")
|
||||||
|
f.Format(s, 'd')
|
||||||
|
if s.Flag('-') {
|
||||||
|
io.WriteString(s, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText formats a stacktrace Frame as a text string. The output is the
|
||||||
|
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
|
||||||
|
func (f Frame) MarshalText() ([]byte, error) {
|
||||||
|
name := f.name()
|
||||||
|
if name == "unknown" {
|
||||||
|
return []byte(name), nil
|
||||||
|
}
|
||||||
|
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||||
|
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
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
for _, f := range st {
|
||||||
|
io.WriteString(s, "\n")
|
||||||
|
f.Format(s, verb)
|
||||||
|
}
|
||||||
|
case s.Flag('-'):
|
||||||
|
for _, f := range st {
|
||||||
|
io.WriteString(s, " ")
|
||||||
|
f.Format(s, verb)
|
||||||
|
}
|
||||||
|
case s.Flag('#'):
|
||||||
|
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||||
|
default:
|
||||||
|
st.formatSlice(s, verb)
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
st.formatSlice(s, verb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(s fmt.State, verb rune) {
|
||||||
|
io.WriteString(s, "[")
|
||||||
|
for i, f := range st {
|
||||||
|
if i > 0 {
|
||||||
|
io.WriteString(s, " ")
|
||||||
|
}
|
||||||
|
f.Format(s, verb)
|
||||||
|
}
|
||||||
|
io.WriteString(s, "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack represents a stack of program counters.
|
||||||
|
type stack []uintptr
|
||||||
|
|
||||||
|
func (s *stack) Format(st fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case st.Flag('+'):
|
||||||
|
for _, pc := range *s {
|
||||||
|
f := Frame(pc)
|
||||||
|
fmt.Fprintf(st, "\n%+v", f)
|
||||||
|
}
|
||||||
|
case st.Flag('-'):
|
||||||
|
for _, pc := range *s {
|
||||||
|
f := Frame(pc)
|
||||||
|
fmt.Fprintf(st, " < %-v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stack) StackTrace() StackTrace {
|
||||||
|
f := make([]Frame, len(*s))
|
||||||
|
for i := 0; i < len(f); i++ {
|
||||||
|
f[i] = Frame((*s)[i])
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func callers() *stack {
|
||||||
|
const depth = 7
|
||||||
|
var pcs [depth]uintptr
|
||||||
|
n := runtime.Callers(3, pcs[:])
|
||||||
|
var st stack = pcs[0:n]
|
||||||
|
return &st
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||||
|
func funcname(name string) string {
|
||||||
|
i := strings.LastIndex(name, "/")
|
||||||
|
name = name[i+1:]
|
||||||
|
i = strings.Index(name, ".")
|
||||||
|
return name[i+1:]
|
||||||
|
}
|
|
@ -0,0 +1,250 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "fmt"
|
||||||
|
"runtime"
|
||||||
|
// "testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initpc = caller()
|
||||||
|
|
||||||
|
//type X struct{}
|
||||||
|
//
|
||||||
|
//// val returns a Frame pointing to itself.
|
||||||
|
//func (x X) val() Frame {
|
||||||
|
// return caller()
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// ptr returns a Frame pointing to itself.
|
||||||
|
//func (x *X) ptr() Frame {
|
||||||
|
// return caller()
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFrameFormat(t *testing.T) {
|
||||||
|
// var tests = []struct {
|
||||||
|
// Frame
|
||||||
|
// format string
|
||||||
|
// want string
|
||||||
|
// }{{
|
||||||
|
// initpc,
|
||||||
|
// "%s",
|
||||||
|
// "stack_test.go",
|
||||||
|
// }, {
|
||||||
|
// initpc,
|
||||||
|
// "%+s",
|
||||||
|
// "game/errors/errors.init\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go",
|
||||||
|
// }, {
|
||||||
|
// 0,
|
||||||
|
// "%s",
|
||||||
|
// "unknown",
|
||||||
|
// }, {
|
||||||
|
// 0,
|
||||||
|
// "%+s",
|
||||||
|
// "unknown",
|
||||||
|
// }, {
|
||||||
|
// initpc,
|
||||||
|
// "%d",
|
||||||
|
// "9",
|
||||||
|
// }, {
|
||||||
|
// 0,
|
||||||
|
// "%d",
|
||||||
|
// "0",
|
||||||
|
// }, {
|
||||||
|
// initpc,
|
||||||
|
// "%n",
|
||||||
|
// "init",
|
||||||
|
// }, {
|
||||||
|
// func() Frame {
|
||||||
|
// var x X
|
||||||
|
// return x.ptr()
|
||||||
|
// }(),
|
||||||
|
// "%n",
|
||||||
|
// `\(\*X\).ptr`,
|
||||||
|
// }, {
|
||||||
|
// func() Frame {
|
||||||
|
// var x X
|
||||||
|
// return x.val()
|
||||||
|
// }(),
|
||||||
|
// "%n",
|
||||||
|
// "X.val",
|
||||||
|
// }, {
|
||||||
|
// 0,
|
||||||
|
// "%n",
|
||||||
|
// "",
|
||||||
|
// }, {
|
||||||
|
// initpc,
|
||||||
|
// "%v",
|
||||||
|
// "stack_test.go:9",
|
||||||
|
// }, {
|
||||||
|
// initpc,
|
||||||
|
// "%+v",
|
||||||
|
// "game/errors/errors.init\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:9",
|
||||||
|
// }, {
|
||||||
|
// 0,
|
||||||
|
// "%v",
|
||||||
|
// "unknown:0",
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestFuncname(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// name, want string
|
||||||
|
// }{
|
||||||
|
// {"", ""},
|
||||||
|
// {"runtime.main", "main"},
|
||||||
|
// {"game/errors/errors.funcname", "funcname"},
|
||||||
|
// {"funcname", "funcname"},
|
||||||
|
// {"io.copyBuffer", "copyBuffer"},
|
||||||
|
// {"main.(*R).Write", "(*R).Write"},
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for _, tt := range tests {
|
||||||
|
// got := funcname(tt.name)
|
||||||
|
// want := tt.want
|
||||||
|
// if got != want {
|
||||||
|
// t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestStackTrace(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// err error
|
||||||
|
// want []string
|
||||||
|
// }{{
|
||||||
|
// New("ooh"), []string{
|
||||||
|
// "game/errors/errors.TestStackTrace\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:121",
|
||||||
|
// },
|
||||||
|
// }, {
|
||||||
|
// Wrap(New("ooh"), "ahh"), []string{
|
||||||
|
// "game/errors/errors.TestStackTrace\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:126", // this is the stack of Wrap, not New
|
||||||
|
// },
|
||||||
|
// }, {
|
||||||
|
// Cause(Wrap(New("ooh"), "ahh")), []string{
|
||||||
|
// "game/errors/errors.TestStackTrace\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:131", // this is the stack of New
|
||||||
|
// },
|
||||||
|
// }, {
|
||||||
|
// func() error { return New("ooh") }(), []string{
|
||||||
|
// `game/errors/errors.TestStackTrace.func1` +
|
||||||
|
// "\n\t.+game/errors/errors/stack_test.go:136", // this is the stack of New
|
||||||
|
// "game/errors/errors.TestStackTrace\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:136", // this is the stack of New's caller
|
||||||
|
// },
|
||||||
|
// }, {
|
||||||
|
// Cause(func() error {
|
||||||
|
// return func() error {
|
||||||
|
// return Errorf("hello %s", fmt.Sprintf("world"))
|
||||||
|
// }()
|
||||||
|
// }()), []string{
|
||||||
|
// `game/errors/errors.TestStackTrace.func2.1` +
|
||||||
|
// "\n\t.+game/errors/errors/stack_test.go:145", // this is the stack of Errorf
|
||||||
|
// `game/errors/errors.TestStackTrace.func2` +
|
||||||
|
// "\n\t.+game/errors/errors/stack_test.go:146", // this is the stack of Errorf's caller
|
||||||
|
// "game/errors/errors.TestStackTrace\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// x, ok := tt.err.(interface {
|
||||||
|
// StackTrace() StackTrace
|
||||||
|
// })
|
||||||
|
// if !ok {
|
||||||
|
// t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// st := x.StackTrace()
|
||||||
|
// for j, want := range tt.want {
|
||||||
|
// testFormatRegexp(t, i, st[j], "%+v", want)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func stackTrace() StackTrace {
|
||||||
|
// const depth = 8
|
||||||
|
// var pcs [depth]uintptr
|
||||||
|
// n := runtime.Callers(1, pcs[:])
|
||||||
|
// var st stack = pcs[0:n]
|
||||||
|
// return st.StackTrace()
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func TestStackTraceFormat(t *testing.T) {
|
||||||
|
// tests := []struct {
|
||||||
|
// StackTrace
|
||||||
|
// format string
|
||||||
|
// want string
|
||||||
|
// }{{
|
||||||
|
// nil,
|
||||||
|
// "%s",
|
||||||
|
// `\[\]`,
|
||||||
|
// }, {
|
||||||
|
// nil,
|
||||||
|
// "%v",
|
||||||
|
// `\[\]`,
|
||||||
|
// }, {
|
||||||
|
// nil,
|
||||||
|
// "%+v",
|
||||||
|
// "",
|
||||||
|
// }, {
|
||||||
|
// nil,
|
||||||
|
// "%#v",
|
||||||
|
// `\[\]errors.Frame\(nil\)`,
|
||||||
|
// }, {
|
||||||
|
// make(StackTrace, 0),
|
||||||
|
// "%s",
|
||||||
|
// `\[\]`,
|
||||||
|
// }, {
|
||||||
|
// make(StackTrace, 0),
|
||||||
|
// "%v",
|
||||||
|
// `\[\]`,
|
||||||
|
// }, {
|
||||||
|
// make(StackTrace, 0),
|
||||||
|
// "%+v",
|
||||||
|
// "",
|
||||||
|
// }, {
|
||||||
|
// make(StackTrace, 0),
|
||||||
|
// "%#v",
|
||||||
|
// `\[\]errors.Frame{}`,
|
||||||
|
// }, {
|
||||||
|
// stackTrace()[:2],
|
||||||
|
// "%s",
|
||||||
|
// `\[stack_test.go stack_test.go\]`,
|
||||||
|
// }, {
|
||||||
|
// stackTrace()[:2],
|
||||||
|
// "%v",
|
||||||
|
// `\[stack_test.go:174 stack_test.go:221\]`,
|
||||||
|
// }, {
|
||||||
|
// stackTrace()[:2],
|
||||||
|
// "%+v",
|
||||||
|
// "\n" +
|
||||||
|
// "game/errors/errors.stackTrace\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:174\n" +
|
||||||
|
// "game/errors/errors.TestStackTraceFormat\n" +
|
||||||
|
// "\t.+game/errors/errors/stack_test.go:225",
|
||||||
|
// }, {
|
||||||
|
// stackTrace()[:2],
|
||||||
|
// "%#v",
|
||||||
|
// `\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`,
|
||||||
|
// }}
|
||||||
|
//
|
||||||
|
// for i, tt := range tests {
|
||||||
|
// testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
// a version of runtime.Caller that returns a Frame, not a uintptr.
|
||||||
|
func caller() Frame {
|
||||||
|
var pcs [3]uintptr
|
||||||
|
n := runtime.Callers(2, pcs[:])
|
||||||
|
frames := runtime.CallersFrames(pcs[:n])
|
||||||
|
frame, _ := frames.Next()
|
||||||
|
return Frame(frame.PC)
|
||||||
|
}
|
Loading…
Reference in New Issue