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