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" + "github.com/pkg/errors.TestFormatNew\n" + "\t.+/github.com/pkg/errors/format_test.go:25", }, { New("error"), "%q", `"error"`, "\t.+/github.com/pkg/errors/format_test.go:26", }} 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" + "github.com/pkg/errors.TestFormatErrorf\n" + "\t.+/github.com/pkg/errors/format_test.go:55", }} 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" + "github.com/pkg/errors.TestFormatWrap\n" + "\t.+/github.com/pkg/errors/format_test.go:81", }, { Wrap(io.EOF, "error"), "%s", "error: EOF", }, { Wrap(io.EOF, "error"), "%v", "error: EOF", }, { Wrap(io.EOF, "error"), "%+v", "EOF\n" + "error\n" + "github.com/pkg/errors.TestFormatWrap\n" + "\t.+/github.com/pkg/errors/format_test.go:95", }, { Wrap(Wrap(io.EOF, "error1"), "error2"), "%+v", "EOF\n" + "error1\n" + "github.com/pkg/errors.TestFormatWrap\n" + "\t.+/github.com/pkg/errors/format_test.go:102\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" + "github.com/pkg/errors.TestFormatWrapf\n" + "\t.+/github.com/pkg/errors/format_test.go:133", }, { 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" + "github.com/pkg/errors.TestFormatWrapf\n" + "\t.+/github.com/pkg/errors/format_test.go:148", }} 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", "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) 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. // // 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]) } } } }