Skip to content

Commit

Permalink
Merge pull request #118 from dhartunian/go-1.20-join
Browse files Browse the repository at this point in the history
Add Go 1.20 errors.Join support
  • Loading branch information
dhartunian authored Aug 23, 2023
2 parents f0a2a69 + 89c9f9e commit 2fb1006
Show file tree
Hide file tree
Showing 16 changed files with 2,449 additions and 0 deletions.
8 changes: 8 additions & 0 deletions errutil/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package errutil

import (
"github.com/cockroachdb/errors/join"
"github.com/cockroachdb/errors/secondary"
"github.com/cockroachdb/errors/withstack"
"github.com/cockroachdb/redact"
Expand Down Expand Up @@ -158,3 +159,10 @@ func WrapWithDepthf(depth int, err error, format string, args ...interface{}) er
err = withstack.WithStackDepth(err, depth+1)
return err
}

// JoinWithDepth constructs a Join error with the provided list of
// errors as arguments, and wraps it in a `WithStackDepth` to capture a
// stacktrace alongside.
func JoinWithDepth(depth int, errs ...error) error {
return withstack.WithStackDepth(join.Join(errs...), depth+1)
}
16 changes: 16 additions & 0 deletions errutil_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,19 @@ func HandleAsAssertionFailureDepth(depth int, origErr error) error {
// - it also supports recursing through causes with Cause().
// - if it detects an API use error, its panic object is a valid error.
func As(err error, target interface{}) bool { return errutil.As(err, target) }

// Join returns an error that wraps the given errors.
// Any nil error values are discarded.
// Join returns nil if errs contains no non-nil values.
// The error formats as the concatenation of the strings obtained
// by calling the Error method of each element of errs, with a newline
// between each string. A stack trace is also retained.
func Join(errs ...error) error {
return errutil.JoinWithDepth(1, errs...)
}

// JoinWithDepth is like Join but the depth at which the call stack is
// captured can be specified.
func JoinWithDepth(depth int, errs ...error) error {
return errutil.JoinWithDepth(depth+1, errs...)
}
12 changes: 12 additions & 0 deletions errutil_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package errors_test

import (
"fmt"
"strings"
"testing"

"github.com/cockroachdb/errors"
Expand All @@ -17,3 +18,14 @@ func TestUnwrap(t *testing.T) {
// (per API documentation)
tt.Check(errors.Unwrap(e) == nil)
}

// More detailed testing of Join is in datadriven_test.go. Here we make
// sure that the public API includes the stacktrace wrapper.
func TestJoin(t *testing.T) {
e := errors.Join(errors.New("abc123"), errors.New("def456"))
printed := fmt.Sprintf("%+v", e)
expected := `Error types: (1) *withstack.withStack (2) *join.joinError (3) *withstack.withStack (4) *errutil.leafError (5) *withstack.withStack (6) *errutil.leafError`
if !strings.Contains(printed, expected) {
t.Errorf("Expected: %s to contain: %s", printed, expected)
}
}
6 changes: 6 additions & 0 deletions fmttests/datadriven_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/cockroachdb/errors/errutil"
"github.com/cockroachdb/errors/hintdetail"
"github.com/cockroachdb/errors/issuelink"
"github.com/cockroachdb/errors/join"
"github.com/cockroachdb/errors/report"
"github.com/cockroachdb/errors/safedetails"
"github.com/cockroachdb/errors/secondary"
Expand Down Expand Up @@ -276,6 +277,9 @@ var wrapCommands = map[string]commandFn{
ctx = logtags.AddTag(ctx, "safe", redact.Safe(456))
return contexttags.WithContextTags(err, ctx)
},
"join": func(err error, args []arg) error {
return join.Join(err, errutil.New(strfy(args)))
},
}

var noPrefixWrappers = map[string]bool{
Expand All @@ -300,6 +304,7 @@ var noPrefixWrappers = map[string]bool{
"stack": true,
"tags": true,
"telemetry": true,
"join": true,
}

var wrapOnlyExceptions = map[string]string{}
Expand Down Expand Up @@ -329,6 +334,7 @@ func init() {
// means they don't match.
"nofmt",
"errorf",
"join",
} {
wrapOnlyExceptions[v] = `
accept %\+v via Formattable.*IRREGULAR: not same as %\+v
Expand Down
191 changes: 191 additions & 0 deletions fmttests/testdata/format/wrap-fmt
Original file line number Diff line number Diff line change
Expand Up @@ -2251,6 +2251,197 @@ Type: "*fmttests.errFmt"
Title: "×"
(NO STACKTRACE)

run
fmt innerone innertwo
join outerthree outerfour

accept %\+v via Formattable.*IRREGULAR: not same as %\+v
accept %\#v via Formattable.*IRREGULAR: not same as %\#v

require (?s)
----
&join.joinError{
errs: {
&fmttests.errFmt{msg:"innerone\ninnertwo"},
&withstack.withStack{
cause: &errutil.leafError{msg:"outerthree\nouterfour"},
stack: &stack{...},
},
},
}
=====
===== non-redactable formats
=====
== %#v
&join.joinError{
errs: {
&fmttests.errFmt{msg:"innerone\ninnertwo"},
&withstack.withStack{
cause: &errutil.leafError{msg:"outerthree\nouterfour"},
stack: &stack{...},
},
},
}
== Error()
innerone
innertwo
outerthree
outerfour
== %v = Error(), good
== %s = Error(), good
== %q = quoted Error(), good
== %x = hex Error(), good
== %X = HEX Error(), good
== %+v
innerone
(1) innerone
| innertwo
| outerthree
| outerfour
Wraps: (2) attached stack trace
-- stack trace:
| github.com/cockroachdb/errors/fmttests.glob...funcNN...
| <tab><path>:<lineno>
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2.1
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirective.func1
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirective
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirectiveOrSubTest
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runTestInternal
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.RunTest
| <tab><path>:<lineno>
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.Walk
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.Walk.func1
| <tab><path>:<lineno>
| testing.tRunner
| <tab><path>:<lineno>
| runtime.goexit
| <tab><path>:<lineno>
└─ Wraps: (3) outerthree
| outerfour
Wraps: (4) innerone
| innertwo
| -- this is innerone
| innertwo's
| multi-line leaf payload
Error types: (1) *join.joinError (2) *withstack.withStack (3) *errutil.leafError (4) *fmttests.errFmt
== %#v via Formattable() = %#v, good
== %v via Formattable() = Error(), good
== %s via Formattable() = %v via Formattable(), good
== %q via Formattable() = quoted %v via Formattable(), good
== %+v via Formattable() == %+v, good
=====
===== redactable formats
=====
== printed via redact Print(), ok - congruent with %v
‹innerone›
‹innertwo›
outerthree
outerfour
== printed via redact Printf() %v = Print(), good
== printed via redact Printf() %s = Print(), good
== printed via redact Printf() %q, refused - good
== printed via redact Printf() %x, refused - good
== printed via redact Printf() %X, refused - good
== printed via redact Printf() %+v, ok - congruent with %+v
‹innerone›
(1) ‹innerone›
| ‹innertwo›
| outerthree
| outerfour
Wraps: (2) attached stack trace
-- stack trace:
| github.com/cockroachdb/errors/fmttests.glob...funcNN...
| <tab><path>:<lineno>
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2.1
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirective.func1
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirective
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirectiveOrSubTest
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runTestInternal
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.RunTest
| <tab><path>:<lineno>
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.Walk
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.Walk.func1
| <tab><path>:<lineno>
| testing.tRunner
| <tab><path>:<lineno>
| runtime.goexit
| <tab><path>:<lineno>
└─ Wraps: (3) outerthree
| outerfour
Wraps: (4) ‹innerone›
‹ | innertwo›
‹ | -- this is innerone›
‹ | innertwo's›
‹ | multi-line leaf payload›
Error types: (1) *join.joinError (2) *withstack.withStack (3) *errutil.leafError (4) *fmttests.errFmt
=====
===== Sentry reporting
=====
== Message payload
×
(1) ×
| ×
| outerthree
| outerfour
Wraps: (2) attached stack trace
-- stack trace:
| github.com/cockroachdb/errors/fmttests.glob...funcNN...
| <tab><path>:<lineno>
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2.1
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirective.func1
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirective
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runDirectiveOrSubTest
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.runTestInternal
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.RunTest
| <tab><path>:<lineno>
| github.com/cockroachdb/errors/fmttests.TestDatadriven.func2
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.Walk
| <tab><path>:<lineno>
| github.com/cockroachdb/datadriven.Walk.func1
| <tab><path>:<lineno>
| testing.tRunner
| <tab><path>:<lineno>
| runtime.goexit
| <tab><path>:<lineno>
└─ Wraps: (3) outerthree
| outerfour
Wraps: (4) ×
×
×
×
×
Error types: (1) *join.joinError (2) *withstack.withStack (3) *errutil.leafError (4) *fmttests.errFmt
-- report composition:
*join.joinError
== Extra "error types"
github.com/cockroachdb/errors/join/*join.joinError (*::)
== Exception 1 (Module: "error domain: <none>")
Type: "*join.joinError"
Title: "×"
(NO STACKTRACE)

run
fmt innerone innertwo
migrated outerthree outerfour
Expand Down
Loading

0 comments on commit 2fb1006

Please sign in to comment.