Adding upstream version 0.0~git20250501.cd50c6a.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
947813c282
commit
d8f2a7c92a
16 changed files with 3363 additions and 0 deletions
16
.build.yml
Normal file
16
.build.yml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
image: archlinux
|
||||||
|
packages:
|
||||||
|
- go
|
||||||
|
sources:
|
||||||
|
- https://github.com/go-ap/errors
|
||||||
|
environment:
|
||||||
|
GO111MODULE: 'on'
|
||||||
|
tasks:
|
||||||
|
- tests: |
|
||||||
|
cd errors
|
||||||
|
make test
|
||||||
|
- coverage: |
|
||||||
|
set -a +x
|
||||||
|
cd errors && make coverage
|
||||||
|
GIT_SHA=$(git rev-parse --verify HEAD)
|
||||||
|
GIT_BRANCH=$(git name-rev --name-only HEAD)
|
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Gogland
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tools
|
||||||
|
*.out
|
||||||
|
*.coverprofile
|
||||||
|
|
||||||
|
*pkg
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Golang ActitvityPub
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
22
Makefile
Normal file
22
Makefile
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
GO ?= go
|
||||||
|
TEST := $(GO) test
|
||||||
|
TEST_FLAGS ?= -v
|
||||||
|
TEST_TARGET ?= ./...
|
||||||
|
GO111MODULE=on
|
||||||
|
PROJECT_NAME := $(shell basename $(PWD))
|
||||||
|
|
||||||
|
.PHONY: test coverage clean download
|
||||||
|
|
||||||
|
download:
|
||||||
|
$(GO) mod download all
|
||||||
|
|
||||||
|
test: download
|
||||||
|
$(TEST) $(TEST_FLAGS) $(TEST_TARGET)
|
||||||
|
|
||||||
|
coverage: TEST_TARGET := .
|
||||||
|
coverage: TEST_FLAGS += -covermode=count -coverprofile $(PROJECT_NAME).coverprofile
|
||||||
|
coverage: test
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) -v *.coverprofile
|
||||||
|
|
6
README.md
Normal file
6
README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
[](https://raw.githubusercontent.com/go-ap/errors/master/LICENSE)
|
||||||
|
[](https://builds.sr.ht/~mariusor/errors)
|
||||||
|
[](https://codecov.io/gh/go-ap/errors)
|
||||||
|
[](https://goreportcard.com/report/github.com/go-ap/errors)
|
||||||
|
<!--[](https://www.codacy.com/app/go-ap/errors/dashboard)-->
|
85
decoding.go
Normal file
85
decoding.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/valyala/fastjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnmarshalJSON(data []byte) ([]error, error) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
p := fastjson.Parser{}
|
||||||
|
val, err := p.ParseBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := val.Get("errors")
|
||||||
|
if v == nil {
|
||||||
|
return nil, wrap(nil, "invalid errors array")
|
||||||
|
}
|
||||||
|
items := make([]error, 0)
|
||||||
|
switch v.Type() {
|
||||||
|
case fastjson.TypeArray:
|
||||||
|
for _, v := range v.GetArray() {
|
||||||
|
status := v.GetInt("status")
|
||||||
|
localErr := errorFromStatus(status)
|
||||||
|
if err := localErr.UnmarshalJSON([]byte(v.String())); err == nil {
|
||||||
|
items = append(items, localErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items, err
|
||||||
|
case fastjson.TypeObject:
|
||||||
|
status := v.GetInt("status")
|
||||||
|
localErr := errorFromStatus(status)
|
||||||
|
if err := localErr.UnmarshalJSON([]byte(v.String())); err == nil {
|
||||||
|
items = append(items, localErr)
|
||||||
|
}
|
||||||
|
case fastjson.TypeString:
|
||||||
|
it := new(Err)
|
||||||
|
it.m = string(data)
|
||||||
|
items = append(items, it)
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Err) UnmarshalJSON(data []byte) error {
|
||||||
|
if m := fastjson.GetString(data, "message"); len(m) > 0 {
|
||||||
|
e.m = m
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notFound) UnmarshalJSON(data []byte) error {
|
||||||
|
return n.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (m *methodNotAllowed) UnmarshalJSON(data []byte) error {
|
||||||
|
return m.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (n *notValid) UnmarshalJSON(data []byte) error {
|
||||||
|
return n.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (f *forbidden) UnmarshalJSON(data []byte) error {
|
||||||
|
return f.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (n *notImplemented) UnmarshalJSON(data []byte) error {
|
||||||
|
return n.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (b *badRequest) UnmarshalJSON(data []byte) error {
|
||||||
|
return b.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (u *unauthorized) UnmarshalJSON(data []byte) error {
|
||||||
|
return u.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (n *notSupported) UnmarshalJSON(data []byte) error {
|
||||||
|
return n.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (t *timeout) UnmarshalJSON(data []byte) error {
|
||||||
|
return t.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (b *badGateway) UnmarshalJSON(data []byte) error {
|
||||||
|
return b.Err.UnmarshalJSON(data)
|
||||||
|
}
|
||||||
|
func (s *serviceUnavailable) UnmarshalJSON(data []byte) error {
|
||||||
|
return s.Err.UnmarshalJSON(data)
|
||||||
|
}
|
99
decoding_test.go
Normal file
99
decoding_test.go
Normal file
File diff suppressed because one or more lines are too long
166
errors.go
Normal file
166
errors.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Export a number of functions or variables from package errors.
|
||||||
|
var (
|
||||||
|
As = errors.As
|
||||||
|
Is = errors.Is
|
||||||
|
Unwrap = errors.Unwrap
|
||||||
|
//Join = errors.Join
|
||||||
|
)
|
||||||
|
|
||||||
|
// IncludeBacktrace is a static variable that decides if when creating an error we store the backtrace with it.
|
||||||
|
var IncludeBacktrace = true
|
||||||
|
|
||||||
|
// Err is our custom error type that can store backtrace, file and line number
|
||||||
|
type Err struct {
|
||||||
|
m string
|
||||||
|
c error
|
||||||
|
t stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Err) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, e.m)
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
if e.c != nil {
|
||||||
|
io.WriteString(s, ": ")
|
||||||
|
io.WriteString(s, fmt.Sprintf("%+s", e.c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'v':
|
||||||
|
e.Format(s, 's')
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
if e.t != nil {
|
||||||
|
io.WriteString(s, "\n\t")
|
||||||
|
e.t.Format(s, 'v')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface
|
||||||
|
func (e Err) Error() string {
|
||||||
|
if IncludeBacktrace {
|
||||||
|
return e.m
|
||||||
|
}
|
||||||
|
s := strings.Builder{}
|
||||||
|
s.WriteString(e.m)
|
||||||
|
if ch := errors.Unwrap(e); ch != nil {
|
||||||
|
s.WriteString(": ")
|
||||||
|
s.WriteString(ch.Error())
|
||||||
|
}
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap implements the errors.Wrapper interface
|
||||||
|
func (e Err) Unwrap() error {
|
||||||
|
return e.c
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackTrace returns the stack trace as returned by the debug.Stack function
|
||||||
|
func (e Err) StackTrace() StackTrace {
|
||||||
|
return e.t.StackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotatef wraps an error with new message
|
||||||
|
func Annotatef(e error, s string, args ...interface{}) *Err {
|
||||||
|
err := wrap(e, s, args...)
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newf creaates a new error
|
||||||
|
func Newf(s string, args ...interface{}) *Err {
|
||||||
|
err := wrap(nil, s, args...)
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf is an alias for Newf
|
||||||
|
func Errorf(s string, args ...interface{}) error {
|
||||||
|
err := wrap(nil, s, args...)
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
// As implements support for errors.As
|
||||||
|
func (e *Err) As(err interface{}) bool {
|
||||||
|
switch x := err.(type) {
|
||||||
|
case **Err:
|
||||||
|
*x = e
|
||||||
|
case *Err:
|
||||||
|
*x = *e
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type StackTracer interface {
|
||||||
|
StackTrace() StackTrace
|
||||||
|
}
|
||||||
|
|
||||||
|
// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack
|
||||||
|
// trace. We check this by seeing whether our stack prefix-matches the cause stack, which
|
||||||
|
// should imply the error was generated directly from our goroutine.
|
||||||
|
func ancestorOfCause(ourStack stack, causeStack StackTrace) bool {
|
||||||
|
// Stack traces are ordered such that the deepest frame is first. We'll want to check
|
||||||
|
// for prefix matching in reverse.
|
||||||
|
//
|
||||||
|
// As an example, imagine we have a prefix-matching stack for ourselves:
|
||||||
|
// [
|
||||||
|
// "github.com/go-ap/processing/processing.Validate",
|
||||||
|
// "testing.tRunner",
|
||||||
|
// "runtime.goexit"
|
||||||
|
// ]
|
||||||
|
//
|
||||||
|
// We'll want to compare this against an error cause that will have happened further
|
||||||
|
// down the stack. An example stack trace from such an error might be:
|
||||||
|
// [
|
||||||
|
// "github.com/go-ap/errors/errors.New",
|
||||||
|
// "testing.tRunner",
|
||||||
|
// "runtime.goexit"
|
||||||
|
// ]
|
||||||
|
//
|
||||||
|
// Their prefix matches, but we'll have to handle the match carefully as we need to match
|
||||||
|
// from back to forward.
|
||||||
|
|
||||||
|
// We can't possibly prefix match if our stack is larger than the cause stack.
|
||||||
|
if len(ourStack) > len(causeStack) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We know the sizes are compatible, so compare program counters from back to front.
|
||||||
|
for idx := 0; idx < len(ourStack); idx++ {
|
||||||
|
if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(e error, s string, args ...interface{}) Err {
|
||||||
|
err := Err{
|
||||||
|
c: e,
|
||||||
|
m: fmt.Sprintf(s, args...),
|
||||||
|
}
|
||||||
|
if IncludeBacktrace {
|
||||||
|
causeStackTracer := new(StackTracer)
|
||||||
|
// If our cause has set a stack trace, and that trace is a child of our own function
|
||||||
|
// as inferred by prefix matching our current program counter stack, then we only want
|
||||||
|
// to decorate the error message rather than add a redundant stack trace.
|
||||||
|
stack := callers(2)
|
||||||
|
if !(As(e, causeStackTracer) && ancestorOfCause(*stack, (*causeStackTracer).StackTrace())) {
|
||||||
|
err.t = *stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
238
errors_test.go
Normal file
238
errors_test.go
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var err = fmt.Errorf("test error")
|
||||||
|
|
||||||
|
func TestFormat(t *testing.T) {
|
||||||
|
IncludeBacktrace = false
|
||||||
|
// %+s check for unwrapped error
|
||||||
|
e1 := Newf("test")
|
||||||
|
str := fmt.Sprintf("%+s", e1)
|
||||||
|
if str != e1.m {
|
||||||
|
t.Errorf("Error message invalid %s, expected %s", str, e1.m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// %+s check for wrapped error
|
||||||
|
e2 := Annotatef(e1, "another")
|
||||||
|
str = fmt.Sprintf("%+s", e2)
|
||||||
|
val := e2.m + ": " + e2.c.Error()
|
||||||
|
if str != val {
|
||||||
|
t.Errorf("Error message invalid %s, expected %s", str, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// %v check for unwrapped error with trace
|
||||||
|
IncludeBacktrace = true
|
||||||
|
e3 := Newf("test1")
|
||||||
|
str = fmt.Sprintf("%+v", e3)
|
||||||
|
if !strings.Contains(str, e3.m) {
|
||||||
|
t.Errorf("Error message %s\n should contain %s", str, e3.m)
|
||||||
|
}
|
||||||
|
if !strings.Contains(str, fmt.Sprintf("%+v", e3.t.StackTrace())) {
|
||||||
|
t.Errorf("Error message %s\n should contain %+v", str, e3.t.StackTrace())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalJSON(t *testing.T) {
|
||||||
|
IncludeBacktrace = true
|
||||||
|
e := Newf("test")
|
||||||
|
|
||||||
|
b, err := e.t.StackTrace().MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalJSON failed with error: %+s", err)
|
||||||
|
}
|
||||||
|
stack := make([]json.RawMessage, 0)
|
||||||
|
err = json.Unmarshal(b, &stack)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("JSON message could not be unmarshaled: %+s", err)
|
||||||
|
}
|
||||||
|
if len(stack) != len(e.t) {
|
||||||
|
t.Errorf("Count of stack frames different after marshaling, expected %d got %d", len(e.t), len(stack))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnotatef(t *testing.T) {
|
||||||
|
testStr := "Annotatef string"
|
||||||
|
te := Annotatef(err, testStr)
|
||||||
|
if te.c != err {
|
||||||
|
t.Errorf("Invalid parent error %T:%s, expected %T:%s", te.c, te.c, err, err)
|
||||||
|
}
|
||||||
|
if te.m != testStr {
|
||||||
|
t.Errorf("Invalid error message %s, expected %s", te.m, testStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var homeVal = "$HOME"
|
||||||
|
|
||||||
|
func TestNewf(t *testing.T) {
|
||||||
|
testStr := "Newf string"
|
||||||
|
te := Newf(testStr)
|
||||||
|
if te.c != nil {
|
||||||
|
t.Errorf("Invalid parent error %T:%s, expected nil", te.c, te.c)
|
||||||
|
}
|
||||||
|
if te.m != testStr {
|
||||||
|
t.Errorf("Invalid error message %s, expected %s", te.m, testStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorf(t *testing.T) {
|
||||||
|
testStr := "Errorf string"
|
||||||
|
err := Errorf(testStr)
|
||||||
|
if te, ok := err.(*Err); ok {
|
||||||
|
if te.c != nil {
|
||||||
|
t.Errorf("Invalid parent error %T:%s, expected nil", te.c, te.c)
|
||||||
|
}
|
||||||
|
if te.m != testStr {
|
||||||
|
t.Errorf("Invalid error message %s, expected %s", te.m, testStr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Invalid error type returned %T, expected type %T", err, &Err{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func TestErr_As(t *testing.T) {
|
||||||
|
e := Err{m: "test", l: 11, f: "random", t: []uintptr{0x6, 0x6, 0x6}, c: fmt.Errorf("ttt")}
|
||||||
|
if e.As(&err) {
|
||||||
|
t.Errorf("%T should not be assertable as %T", err, e)
|
||||||
|
}
|
||||||
|
type clone = Err
|
||||||
|
e1 := clone{}
|
||||||
|
if !e.As(&e1) {
|
||||||
|
t.Errorf("%T should be assertable as %T", e, e1)
|
||||||
|
}
|
||||||
|
if e1.m != e.m {
|
||||||
|
t.Errorf("%T message should equal %T's, received %s, expected %s", e1, e, e1.m, e.m)
|
||||||
|
}
|
||||||
|
if e1.l != e.l {
|
||||||
|
t.Errorf("%T line should equal %T's, received %d, expected %d", e1, e, e1.l, e.l)
|
||||||
|
}
|
||||||
|
if e1.f != e.f {
|
||||||
|
t.Errorf("%T file should equal %T's, received %s, expected %s", e1, e, e1.f, e.f)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(e1.t, e.t) {
|
||||||
|
t.Errorf("%T trace should equal %T's, received %2x, expected %2x", e1, e, e1.t, e.t)
|
||||||
|
}
|
||||||
|
if e1.c != e.c {
|
||||||
|
t.Errorf("%T parent error should equal %T's, received %T[%s], expected %T[%s]", e1, e, e1.c, e1.c, e.c, e.c)
|
||||||
|
}
|
||||||
|
e2 := &e
|
||||||
|
if !e.As(&e2) {
|
||||||
|
t.Errorf("%T should be assertable as %T", e, e2)
|
||||||
|
}
|
||||||
|
if e2.m != e.m {
|
||||||
|
t.Errorf("%T message should equal %T's, received %s, expected %s", e2, e, e2.m, e.m)
|
||||||
|
}
|
||||||
|
if e2.l != e.l {
|
||||||
|
t.Errorf("%T line should equal %T's, received %d, expected %d", e2, e, e2.l, e.l)
|
||||||
|
}
|
||||||
|
if e2.f != e.f {
|
||||||
|
t.Errorf("%T file should equal %T's, received %s, expected %s", e2, e, e2.f, e.f)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(e2.t, e.t) {
|
||||||
|
t.Errorf("%T trace should equal %T's, received %2x, expected %2x", e2, e, e2.t, e.t)
|
||||||
|
}
|
||||||
|
if e2.c != e.c {
|
||||||
|
t.Errorf("%T parent error should equal %T's, received %T[%s], expected %T[%s]", e2, e, e2.c, e2.c, e.c, e.c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErr_Error(t *testing.T) {
|
||||||
|
e := Err{m: "test"}
|
||||||
|
if e.Error() != e.m {
|
||||||
|
t.Errorf("Error() returned %s, expected %s", e.Error(), e.m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErr_Location(t *testing.T) {
|
||||||
|
e := Err{l: 11, f: "random"}
|
||||||
|
if f, l := e.Location(); l != e.l || f != e.f {
|
||||||
|
t.Errorf("Location() returned: %s:%d, expected: %s:%d", f, l, e.f, e.l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErr_StackTrace(t *testing.T) {
|
||||||
|
e := Err{t: []byte{0x6, 0x6, 0x6}}
|
||||||
|
if !bytes.Equal(e.StackTrace(), e.t) {
|
||||||
|
t.Errorf("StackTrace() returned: %2x, expected: %2x", e.StackTrace(), e.t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErr_Unwrap(t *testing.T) {
|
||||||
|
e := Err{c: fmt.Errorf("ttt")}
|
||||||
|
w := e.Unwrap()
|
||||||
|
if w != e.c {
|
||||||
|
t.Errorf("Unwrap() returned: %T[%s], expected: %T[%s]", w, w, e.c, e.c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestErr_Error(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
m string
|
||||||
|
c error
|
||||||
|
t stack
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
quiet bool
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
fields: fields{},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "just text",
|
||||||
|
fields: fields{m: "test"},
|
||||||
|
want: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text with single wrapped error",
|
||||||
|
fields: fields{m: "test", c: fmt.Errorf("error")},
|
||||||
|
want: "test: error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text with two wrapped errors",
|
||||||
|
fields: fields{m: "test", c: fmt.Errorf("error: %w", Newf("some error"))},
|
||||||
|
want: "test: error: some error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text with two wrapped errors, but no unwrapping",
|
||||||
|
quiet: true,
|
||||||
|
fields: fields{m: "test", c: fmt.Errorf("error: %w", Newf("some error"))},
|
||||||
|
want: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text with two wrapped Err errors",
|
||||||
|
fields: fields{m: "test", c: &Err{m: "error", c: &Err{m: "another error"}}},
|
||||||
|
want: "test: error: another error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text with two wrapped Err errors, without unwrapping",
|
||||||
|
quiet: true,
|
||||||
|
fields: fields{m: "test", c: &Err{m: "error", c: &Err{m: "another error"}}},
|
||||||
|
want: "test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
IncludeBacktrace = tt.quiet
|
||||||
|
e := Err{
|
||||||
|
m: tt.fields.m,
|
||||||
|
c: tt.fields.c,
|
||||||
|
t: tt.fields.t,
|
||||||
|
}
|
||||||
|
if got := e.Error(); got != tt.want {
|
||||||
|
t.Errorf("Error() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module github.com/go-ap/errors
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||||
|
github.com/valyala/fastjson v1.6.4
|
||||||
|
)
|
1063
http_test.go
Normal file
1063
http_test.go
Normal file
File diff suppressed because it is too large
Load diff
44
join.go
Normal file
44
join.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
func Join(errs ...error) error {
|
||||||
|
n := 0
|
||||||
|
for _, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
e := &joinError{
|
||||||
|
errs: make([]error, 0, n),
|
||||||
|
}
|
||||||
|
for _, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
e.errs = append(e.errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
type joinError struct {
|
||||||
|
errs []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *joinError) Error() string {
|
||||||
|
// Since Join returns nil if every value in errs is nil,
|
||||||
|
// e.errs cannot be empty.
|
||||||
|
if len(e.errs) == 1 {
|
||||||
|
return e.errs[0].Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
b := []byte(e.errs[0].Error())
|
||||||
|
for _, err := range e.errs[1:] {
|
||||||
|
b = append(b, ':', ' ')
|
||||||
|
b = append(b, err.Error()...)
|
||||||
|
}
|
||||||
|
// At this point, b has at least one byte '\n'.
|
||||||
|
return unsafe.String(&b[0], len(b))
|
||||||
|
}
|
98
redirects.go
Normal file
98
redirects.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SeeOther(u string) *redirect {
|
||||||
|
return &redirect{s: http.StatusSeeOther, u: u}
|
||||||
|
}
|
||||||
|
func NewSeeOther(e error, u string) *redirect {
|
||||||
|
return &redirect{c: e, s: http.StatusSeeOther, u: u}
|
||||||
|
}
|
||||||
|
func Found(u string) *redirect {
|
||||||
|
return &redirect{s: http.StatusFound, u: u}
|
||||||
|
}
|
||||||
|
func NewFound(e error, u string) *redirect {
|
||||||
|
return &redirect{c: e, s: http.StatusFound, u: u}
|
||||||
|
}
|
||||||
|
func MovedPermanently(u string) *redirect {
|
||||||
|
return &redirect{s: http.StatusMovedPermanently, u: u}
|
||||||
|
}
|
||||||
|
func NewMovedPermanently(e error, u string) *redirect {
|
||||||
|
return &redirect{c: e, s: http.StatusMovedPermanently, u: u}
|
||||||
|
}
|
||||||
|
func NotModified(u string) *redirect {
|
||||||
|
return &redirect{s: http.StatusNotModified, u: u}
|
||||||
|
}
|
||||||
|
func NewNotModified(e error, u string) *redirect {
|
||||||
|
return &redirect{c: e, s: http.StatusNotModified, u: u}
|
||||||
|
}
|
||||||
|
func TemporaryRedirect(u string) *redirect {
|
||||||
|
return &redirect{s: http.StatusTemporaryRedirect, u: u}
|
||||||
|
}
|
||||||
|
func NewTemporaryRedirect(e error, u string) *redirect {
|
||||||
|
return &redirect{c: e, s: http.StatusTemporaryRedirect, u: u}
|
||||||
|
}
|
||||||
|
func PermanentRedirect(u string) *redirect {
|
||||||
|
return &redirect{s: http.StatusPermanentRedirect, u: u}
|
||||||
|
}
|
||||||
|
func NewPermanentRedirect(e error, u string) *redirect {
|
||||||
|
return &redirect{c: e, s: http.StatusPermanentRedirect, u: u}
|
||||||
|
}
|
||||||
|
|
||||||
|
type redirect struct {
|
||||||
|
c error
|
||||||
|
u string
|
||||||
|
s int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r redirect) Error() string {
|
||||||
|
if r.c == nil {
|
||||||
|
return fmt.Sprintf("Redirect %d to %s", r.s, r.u)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Redirect %d to %s: %s", r.s, r.u, r.c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
|
||||||
|
//
|
||||||
|
// if the underlying logic of the receiver's type can understand it.
|
||||||
|
//
|
||||||
|
// In this case we're converting a forbidden to its underlying type Err.
|
||||||
|
func (r *redirect) As(err interface{}) bool {
|
||||||
|
switch x := err.(type) {
|
||||||
|
case **redirect:
|
||||||
|
*x = r
|
||||||
|
case *redirect:
|
||||||
|
*x = *r
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r redirect) Is(e error) bool {
|
||||||
|
rr := redirect{}
|
||||||
|
return As(e, &rr) && r.s == rr.s
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRedirect(e error) bool {
|
||||||
|
_, okp := e.(*redirect)
|
||||||
|
_, oks := e.(redirect)
|
||||||
|
return okp || oks || As(e, &redirect{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNotModified(e error) bool {
|
||||||
|
ep, okp := e.(*redirect)
|
||||||
|
es, oks := e.(redirect)
|
||||||
|
|
||||||
|
ae := redirect{}
|
||||||
|
return (okp && ep.s == http.StatusNotModified) ||
|
||||||
|
(oks && es.s == http.StatusNotModified) ||
|
||||||
|
(As(e, &ae) && ae.s == http.StatusNotModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r redirect) Unwrap() error {
|
||||||
|
return r.Unwrap()
|
||||||
|
}
|
219
stack.go
Normal file
219
stack.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"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, 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':
|
||||||
|
f.Format(s, 's')
|
||||||
|
io.WriteString(s, ":")
|
||||||
|
f.Format(s, 'd')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Frame) MarshalJSON() ([]byte, error) {
|
||||||
|
name := f.name()
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
if name == "unknown" {
|
||||||
|
w.WriteByte('"')
|
||||||
|
w.WriteString(name)
|
||||||
|
w.WriteByte('"')
|
||||||
|
return w.Bytes(), nil
|
||||||
|
}
|
||||||
|
w.WriteByte('{')
|
||||||
|
w.WriteString("\"function\": ")
|
||||||
|
w.WriteByte('"')
|
||||||
|
w.WriteString(funcname(name))
|
||||||
|
w.WriteByte('"')
|
||||||
|
w.WriteByte(',')
|
||||||
|
w.WriteString("\"file\": ")
|
||||||
|
w.WriteByte('"')
|
||||||
|
w.WriteString(f.file())
|
||||||
|
w.WriteByte('"')
|
||||||
|
w.WriteByte(',')
|
||||||
|
w.WriteString("\"line\": ")
|
||||||
|
w.WriteString(fmt.Sprintf("%d", f.line()))
|
||||||
|
w.WriteByte('}')
|
||||||
|
return w.Bytes(), 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('#'):
|
||||||
|
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||||
|
default:
|
||||||
|
st.formatSlice(s, verb)
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
st.formatSlice(s, verb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st StackTrace) MarshalJSON() ([]byte, error) {
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
w.WriteByte('[')
|
||||||
|
for i, f := range st {
|
||||||
|
b, _ := f.MarshalJSON()
|
||||||
|
w.Write(b)
|
||||||
|
if i == len(st)-1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w.WriteByte(',')
|
||||||
|
}
|
||||||
|
w.WriteByte(']')
|
||||||
|
return w.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(skip int) *stack {
|
||||||
|
const depth = 32
|
||||||
|
var pcs [depth]uintptr
|
||||||
|
n := runtime.Callers(skip+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:]
|
||||||
|
}
|
263
stack_test.go
Normal file
263
stack_test.go
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initpc = caller()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
"github.com/go-ap/errors.init\n" +
|
||||||
|
"\t.+/errors/stack_test.go",
|
||||||
|
}, {
|
||||||
|
0,
|
||||||
|
"%s",
|
||||||
|
"unknown",
|
||||||
|
}, {
|
||||||
|
0,
|
||||||
|
"%+s",
|
||||||
|
"unknown",
|
||||||
|
}, {
|
||||||
|
initpc,
|
||||||
|
"%d",
|
||||||
|
"11",
|
||||||
|
}, {
|
||||||
|
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:11",
|
||||||
|
}, {
|
||||||
|
initpc,
|
||||||
|
"%+v",
|
||||||
|
"github.com/go-ap/errors.init\n" +
|
||||||
|
"\t.+/errors/stack_test.go:11",
|
||||||
|
}, {
|
||||||
|
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"},
|
||||||
|
{"github.com/go-ap/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
|
||||||
|
}{{
|
||||||
|
Newf("ooh"), []string{
|
||||||
|
"github.com/go-ap/errors.TestStackTrace\n" +
|
||||||
|
"\t.+/errors/stack_test.go:145",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
wrap(Newf("ooh"), "ahh"), []string{
|
||||||
|
"github.com/go-ap/errors.TestStackTrace\n" +
|
||||||
|
"\t.+/errors/stack_test.go:150", // this is the stack of Wrap, not New
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
func() error { return Newf("ooh") }(), []string{
|
||||||
|
`github.com/go-ap/errors.TestStackTrace.func1` +
|
||||||
|
"\n\t.+/errors/stack_test.go:155", // this is the stack of New
|
||||||
|
"github.com/go-ap/errors.TestStackTrace\n" +
|
||||||
|
"\t.+/errors/stack_test.go:155", // this is the stack of New's caller
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
t.Skipf(`TODO(marius): This needs some more work
|
||||||
|
As going one level up the stack in stack.callers() removes meaningful information from the tests`)
|
||||||
|
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()
|
||||||
|
if len(st) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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:183 stack_test.go:230\]`,
|
||||||
|
}, {
|
||||||
|
stackTrace()[:2],
|
||||||
|
"%+v",
|
||||||
|
"\n" +
|
||||||
|
"github.com/go-ap/errors.stackTrace\n" +
|
||||||
|
"\t.+/errors/stack_test.go:183\n" +
|
||||||
|
"github.com/go-ap/errors.TestStackTraceFormat\n" +
|
||||||
|
"\t.+/errors/stack_test.go:234",
|
||||||
|
}, {
|
||||||
|
stackTrace()[:2],
|
||||||
|
"%#v",
|
||||||
|
`\[\]errors.Frame{stack_test.go:183, stack_test.go:242}`,
|
||||||
|
}}
|
||||||
|
|
||||||
|
t.Skipf(`TODO(marius): This needs some more work
|
||||||
|
As going one level up the stack in stack.callers() removes meaningful information from the tests`)
|
||||||
|
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…
Add table
Add a link
Reference in a new issue