...

Source file src/golang.org/x/xerrors/fmt_test.go

Documentation: golang.org/x/xerrors

     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  
     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  		// 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  
   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  // move to 1_12 test file
   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() // 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  
   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  // 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
   504  
   505  func (e formatError) Error() string { return string(e) }
   506  
   507  func (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  
   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