1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
18 "golang.org/x/xerrors"
21 func TestErrorf(t *testing.T) {
22 chained := &wrapped{"chained", nil}
23 chain := func(s ...string) (a []string) {
25 a = append(a, cleanPath(s))
29 testCases := []struct {
33 xerrors.Errorf("no args"),
34 chain("no args/path.TestErrorf/path.go:xxx"),
36 xerrors.Errorf("no args: %s"),
37 chain("no args: %!s(MISSING)/path.TestErrorf/path.go:xxx"),
39 xerrors.Errorf("nounwrap: %s", "simple"),
40 chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
42 xerrors.Errorf("nounwrap: %v", "simple"),
43 chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
45 xerrors.Errorf("%s failed: %v", "foo", chained),
46 chain("foo failed/path.TestErrorf/path.go:xxx",
47 "chained/somefile.go:xxx"),
49 xerrors.Errorf("no wrap: %s", chained),
50 chain("no wrap/path.TestErrorf/path.go:xxx",
51 "chained/somefile.go:xxx"),
53 xerrors.Errorf("%s failed: %w", "foo", chained),
54 chain("wraps:foo failed/path.TestErrorf/path.go:xxx",
55 "chained/somefile.go:xxx"),
57 xerrors.Errorf("nowrapv: %v", chained),
58 chain("nowrapv/path.TestErrorf/path.go:xxx",
59 "chained/somefile.go:xxx"),
61 xerrors.Errorf("wrapw: %w", chained),
62 chain("wraps:wrapw/path.TestErrorf/path.go:xxx",
63 "chained/somefile.go:xxx"),
65 xerrors.Errorf("wrapw %w middle", chained),
66 chain("wraps:wrapw chained middle/path.TestErrorf/path.go:xxx",
67 "chained/somefile.go:xxx"),
69 xerrors.Errorf("not wrapped: %+v", chained),
70 chain("not wrapped: chained: somefile.go:123/path.TestErrorf/path.go:xxx"),
72 for i, tc := range testCases {
73 t.Run(strconv.Itoa(i)+"/"+path.Join(tc.want...), func(t *testing.T) {
74 got := errToParts(tc.got)
75 if !reflect.DeepEqual(got, tc.want) {
76 t.Errorf("Format:\n got: %#v\nwant: %#v", got, tc.want)
79 gotStr := tc.got.Error()
80 wantStr := fmt.Sprint(tc.got)
81 if gotStr != wantStr {
82 t.Errorf("Error:\n got: %#v\nwant: %#v", got, tc.want)
88 func TestErrorFormatter(t *testing.T) {
90 simple = &wrapped{"simple", nil}
92 "can't adumbrate elephant",
95 nonascii = &wrapped{"café", nil}
96 newline = &wrapped{"msg with\nnewline",
97 &wrapped{"and another\none", nil}}
98 fallback = &wrapped{"fallback", os.ErrNotExist}
99 oldAndNew = &wrapped{"new style", formatError("old style")}
100 framed = &withFrameAndMore{
101 frame: xerrors.Caller(0),
103 opaque = &wrapped{"outer",
104 xerrors.Opaque(&wrapped{"mid",
105 &wrapped{"inner", nil}})}
107 testCases := []struct {
119 want: "can't adumbrate elephant: out of peanuts",
121 err: &wrapped{"a", &wrapped{"b", &wrapped{"c", nil}}},
128 "\n somefile.go:123",
132 want: "can't adumbrate elephant:" +
133 "\n somefile.go:123" +
134 "\n - out of peanuts:" +
135 "\n the elephant is on strike" +
136 "\n and the 12 monkeys" +
139 err: &oneNewline{nil},
143 err: &oneNewline{&oneNewline{nil}},
148 err: &newlineAtEnd{nil},
150 want: "newlineAtEnd:\n detail",
152 err: &newlineAtEnd{&newlineAtEnd{nil}},
154 want: "newlineAtEnd:" +
156 "\n - newlineAtEnd:" +
162 "\n golang.org/x/xerrors_test.TestErrorFormatter" +
163 "\n .+/fmt_test.go:101" +
167 err: fmtTwice("Hello World!"),
169 want: "2 times Hello World!",
173 want: "fallback: file does not exist",
177 // Note: no colon after the last error, as there are no details.
179 "\n somefile.go:123" +
180 "\n - file does not exist",
184 want: "outer: mid: inner",
189 "\n somefile.go:123" +
191 "\n somefile.go:123" +
193 "\n somefile.go:123",
197 want: "new style: old style",
201 want: `"new style: old style"`,
205 // Note the extra indentation.
206 // Colon for old style error is rendered by the fmt.Formatter
207 // implementation of the old-style error.
209 "\n somefile.go:123" +
211 "\n otherfile.go:456",
217 // Don't use formatting flags for detailed view.
221 "\n somefile.go:123",
225 want: " can't adumbrate elephant: out of peanuts",
237 want: "73 69 6d 70 6c 65",
242 "\nnewline: and another" +
249 "\n somefile.go:123" +
252 "\n somefile.go:123",
254 err: &wrapped{"", &wrapped{"inner message", nil}},
256 want: "somefile.go:123" +
257 "\n - inner message:" +
258 "\n somefile.go:123",
268 err: spurious("extra"),
272 err: spurious("extra"),
274 want: "spurious:\n" +
281 err: (*wrapped)(nil),
287 want: "*xerrors_test.wrapped",
291 want: "%!🤪(*xerrors_test.wrapped)",
293 // want: "%!🤪(*xerrors_test.wrapped=&{simple <nil>})",
295 err: formatError("use fmt.Formatter"),
297 want: "use fmt.Formatter",
299 err: fmtTwice("%s %s", "ok", panicValue{}),
301 // Different Go versions produce different results.
302 want: `ok %!s\(PANIC=(String method: )?panic\)/ok %!s\(PANIC=(String method: )?panic\)`,
305 err: fmtTwice("%o %s", panicValue{}, "ok"),
309 err: adapted{"adapted", nil},
314 err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}},
323 for i, tc := range testCases {
324 t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) {
325 got := fmt.Sprintf(tc.fmt, tc.err)
329 ok, err = regexp.MatchString(tc.want+"$", got)
337 t.Errorf("\n got: %q\nwant: %q", got, tc.want)
343 func TestAdaptor(t *testing.T) {
344 testCases := []struct {
350 err: adapted{"adapted", nil},
355 err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}},
364 for i, tc := range testCases {
365 t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) {
366 got := fmt.Sprintf(tc.fmt, tc.err)
368 t.Errorf("\n got: %q\nwant: %q", got, tc.want)
374 var _ xerrors.Formatter = wrapped{}
376 type wrapped struct {
381 func (e wrapped) Error() string { return "should call Format" }
383 func (e wrapped) Format(s fmt.State, verb rune) {
384 xerrors.FormatError(&e, s, verb)
387 func (e wrapped) FormatError(p xerrors.Printer) (next error) {
390 p.Print("somefile.go:123")
394 var _ xerrors.Formatter = detailed{}
396 type detailed struct{}
398 func (e detailed) Error() string { panic("should have called FormatError") }
400 func (detailed) FormatError(p xerrors.Printer) (next error) {
401 p.Printf("out of %s", "peanuts")
403 p.Print("the elephant is on strike\n")
404 p.Printf("and the %d monkeys\nare laughing", 12)
408 type withFrameAndMore struct {
412 func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) }
414 func (e *withFrameAndMore) Format(s fmt.State, v rune) {
415 xerrors.FormatError(e, s, v)
418 func (e *withFrameAndMore) FormatError(p xerrors.Printer) (next error) {
422 p.Print("something more")
429 func (e spurious) Error() string { return fmt.Sprint(e) }
431 // move to 1_12 test file
432 func (e spurious) Format(s fmt.State, verb rune) {
433 xerrors.FormatError(e, s, verb)
436 func (e spurious) FormatError(p xerrors.Printer) (next error) {
438 p.Detail() // Call detail even if we don't print anything
442 p.Print("\n", string(e)) // print extraneous leading newline
447 type oneNewline struct {
451 func (e *oneNewline) Error() string { return fmt.Sprint(e) }
453 func (e *oneNewline) Format(s fmt.State, verb rune) {
454 xerrors.FormatError(e, s, verb)
457 func (e *oneNewline) FormatError(p xerrors.Printer) (next error) {
466 type newlineAtEnd struct {
470 func (e *newlineAtEnd) Error() string { return fmt.Sprint(e) }
472 func (e *newlineAtEnd) Format(s fmt.State, verb rune) {
473 xerrors.FormatError(e, s, verb)
476 func (e *newlineAtEnd) FormatError(p xerrors.Printer) (next error) {
477 p.Print("newlineAtEnd")
483 type adapted struct {
488 func (e adapted) Error() string { return string(e.msg) }
490 func (e adapted) Format(s fmt.State, verb rune) {
491 xerrors.FormatError(e, s, verb)
494 func (e adapted) FormatError(p xerrors.Printer) error {
501 // formatError is an error implementing Format instead of xerrors.Formatter.
502 // The implementation mimics the implementation of github.com/pkg/errors.
503 type formatError string
505 func (e formatError) Error() string { return string(e) }
507 func (e formatError) Format(s fmt.State, verb rune) {
508 // Body based on pkg/errors/errors.go
512 io.WriteString(s, string(e))
513 fmt.Fprintf(s, ":\n%s", "otherfile.go:456")
518 io.WriteString(s, string(e))
520 fmt.Fprintf(s, "%q", string(e))
524 func (e formatError) GoString() string {
525 panic("should never be called")
528 type fmtTwiceErr struct {
533 func fmtTwice(format string, a ...interface{}) error {
534 return fmtTwiceErr{format, a}
537 func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
539 func (e fmtTwiceErr) Format(s fmt.State, verb rune) {
540 xerrors.FormatError(e, s, verb)
543 func (e fmtTwiceErr) FormatError(p xerrors.Printer) (next error) {
544 p.Printf(e.format, e.args...)
546 p.Printf(e.format, e.args...)
550 func (e fmtTwiceErr) GoString() string {
551 return "2 times " + fmt.Sprintf(e.format, e.args...)
554 type panicValue struct{}
556 func (panicValue) String() string { panic("panic") }
558 var rePath = regexp.MustCompile(`( [^ ]*)xerrors.*test\.`)
559 var reLine = regexp.MustCompile(":[0-9]*\n?$")
561 func cleanPath(s string) string {
562 s = rePath.ReplaceAllString(s, "/path.")
563 s = reLine.ReplaceAllString(s, ":xxx")
564 s = strings.Replace(s, "\n ", "", -1)
565 s = strings.Replace(s, " /", "/", -1)
569 func errToParts(err error) (a []string) {
572 if xerrors.Unwrap(err) != nil {
575 f, ok := err.(xerrors.Formatter)
577 a = append(a, err.Error())
580 err = f.FormatError(&p)
581 a = append(a, cleanPath(p.str))
587 type testPrinter struct {
591 func (p *testPrinter) Print(a ...interface{}) {
592 p.str += fmt.Sprint(a...)
595 func (p *testPrinter) Printf(format string, a ...interface{}) {
596 p.str += fmt.Sprintf(format, a...)
599 func (p *testPrinter) Detail() bool {