Add WithStack and WithMessage tests
Adds testFormatCompleteCompare as additional testing func.
The new function takes a string slice as "want", wherein
stacktraces and non-stacktrace messages are discerned by
strings.ContainsAny(want[i], "\n").
For example usage, see TestFormatWithStack & TestFormatWithMessage.
diff --git a/errors_test.go b/errors_test.go
index 11d4555..c4ca617 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -84,6 +84,18 @@
}, {
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 {
@@ -137,23 +149,78 @@
}
}
+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)
+ }
+ }
+
+}
+
// 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) {
- tests := []struct {
- err1, err2 error
- }{
- {io.EOF, io.EOF},
- {io.EOF, nil},
- {io.EOF, errors.New("EOF")},
- {io.EOF, New("EOF")},
- {New("EOF"), New("EOF")},
- {New("EOF"), Errorf("EOF")},
- {New("EOF"), Wrap(io.EOF, "EOF")},
+ 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 _, tt := range tests {
- _ = tt.err1 == tt.err2 // mustn't panic
+
+ for i := 0; i < len(vals); i++ {
+ for j := 0; j < len(vals); j++ {
+ _ = vals[i] == vals[j] // mustn't panic
+ }
}
}
diff --git a/example_test.go b/example_test.go
index 5bec3ad..c1fc13e 100644
--- a/example_test.go
+++ b/example_test.go
@@ -35,6 +35,59 @@
// /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/pkg/errors_test.ExampleWithStack_printf
+ // /home/fabstu/go/src/github.com/pkg/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/pkg/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/pkg/errors_test.ExampleWithStack_printf
+ // /home/fabstu/go/src/github.com/pkg/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/pkg/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")
diff --git a/format_test.go b/format_test.go
index fd17581..ae1be51 100644
--- a/format_test.go
+++ b/format_test.go
@@ -1,6 +1,7 @@
package errors
import (
+ "errors"
"fmt"
"io"
"regexp"
@@ -31,6 +32,7 @@
New("error"),
"%q",
`"error"`,
+ "\t.+/github.com/pkg/errors/format_test.go:26",
}}
for i, tt := range tests {
@@ -157,16 +159,270 @@
}
}
+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",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:171"},
+ }, {
+ WithStack(New("error")),
+ "%s",
+ []string{"error"},
+ }, {
+ WithStack(New("error")),
+ "%v",
+ []string{"error"},
+ }, {
+ WithStack(New("error")),
+ "%+v",
+ []string{"error",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:185",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:185"},
+ }, {
+ WithStack(WithStack(io.EOF)),
+ "%+v",
+ []string{"EOF",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:193",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:193"},
+ }, {
+ WithStack(WithStack(Wrapf(io.EOF, "message"))),
+ "%+v",
+ []string{"EOF",
+ "message",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:201",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:201",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:201"},
+ }, {
+ WithStack(Errorf("error%d", 1)),
+ "%+v",
+ []string{"error1",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:212",
+ "github.com/pkg/errors.TestFormatWithStack\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:212"},
+ }}
+
+ for i, tt := range tests {
+ testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want)
+ }
+}
+
+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",
+ "github.com/pkg/errors.TestFormatWithMessage\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:240",
+ "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",
+ "github.com/pkg/errors.TestFormatWithMessage\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:268"},
+ }, {
+ WithMessage(Errorf("error%d", 1), "error2"),
+ "%+v",
+ []string{"error1",
+ "github.com/pkg/errors.TestFormatWithMessage\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:274",
+ "error2"},
+ }, {
+ WithMessage(WithStack(io.EOF), "error"),
+ "%+v",
+ []string{
+ "EOF",
+ "github.com/pkg/errors.TestFormatWithMessage\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:281",
+ "error"},
+ }, {
+ WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
+ "%+v",
+ []string{
+ "EOF",
+ "github.com/pkg/errors.TestFormatWithMessage\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:289",
+ "inside-error",
+ "github.com/pkg/errors.TestFormatWithMessage\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:289",
+ "outside-error"},
+ }}
+
+ for i, tt := range tests {
+ testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want)
+ }
+}
+
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
got := fmt.Sprintf(format, arg)
- lines := strings.SplitN(got, "\n", -1)
- for i, w := range strings.SplitN(want, "\n", -1) {
- match, err := regexp.MatchString(w, lines[i])
+ 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): got: %q, want: %q", n+1, i+1, format, got, want)
+ 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.
+//
+// Example use:
+//
+// for _, e := range blocks {
+// if strings.ContainsAny(e, "\n") {
+// // Match as stack
+// } else {
+// // Match as line
+// }
+// }
+func parseBlocks(input string) ([]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 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) {
+ gotStr := fmt.Sprintf(format, arg)
+
+ got, err := parseBlocks(gotStr)
+ 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: %q\nwant: %q\ngotStr: %q",
+ n+1, format, len(got), len(want), got, 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.Errorf("test %d: block %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got[i], want[i])
+ }
+ } else {
+ // Match as message
+ if got[i] != want[i] {
+ t.Errorf("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])
+ }
}
}
}