1
2
3
4
5 package xerrors_test
6
7 import (
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
21 func 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
88 func 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
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
206
207
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
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
293
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
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
343 func 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
374 var _ xerrors.Formatter = wrapped{}
375
376 type wrapped struct {
377 msg string
378 err error
379 }
380
381 func (e wrapped) Error() string { return "should call Format" }
382
383 func (e wrapped) Format(s fmt.State, verb rune) {
384 xerrors.FormatError(&e, s, verb)
385 }
386
387 func (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
394 var _ xerrors.Formatter = detailed{}
395
396 type detailed struct{}
397
398 func (e detailed) Error() string { panic("should have called FormatError") }
399
400 func (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
408 type withFrameAndMore struct {
409 frame xerrors.Frame
410 }
411
412 func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) }
413
414 func (e *withFrameAndMore) Format(s fmt.State, v rune) {
415 xerrors.FormatError(e, s, v)
416 }
417
418 func (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
427 type spurious string
428
429 func (e spurious) Error() string { return fmt.Sprint(e) }
430
431
432 func (e spurious) Format(s fmt.State, verb rune) {
433 xerrors.FormatError(e, s, verb)
434 }
435
436 func (e spurious) FormatError(p xerrors.Printer) (next error) {
437 p.Print("spurious")
438 p.Detail()
439 if e == "" {
440 p.Print()
441 } else {
442 p.Print("\n", string(e))
443 }
444 return nil
445 }
446
447 type oneNewline struct {
448 next error
449 }
450
451 func (e *oneNewline) Error() string { return fmt.Sprint(e) }
452
453 func (e *oneNewline) Format(s fmt.State, verb rune) {
454 xerrors.FormatError(e, s, verb)
455 }
456
457 func (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
466 type newlineAtEnd struct {
467 next error
468 }
469
470 func (e *newlineAtEnd) Error() string { return fmt.Sprint(e) }
471
472 func (e *newlineAtEnd) Format(s fmt.State, verb rune) {
473 xerrors.FormatError(e, s, verb)
474 }
475
476 func (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
483 type adapted struct {
484 msg string
485 err error
486 }
487
488 func (e adapted) Error() string { return e.msg }
489
490 func (e adapted) Format(s fmt.State, verb rune) {
491 xerrors.FormatError(e, s, verb)
492 }
493
494 func (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
502
503 type formatError string
504
505 func (e formatError) Error() string { return string(e) }
506
507 func (e formatError) Format(s fmt.State, verb rune) {
508
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
524 func (e formatError) GoString() string {
525 panic("should never be called")
526 }
527
528 type fmtTwiceErr struct {
529 format string
530 args []interface{}
531 }
532
533 func fmtTwice(format string, a ...interface{}) error {
534 return fmtTwiceErr{format, a}
535 }
536
537 func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
538
539 func (e fmtTwiceErr) Format(s fmt.State, verb rune) {
540 xerrors.FormatError(e, s, verb)
541 }
542
543 func (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
550 func (e fmtTwiceErr) GoString() string {
551 return "2 times " + fmt.Sprintf(e.format, e.args...)
552 }
553
554 type panicValue struct{}
555
556 func (panicValue) String() string { panic("panic") }
557
558 var rePath = regexp.MustCompile(`( [^ ]*)xerrors.*test\.`)
559 var reLine = regexp.MustCompile(":[0-9]*\n?$")
560
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)
566 return s
567 }
568
569 func 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
587 type testPrinter struct {
588 str string
589 }
590
591 func (p *testPrinter) Print(a ...interface{}) {
592 p.str += fmt.Sprint(a...)
593 }
594
595 func (p *testPrinter) Printf(format string, a ...interface{}) {
596 p.str += fmt.Sprintf(format, a...)
597 }
598
599 func (p *testPrinter) Detail() bool {
600 p.str += " /"
601 return true
602 }
603
View as plain text