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.
4
5package xerrors_test
6
7import (
8	"fmt"
9	"io"
10	"os"
11	"path"
12	"reflect"
13	"regexp"
14	"strconv"
15	"strings"
16	"testing"
17
18	"golang.org/x/xerrors"
19)
20
21func TestErrorf(t *testing.T) {
22	chained := &wrapped{"chained", nil}
23	chain := func(s ...string) (a []string) {
24		for _, s := range s {
25			a = append(a, cleanPath(s))
26		}
27		return a
28	}
29	testCases := []struct {
30		got  error
31		want []string
32	}{{
33		xerrors.Errorf("no args"),
34		chain("no args/path.TestErrorf/path.go:xxx"),
35	}, {
36		xerrors.Errorf("no args: %s"),
37		chain("no args: %!s(MISSING)/path.TestErrorf/path.go:xxx"),
38	}, {
39		xerrors.Errorf("nounwrap: %s", "simple"),
40		chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
41	}, {
42		xerrors.Errorf("nounwrap: %v", "simple"),
43		chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
44	}, {
45		xerrors.Errorf("%s failed: %v", "foo", chained),
46		chain("foo failed/path.TestErrorf/path.go:xxx",
47			"chained/somefile.go:xxx"),
48	}, {
49		xerrors.Errorf("no wrap: %s", chained),
50		chain("no wrap/path.TestErrorf/path.go:xxx",
51			"chained/somefile.go:xxx"),
52	}, {
53		xerrors.Errorf("%s failed: %w", "foo", chained),
54		chain("wraps:foo failed/path.TestErrorf/path.go:xxx",
55			"chained/somefile.go:xxx"),
56	}, {
57		xerrors.Errorf("nowrapv: %v", chained),
58		chain("nowrapv/path.TestErrorf/path.go:xxx",
59			"chained/somefile.go:xxx"),
60	}, {
61		xerrors.Errorf("wrapw: %w", chained),
62		chain("wraps:wrapw/path.TestErrorf/path.go:xxx",
63			"chained/somefile.go:xxx"),
64	}, {
65		xerrors.Errorf("wrapw %w middle", chained),
66		chain("wraps:wrapw chained middle/path.TestErrorf/path.go:xxx",
67			"chained/somefile.go:xxx"),
68	}, {
69		xerrors.Errorf("not wrapped: %+v", chained),
70		chain("not wrapped: chained: somefile.go:123/path.TestErrorf/path.go:xxx"),
71	}}
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)
77			}
78
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)
83			}
84		})
85	}
86}
87
88func TestErrorFormatter(t *testing.T) {
89	var (
90		simple   = &wrapped{"simple", nil}
91		elephant = &wrapped{
92			"can't adumbrate elephant",
93			detailed{},
94		}
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),
102		}
103		opaque = &wrapped{"outer",
104			xerrors.Opaque(&wrapped{"mid",
105				&wrapped{"inner", nil}})}
106	)
107	testCases := []struct {
108		err    error
109		fmt    string
110		want   string
111		regexp bool
112	}{{
113		err:  simple,
114		fmt:  "%s",
115		want: "simple",
116	}, {
117		err:  elephant,
118		fmt:  "%s",
119		want: "can't adumbrate elephant: out of peanuts",
120	}, {
121		err:  &wrapped{"a", &wrapped{"b", &wrapped{"c", nil}}},
122		fmt:  "%s",
123		want: "a: b: c",
124	}, {
125		err: simple,
126		fmt: "%+v",
127		want: "simple:" +
128			"\n    somefile.go:123",
129	}, {
130		err: elephant,
131		fmt: "%+v",
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" +
137			"\n    are laughing",
138	}, {
139		err:  &oneNewline{nil},
140		fmt:  "%+v",
141		want: "123",
142	}, {
143		err: &oneNewline{&oneNewline{nil}},
144		fmt: "%+v",
145		want: "123:" +
146			"\n  - 123",
147	}, {
148		err:  &newlineAtEnd{nil},
149		fmt:  "%+v",
150		want: "newlineAtEnd:\n    detail",
151	}, {
152		err: &newlineAtEnd{&newlineAtEnd{nil}},
153		fmt: "%+v",
154		want: "newlineAtEnd:" +
155			"\n    detail" +
156			"\n  - newlineAtEnd:" +
157			"\n    detail",
158	}, {
159		err: framed,
160		fmt: "%+v",
161		want: "something:" +
162			"\n    golang.org/x/xerrors_test.TestErrorFormatter" +
163			"\n        .+/fmt_test.go:101" +
164			"\n    something more",
165		regexp: true,
166	}, {
167		err:  fmtTwice("Hello World!"),
168		fmt:  "%#v",
169		want: "2 times Hello World!",
170	}, {
171		err:  fallback,
172		fmt:  "%s",
173		want: "fallback: file does not exist",
174	}, {
175		err: fallback,
176		fmt: "%+v",
177		// Note: no colon after the last error, as there are no details.
178		want: "fallback:" +
179			"\n    somefile.go:123" +
180			"\n  - file does not exist",
181	}, {
182		err:  opaque,
183		fmt:  "%s",
184		want: "outer: mid: inner",
185	}, {
186		err: opaque,
187		fmt: "%+v",
188		want: "outer:" +
189			"\n    somefile.go:123" +
190			"\n  - mid:" +
191			"\n    somefile.go:123" +
192			"\n  - inner:" +
193			"\n    somefile.go:123",
194	}, {
195		err:  oldAndNew,
196		fmt:  "%v",
197		want: "new style: old style",
198	}, {
199		err:  oldAndNew,
200		fmt:  "%q",
201		want: `"new style: old style"`,
202	}, {
203		err: oldAndNew,
204		fmt: "%+v",
205		// Note the extra indentation.
206		// Colon for old style error is rendered by the fmt.Formatter
207		// implementation of the old-style error.
208		want: "new style:" +
209			"\n    somefile.go:123" +
210			"\n  - old style:" +
211			"\n    otherfile.go:456",
212	}, {
213		err:  simple,
214		fmt:  "%-12s",
215		want: "simple      ",
216	}, {
217		// Don't use formatting flags for detailed view.
218		err: simple,
219		fmt: "%+12v",
220		want: "simple:" +
221			"\n    somefile.go:123",
222	}, {
223		err:  elephant,
224		fmt:  "%+50s",
225		want: "          can't adumbrate elephant: out of peanuts",
226	}, {
227		err:  nonascii,
228		fmt:  "%q",
229		want: `"café"`,
230	}, {
231		err:  nonascii,
232		fmt:  "%+q",
233		want: `"caf\u00e9"`,
234	}, {
235		err:  simple,
236		fmt:  "% x",
237		want: "73 69 6d 70 6c 65",
238	}, {
239		err: newline,
240		fmt: "%s",
241		want: "msg with" +
242			"\nnewline: and another" +
243			"\none",
244	}, {
245		err: newline,
246		fmt: "%+v",
247		want: "msg with" +
248			"\n    newline:" +
249			"\n    somefile.go:123" +
250			"\n  - and another" +
251			"\n    one:" +
252			"\n    somefile.go:123",
253	}, {
254		err: &wrapped{"", &wrapped{"inner message", nil}},
255		fmt: "%+v",
256		want: "somefile.go:123" +
257			"\n  - inner message:" +
258			"\n    somefile.go:123",
259	}, {
260		err:  spurious(""),
261		fmt:  "%s",
262		want: "spurious",
263	}, {
264		err:  spurious(""),
265		fmt:  "%+v",
266		want: "spurious",
267	}, {
268		err:  spurious("extra"),
269		fmt:  "%s",
270		want: "spurious",
271	}, {
272		err: spurious("extra"),
273		fmt: "%+v",
274		want: "spurious:\n" +
275			"    extra",
276	}, {
277		err:  nil,
278		fmt:  "%+v",
279		want: "<nil>",
280	}, {
281		err:  (*wrapped)(nil),
282		fmt:  "%+v",
283		want: "<nil>",
284	}, {
285		err:  simple,
286		fmt:  "%T",
287		want: "*xerrors_test.wrapped",
288	}, {
289		err:  simple,
290		fmt:  "%��",
291		want: "%!��(*xerrors_test.wrapped)",
292		// For 1.13:
293		//  want: "%!��(*xerrors_test.wrapped=&{simple <nil>})",
294	}, {
295		err:  formatError("use fmt.Formatter"),
296		fmt:  "%#v",
297		want: "use fmt.Formatter",
298	}, {
299		err: fmtTwice("%s %s", "ok", panicValue{}),
300		fmt: "%s",
301		// Different Go versions produce different results.
302		want:   `ok %!s\(PANIC=(String method: )?panic\)/ok %!s\(PANIC=(String method: )?panic\)`,
303		regexp: true,
304	}, {
305		err:  fmtTwice("%o %s", panicValue{}, "ok"),
306		fmt:  "%s",
307		want: "{} ok/{} ok",
308	}, {
309		err: adapted{"adapted", nil},
310		fmt: "%+v",
311		want: "adapted:" +
312			"\n    detail",
313	}, {
314		err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}},
315		fmt: "%+v",
316		want: "outer:" +
317			"\n    detail" +
318			"\n  - mid:" +
319			"\n    detail" +
320			"\n  - inner:" +
321			"\n    detail",
322	}}
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)
326			var ok bool
327			if tc.regexp {
328				var err error
329				ok, err = regexp.MatchString(tc.want+"$", got)
330				if err != nil {
331					t.Fatal(err)
332				}
333			} else {
334				ok = got == tc.want
335			}
336			if !ok {
337				t.Errorf("\n got: %q\nwant: %q", got, tc.want)
338			}
339		})
340	}
341}
342
343func TestAdaptor(t *testing.T) {
344	testCases := []struct {
345		err    error
346		fmt    string
347		want   string
348		regexp bool
349	}{{
350		err: adapted{"adapted", nil},
351		fmt: "%+v",
352		want: "adapted:" +
353			"\n    detail",
354	}, {
355		err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}},
356		fmt: "%+v",
357		want: "outer:" +
358			"\n    detail" +
359			"\n  - mid:" +
360			"\n    detail" +
361			"\n  - inner:" +
362			"\n    detail",
363	}}
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)
367			if got != tc.want {
368				t.Errorf("\n got: %q\nwant: %q", got, tc.want)
369			}
370		})
371	}
372}
373
374var _ xerrors.Formatter = wrapped{}
375
376type wrapped struct {
377	msg string
378	err error
379}
380
381func (e wrapped) Error() string { return "should call Format" }
382
383func (e wrapped) Format(s fmt.State, verb rune) {
384	xerrors.FormatError(&e, s, verb)
385}
386
387func (e wrapped) FormatError(p xerrors.Printer) (next error) {
388	p.Print(e.msg)
389	p.Detail()
390	p.Print("somefile.go:123")
391	return e.err
392}
393
394var _ xerrors.Formatter = detailed{}
395
396type detailed struct{}
397
398func (e detailed) Error() string { return fmt.Sprint(e) }
399
400func (detailed) FormatError(p xerrors.Printer) (next error) {
401	p.Printf("out of %s", "peanuts")
402	p.Detail()
403	p.Print("the elephant is on strike\n")
404	p.Printf("and the %d monkeys\nare laughing", 12)
405	return nil
406}
407
408type withFrameAndMore struct {
409	frame xerrors.Frame
410}
411
412func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) }
413
414func (e *withFrameAndMore) Format(s fmt.State, v rune) {
415	xerrors.FormatError(e, s, v)
416}
417
418func (e *withFrameAndMore) FormatError(p xerrors.Printer) (next error) {
419	p.Print("something")
420	if p.Detail() {
421		e.frame.Format(p)
422		p.Print("something more")
423	}
424	return nil
425}
426
427type spurious string
428
429func (e spurious) Error() string { return fmt.Sprint(e) }
430
431// move to 1_12 test file
432func (e spurious) Format(s fmt.State, verb rune) {
433	xerrors.FormatError(e, s, verb)
434}
435
436func (e spurious) FormatError(p xerrors.Printer) (next error) {
437	p.Print("spurious")
438	p.Detail() // Call detail even if we don't print anything
439	if e == "" {
440		p.Print()
441	} else {
442		p.Print("\n", string(e)) // print extraneous leading newline
443	}
444	return nil
445}
446
447type oneNewline struct {
448	next error
449}
450
451func (e *oneNewline) Error() string { return fmt.Sprint(e) }
452
453func (e *oneNewline) Format(s fmt.State, verb rune) {
454	xerrors.FormatError(e, s, verb)
455}
456
457func (e *oneNewline) FormatError(p xerrors.Printer) (next error) {
458	p.Print("1")
459	p.Print("2")
460	p.Print("3")
461	p.Detail()
462	p.Print("\n")
463	return e.next
464}
465
466type newlineAtEnd struct {
467	next error
468}
469
470func (e *newlineAtEnd) Error() string { return fmt.Sprint(e) }
471
472func (e *newlineAtEnd) Format(s fmt.State, verb rune) {
473	xerrors.FormatError(e, s, verb)
474}
475
476func (e *newlineAtEnd) FormatError(p xerrors.Printer) (next error) {
477	p.Print("newlineAtEnd")
478	p.Detail()
479	p.Print("detail\n")
480	return e.next
481}
482
483type adapted struct {
484	msg string
485	err error
486}
487
488func (e adapted) Error() string { return string(e.msg) }
489
490func (e adapted) Format(s fmt.State, verb rune) {
491	xerrors.FormatError(e, s, verb)
492}
493
494func (e adapted) FormatError(p xerrors.Printer) error {
495	p.Print(e.msg)
496	p.Detail()
497	p.Print("detail")
498	return e.err
499}
500
501// formatError is an error implementing Format instead of xerrors.Formatter.
502// The implementation mimics the implementation of github.com/pkg/errors.
503type formatError string
504
505func (e formatError) Error() string { return string(e) }
506
507func (e formatError) Format(s fmt.State, verb rune) {
508	// Body based on pkg/errors/errors.go
509	switch verb {
510	case 'v':
511		if s.Flag('+') {
512			io.WriteString(s, string(e))
513			fmt.Fprintf(s, ":\n%s", "otherfile.go:456")
514			return
515		}
516		fallthrough
517	case 's':
518		io.WriteString(s, string(e))
519	case 'q':
520		fmt.Fprintf(s, "%q", string(e))
521	}
522}
523
524func (e formatError) GoString() string {
525	panic("should never be called")
526}
527
528type fmtTwiceErr struct {
529	format string
530	args   []interface{}
531}
532
533func fmtTwice(format string, a ...interface{}) error {
534	return fmtTwiceErr{format, a}
535}
536
537func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
538
539func (e fmtTwiceErr) Format(s fmt.State, verb rune) {
540	xerrors.FormatError(e, s, verb)
541}
542
543func (e fmtTwiceErr) FormatError(p xerrors.Printer) (next error) {
544	p.Printf(e.format, e.args...)
545	p.Print("/")
546	p.Printf(e.format, e.args...)
547	return nil
548}
549
550func (e fmtTwiceErr) GoString() string {
551	return "2 times " + fmt.Sprintf(e.format, e.args...)
552}
553
554type panicValue struct{}
555
556func (panicValue) String() string { panic("panic") }
557
558var rePath = regexp.MustCompile(`( [^ ]*)xerrors.*test\.`)
559var reLine = regexp.MustCompile(":[0-9]*\n?$")
560
561func 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)
566	return s
567}
568
569func errToParts(err error) (a []string) {
570	for err != nil {
571		var p testPrinter
572		if xerrors.Unwrap(err) != nil {
573			p.str += "wraps:"
574		}
575		f, ok := err.(xerrors.Formatter)
576		if !ok {
577			a = append(a, err.Error())
578			break
579		}
580		err = f.FormatError(&p)
581		a = append(a, cleanPath(p.str))
582	}
583	return a
584
585}
586
587type testPrinter struct {
588	str string
589}
590
591func (p *testPrinter) Print(a ...interface{}) {
592	p.str += fmt.Sprint(a...)
593}
594
595func (p *testPrinter) Printf(format string, a ...interface{}) {
596	p.str += fmt.Sprintf(format, a...)
597}
598
599func (p *testPrinter) Detail() bool {
600	p.str += " /"
601	return true
602}
603