New Upstream Snapshot - golang-github-getlantern-errors
Ready changes
Summary
Merged new upstream version: 1.0.1+git20210901.1.1b7978a (was: 0.0~git20190325.abdb3e3).
Resulting package
Built on 2022-05-12T17:08 (took 5m38s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-snapshots golang-github-getlantern-errors-dev
Lintian Result
- golang-github-getlantern-errors-dev_1.0.1+git20210901.1.1b7978a-1~jan+nus1_all.deb
- golang-github-getlantern-errors_1.0.1+git20210901.1.1b7978a-1~jan+nus1.dsc
- golang-github-getlantern-errors_1.0.1+git20210901.1.1b7978a-1~jan+nus1_amd64.buildinfo
- golang-github-getlantern-errors_1.0.1+git20210901.1.1b7978a-1~jan+nus1_amd64.changes
Diff
diff --git a/LICENSE b/LICENSE
index 416dd69..2d1cb59 100644
--- a/LICENSE
+++ b/LICENSE
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2018 Brave New Software Project, Inc.
+ Copyright 2021 Brave New Software Project, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/debian/changelog b/debian/changelog
index 19d6924..e743ccd 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-getlantern-errors (1.0.1+git20210901.1.1b7978a-1) UNRELEASED; urgency=low
+
+ * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk> Thu, 12 May 2022 17:04:05 -0000
+
golang-github-getlantern-errors (0.0~git20190325.abdb3e3-2) unstable; urgency=medium
* Add missing (build-)dep on getlantern-hex (Closes: #927250)
diff --git a/errors.go b/errors.go
index 8b2b84f..318358c 100644
--- a/errors.go
+++ b/errors.go
@@ -123,13 +123,11 @@ type Error interface {
RootCause() error
}
-type structured struct {
- id uint64
+type baseError struct {
+ errID uint64
hiddenID string
data context.Map
context context.Map
- wrapped error
- cause Error
callStack stack.CallStack
}
@@ -142,16 +140,24 @@ func New(desc string, args ...interface{}) Error {
// NewOffset is like New but offsets the stack by the given offset. This is
// useful for utilities like golog that may create errors on behalf of others.
func NewOffset(offset int, desc string, args ...interface{}) Error {
- var cause error
+ e := buildError(desc, fmt.Sprintf(desc, args...))
+ e.attachStack(2 + offset)
for _, arg := range args {
- err, isError := arg.(error)
+ wrapped, isError := arg.(error)
if isError {
- cause = err
- break
+ op, _, _, extraData := parseError(wrapped)
+ if op != "" {
+ e.Op(op)
+ }
+ for k, v := range extraData {
+ e.data[k] = v
+ }
+ we := &wrappingError{e, wrapped}
+ bufferError(we)
+ return we
}
}
- e := buildError(desc, fmt.Sprintf(desc, args...), nil, Wrap(cause))
- e.attachStack(2 + offset)
+ bufferError(e)
return e
}
@@ -160,33 +166,57 @@ func NewOffset(offset int, desc string, args ...interface{}) Error {
// errors.Wrap(s.l.Close()) regardless there's an error or not. If the error is
// already wrapped, it is returned as is.
func Wrap(err error) Error {
- return wrapSkipFrames(err, 1)
+ if err == nil {
+ return nil
+ }
+ if e, ok := err.(Error); ok {
+ return e
+ }
+
+ op, goType, desc, extraData := parseError(err)
+ if desc == "" {
+ desc = err.Error()
+ }
+ e := buildError(desc, desc)
+ e.attachStack(2)
+ if op != "" {
+ e.Op(op)
+ }
+ e.data["error_type"] = goType
+ for k, v := range extraData {
+ e.data[k] = v
+ }
+ if cause := getCause(err); cause != nil {
+ we := &wrappingError{e, cause}
+ bufferError(we)
+ return we
+ }
+ bufferError(e)
+ return e
}
// Fill implements the method from the context.Contextual interface.
-func (e *structured) Fill(m context.Map) {
- if e != nil {
- if e.cause != nil {
- // Include data from cause, which supercedes context
- e.cause.Fill(m)
- }
- // Include the context, which supercedes the cause
- for key, value := range e.context {
- m[key] = value
- }
- // Now include the error's data, which supercedes everything
- for key, value := range e.data {
- m[key] = value
- }
+func (e *baseError) Fill(m context.Map) {
+ if e == nil {
+ return
+ }
+
+ // Include the context, which supercedes the cause
+ for key, value := range e.context {
+ m[key] = value
+ }
+ // Now include the error's data, which supercedes everything
+ for key, value := range e.data {
+ m[key] = value
}
}
-func (e *structured) Op(op string) Error {
+func (e *baseError) Op(op string) Error {
e.data["error_op"] = op
return e
}
-func (e *structured) With(key string, value interface{}) Error {
+func (e *baseError) With(key string, value interface{}) Error {
parts := strings.FieldsFunc(key, func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
})
@@ -204,126 +234,62 @@ func (e *structured) With(key string, value interface{}) Error {
return e
}
-func (e *structured) RootCause() error {
- if e.cause == nil {
- if e.wrapped != nil {
- return e.wrapped
- }
- return e
- }
- return e.cause.RootCause()
+func (e *baseError) RootCause() error {
+ return e
}
-func (e *structured) ErrorClean() string {
+func (e *baseError) ErrorClean() string {
return e.data["error"].(string)
}
// Error satisfies the error interface
-func (e *structured) Error() string {
+func (e *baseError) Error() string {
return e.data["error_text"].(string) + e.hiddenID
}
-func (e *structured) MultiLinePrinter() func(buf *bytes.Buffer) bool {
- first := true
- indent := false
- err := e
+func (e *baseError) MultiLinePrinter() func(*bytes.Buffer) bool {
+ return e.topLevelPrinter()
+}
+
+func (e *baseError) topLevelPrinter() func(*bytes.Buffer) bool {
+ printingStack := false
stackPosition := 0
- switchedCause := false
return func(buf *bytes.Buffer) bool {
- if indent {
- buf.WriteString(" ")
- }
- if first {
+ if !printingStack {
buf.WriteString(e.Error())
- first = false
- indent = true
- return true
- }
- if switchedCause {
- fmt.Fprintf(buf, "Caused by: %v", err)
- if err.callStack != nil && len(err.callStack) > 0 {
- switchedCause = false
- indent = true
- return true
- }
- if err.cause == nil {
- return false
- }
- err = err.cause.(*structured)
- return true
- }
- if stackPosition < len(err.callStack) {
- buf.WriteString("at ")
- call := err.callStack[stackPosition]
- fmt.Fprintf(buf, "%+n (%s:%d)", call, call, call)
- stackPosition++
- }
- if stackPosition >= len(err.callStack) {
- switch cause := err.cause.(type) {
- case *structured:
- err = cause
- indent = false
- stackPosition = 0
- switchedCause = true
- default:
- return false
- }
+ printingStack = true
+ return len(e.callStack) > 0
}
- return err != nil
+ call := e.callStack[stackPosition]
+ fmt.Fprintf(buf, " at %+n (%s:%d)", call, call, call)
+ stackPosition++
+ return stackPosition < len(e.callStack)
}
}
-func wrapSkipFrames(err error, skip int) Error {
- if err == nil {
- return nil
- }
-
- // Look for *structureds
- if e, ok := err.(*structured); ok {
- return e
- }
+func (e *baseError) attachStack(skip int) {
+ call := stack.Caller(skip)
+ e.callStack = stack.Trace().TrimBelow(call)
+ e.data["error_location"] = fmt.Sprintf("%+n (%s:%d)", call, call, call)
+}
- var cause Error
- // Look for hidden *structureds
- hiddenIDs, err2 := hidden.Extract(err.Error())
- if err2 == nil && len(hiddenIDs) > 0 {
- // Take the first hidden ID as our cause
- cause = get(hiddenIDs[0])
- }
+func (e *baseError) id() uint64 {
+ return e.errID
+}
- // Create a new *structured
- return buildError("", "", err, cause)
+func (e *baseError) setID(id uint64) {
+ e.errID = id
}
-func (e *structured) attachStack(skip int) {
- call := stack.Caller(skip)
- e.callStack = stack.Trace().TrimBelow(call)
- e.data["error_location"] = fmt.Sprintf("%+n (%s:%d)", call, call, call)
+func (e *baseError) setHiddenID(id string) {
+ e.hiddenID = id
}
-func buildError(desc string, fullText string, wrapped error, cause Error) *structured {
- e := &structured{
+func buildError(desc string, fullText string) *baseError {
+ e := &baseError{
data: make(context.Map),
// We capture the current context to allow it to propagate to higher layers.
context: ops.AsMap(nil, false),
- wrapped: wrapped,
- cause: cause,
- }
- e.save()
-
- errorType := "errors.Error"
- if wrapped != nil {
- op, goType, wrappedDesc, extra := parseError(wrapped)
- if desc == "" {
- desc = wrappedDesc
- }
- e.Op(op)
- errorType = goType
- if extra != nil {
- for key, value := range extra {
- e.data[key] = value
- }
- }
}
cleanedDesc := hidden.Clean(desc)
@@ -333,11 +299,125 @@ func buildError(desc string, fullText string, wrapped error, cause Error) *struc
} else {
e.data["error_text"] = cleanedDesc
}
- e.data["error_type"] = errorType
+ e.data["error_type"] = "errors.Error"
+
+ return e
+}
+
+type topLevelPrinter interface {
+ // Returns a printer which prints only the top-level error and any associated stack trace. The
+ // output of this printer will be a prefix of the output from MultiLinePrinter().
+ topLevelPrinter() func(*bytes.Buffer) bool
+}
+
+type unwrapper interface {
+ Unwrap() error
+}
+
+type wrappingError struct {
+ *baseError
+ wrapped error
+}
+
+// Implements error unwrapping as described in the standard library's errors package:
+// https://golang.org/pkg/errors/#pkg-overview
+func (e *wrappingError) Unwrap() error {
+ return e.wrapped
+}
+
+func (e *wrappingError) Fill(m context.Map) {
+ type filler interface{ Fill(context.Map) }
+
+ applyToChain(e.wrapped, func(err error) {
+ if f, ok := err.(filler); ok {
+ f.Fill(m)
+ }
+ })
+ e.baseError.Fill(m)
+}
+
+func (e *wrappingError) RootCause() error {
+ return unwrapToRoot(e)
+}
+
+func (e *wrappingError) MultiLinePrinter() func(*bytes.Buffer) bool {
+ var (
+ currentPrinter = e.baseError.topLevelPrinter()
+ nextErr = e.wrapped
+ prefix = ""
+ )
+ return func(buf *bytes.Buffer) bool {
+ fmt.Fprint(buf, prefix)
+ if currentPrinter(buf) {
+ prefix = ""
+ return true
+ }
+ if nextErr == nil {
+ return false
+ }
+ currentPrinter = getTopLevelPrinter(nextErr)
+ prefix = "Caused by: "
+ if uw, ok := nextErr.(unwrapper); ok {
+ nextErr = uw.Unwrap()
+ } else {
+ nextErr = nil
+ }
+ return true
+ }
+}
+
+// We have to implement these two methods or the fluid syntax will result in the embedded *baseError
+// being returned, not the *wrappingError.
+func (e *wrappingError) Op(op string) Error {
+ e.baseError = e.baseError.Op(op).(*baseError)
return e
}
+func (e *wrappingError) With(key string, value interface{}) Error {
+ e.baseError = e.baseError.With(key, value).(*baseError)
+ return e
+}
+
+func getTopLevelPrinter(err error) func(*bytes.Buffer) bool {
+ if tlp, ok := err.(topLevelPrinter); ok {
+ return tlp.topLevelPrinter()
+ }
+ return func(buf *bytes.Buffer) bool {
+ fmt.Fprint(buf, err)
+ return false
+ }
+}
+
+func getCause(e error) error {
+ if uw, ok := e.(unwrapper); ok {
+ return uw.Unwrap()
+ }
+ // Look for hidden *baseErrors
+ hiddenIDs, extractErr := hidden.Extract(e.Error())
+ if extractErr == nil && len(hiddenIDs) > 0 {
+ // Take the first hidden ID as our cause
+ return get(hiddenIDs[0])
+ }
+ return nil
+}
+
+func unwrapToRoot(e error) error {
+ if uw, ok := e.(unwrapper); ok {
+ return unwrapToRoot(uw.Unwrap())
+ }
+ return e
+}
+
+// Applies f to the chain of errors unwrapped from err. The function is applied to the root cause
+// first and err last.
+func applyToChain(err error, f func(error)) {
+ if uw, ok := err.(unwrapper); ok {
+ applyToChain(uw.Unwrap(), f)
+ }
+ f(err)
+}
+
func parseError(err error) (op string, goType string, desc string, extra map[string]string) {
extra = make(map[string]string)
diff --git a/errors_test.go b/errors_test.go
index 7c4887a..effee34 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -2,7 +2,10 @@ package errors
import (
"bytes"
+ "errors"
"fmt"
+ "io"
+ "net"
"regexp"
"testing"
@@ -10,6 +13,7 @@ import (
"github.com/getlantern/hidden"
"github.com/getlantern/ops"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var (
@@ -29,9 +33,10 @@ func TestFull(t *testing.T) {
}
assert.Equal(t, "Hello There", e.Error()[:11])
op = ops.Begin("op2").Set("ca", 200).Set("cb", 200).Set("cc", 200)
- e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", e)).Op("outer op").With("dATA+1", i).With("cb", 300)
+ e3 := Wrap(fmt.Errorf("I'm wrapping your text: %w", e)).Op("outer op").With("dATA+1", i).With("cb", 300)
op.End()
- assert.Equal(t, e, e3.(*structured).cause, "Wrapping a regular error should have extracted the contained *Error")
+ require.IsType(t, (*wrappingError)(nil), e3, "wrapping an error with cause should have resulted in a *wrappingError")
+ assert.Equal(t, e, e3.(*wrappingError).wrapped, "Wrapping a regular error should have extracted the contained *Error")
m := make(context.Map)
e3.Fill(m)
assert.Equal(t, i, m["data_1"], "Error's data should dominate all")
@@ -39,26 +44,27 @@ func TestFull(t *testing.T) {
assert.Equal(t, 300, m["cb"], "Error's data should dominate its context")
assert.Equal(t, 200, m["cc"], "Error's context should come through")
assert.Equal(t, 100, m["cd"], "Cause's context should come through")
- assert.Equal(t, "My Op", e.(*structured).data["error_op"], "Op should be available from cause")
+ assert.Equal(t, "My Op", e.(*baseError).data["error_op"], "Op should be available from cause")
- for _, call := range e3.(*structured).callStack {
+ for _, call := range e3.(*wrappingError).callStack {
t.Logf("at %v", call)
}
}
e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", firstErr)).With("a", 2)
- assert.Nil(t, e3.(*structured).cause, "Wrapping an *Error that's no longer buffered should have yielded no cause")
+ require.IsType(t, (*baseError)(nil), e3, "Wrapping an *Error that's no longer buffered should have resulted in a *baseError")
}
func TestNewWithCause(t *testing.T) {
cause := buildCause()
outer := New("Hello %v", cause)
assert.Equal(t, "Hello World", hidden.Clean(outer.Error()))
- assert.Equal(t, "Hello %v", outer.(*structured).ErrorClean())
+ assert.Equal(t, "Hello %v", outer.ErrorClean())
+ require.IsType(t, (*wrappingError)(nil), outer, "Including an error arg should have resulted in a *wrappingError")
assert.Equal(t,
"github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)",
- replaceNumbers.ReplaceAllString(outer.(*structured).data["error_location"].(string), "999"))
- assert.Equal(t, cause, outer.(*structured).cause)
+ replaceNumbers.ReplaceAllString(outer.(*wrappingError).data["error_location"].(string), "999"))
+ assert.Equal(t, cause, outer.(*wrappingError).wrapped)
// Make sure that stacktrace prints out okay
buf := &bytes.Buffer{}
@@ -99,7 +105,7 @@ func buildCause() Error {
}
func buildSubCause() error {
- return fmt.Errorf("or%v", buildSubSubCause())
+ return fmt.Errorf("or%w", buildSubSubCause())
}
func buildSubSubCause() error {
@@ -123,7 +129,7 @@ func TestHiddenWithCause(t *testing.T) {
e2 := New("I wrap: %v", e1)
e3 := fmt.Errorf("Hiding %v", e2)
// clear hidden buffer
- hiddenErrors = make([]*structured, 100)
+ hiddenErrors = make([]hideableError, 100)
e4 := Wrap(e3)
e5 := New("I'm really outer: %v", e4)
@@ -136,7 +142,54 @@ func TestHiddenWithCause(t *testing.T) {
break
}
}
- fmt.Println(buf.String())
// We're not asserting the output because we're just making sure that printing
// doesn't panic. If we get to this point without panicking, we're happy.
}
+
+func TestFill(t *testing.T) {
+ e := New("something happened").(*baseError)
+ e2 := New("uh oh: %v", e).(*wrappingError)
+ e3 := fmt.Errorf("hmm: %w", e2)
+ e4 := New("umm: %v", e3).(*wrappingError)
+
+ e4.data["name"] = "e4"
+ e2.data["name"] = "e2"
+ e.data["name"] = "e"
+ e2.data["k"] = "v2"
+ e.data["k"] = "v"
+ e.data["a"] = "b"
+
+ m := context.Map{}
+ e4.Fill(m)
+ require.Equal(t, "e4", m["name"])
+ require.Equal(t, "v2", m["k"])
+ require.Equal(t, "b", m["a"])
+}
+
+// Ensures that this package implements error unwrapping as described in:
+// https://golang.org/pkg/errors/#pkg-overview
+func TestUnwrapping(t *testing.T) {
+ sampleUnwrapper := fmt.Errorf("%w", fmt.Errorf("something happened"))
+
+ errNoCause := New("something happened")
+ _, ok := errNoCause.(unwrapper)
+ assert.False(t, ok, "error with no cause should not implement Unwrap method")
+ wrappedNoCause := Wrap(errNoCause)
+ _, ok = wrappedNoCause.(unwrapper)
+ assert.False(t, ok, "wrapped error with no cause should not implement Unwrap method")
+
+ errFromEOF := New("something happened: %v", io.EOF)
+ assert.Implements(t, &sampleUnwrapper, errFromEOF)
+ assert.True(t, errors.Is(errFromEOF, io.EOF))
+ wrappedFromEOF := Wrap(errFromEOF)
+ assert.Implements(t, &sampleUnwrapper, wrappedFromEOF)
+ assert.True(t, errors.Is(wrappedFromEOF, io.EOF))
+
+ addrErrHolder := new(net.AddrError)
+ errFromAddrErr := New("something happend: %v", new(net.AddrError))
+ assert.Implements(t, &sampleUnwrapper, errFromAddrErr)
+ assert.True(t, errors.As(errFromAddrErr, &addrErrHolder))
+ wrappedFromAddrErr := Wrap(errFromAddrErr)
+ assert.Implements(t, &sampleUnwrapper, wrappedFromAddrErr)
+ assert.True(t, errors.As(wrappedFromAddrErr, &addrErrHolder))
+}
diff --git a/hide.go b/hide.go
index f10d863..6dbd410 100644
--- a/hide.go
+++ b/hide.go
@@ -8,22 +8,29 @@ import (
)
var (
- hiddenErrors = make([]*structured, 100)
+ hiddenErrors = make([]hideableError, 100)
nextID = uint64(0)
hiddenMutex sync.RWMutex
)
+type hideableError interface {
+ Error
+ id() uint64
+ setID(uint64)
+ setHiddenID(string)
+}
+
// This trick saves the error to a ring buffer and embeds a non-printing
-// hiddenID in the error's description, so that if the errors is later wrapped
+// hiddenID in the error's description, so that if the error is later wrapped
// by a standard error using something like
// fmt.Errorf("An error occurred: %v", thisError), we can subsequently extract
// the error simply using the hiddenID in the string.
-func (e *structured) save() {
+func bufferError(e hideableError) {
hiddenMutex.Lock()
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, nextID)
- e.id = nextID
- e.hiddenID = hidden.ToString(b)
+ e.setID(nextID)
+ e.setHiddenID(hidden.ToString(b))
hiddenErrors[idxForID(nextID)] = e
nextID++
hiddenMutex.Unlock()
@@ -37,7 +44,7 @@ func get(hiddenID []byte) Error {
hiddenMutex.RLock()
err := hiddenErrors[idxForID(id)]
hiddenMutex.RUnlock()
- if err != nil && err.id == id {
+ if err != nil && err.id() == id {
// Found it!
return err
}
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files