github.com/mailgun/holster/v4@v4.20.0/errors/format_test.go (about)

     1  package errors
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"regexp"
     8  	"strings"
     9  	"testing"
    10  )
    11  
    12  func TestFormatNew(t *testing.T) {
    13  	tests := []struct {
    14  		error
    15  		format string
    16  		want   string
    17  	}{{
    18  		New("error"),
    19  		"%s",
    20  		"error",
    21  	}, {
    22  		New("error"),
    23  		"%v",
    24  		"error",
    25  	}, {
    26  		New("error"),
    27  		"%+v",
    28  		"error\n" +
    29  			"github.com/mailgun/holster/v4/errors.TestFormatNew\n" +
    30  			"\t.+/errors/format_test.go:26",
    31  	}, {
    32  		New("error"),
    33  		"%q",
    34  		`"error"`,
    35  	}}
    36  
    37  	for i, tt := range tests {
    38  		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
    39  	}
    40  }
    41  
    42  func TestFormatErrorf(t *testing.T) {
    43  	tests := []struct {
    44  		error
    45  		format string
    46  		want   string
    47  	}{{
    48  		Errorf("%s", "error"),
    49  		"%s",
    50  		"error",
    51  	}, {
    52  		Errorf("%s", "error"),
    53  		"%v",
    54  		"error",
    55  	}, {
    56  		Errorf("%s", "error"),
    57  		"%+v",
    58  		"error\n" +
    59  			"github.com/mailgun/holster/v4/errors.TestFormatErrorf\n" +
    60  			"\t.+/errors/format_test.go:56",
    61  	}}
    62  
    63  	for i, tt := range tests {
    64  		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
    65  	}
    66  }
    67  
    68  func TestFormatWrap(t *testing.T) {
    69  	tests := []struct {
    70  		error
    71  		format string
    72  		want   string
    73  	}{{
    74  		Wrap(New("error"), "error2"),
    75  		"%s",
    76  		"error2: error",
    77  	}, {
    78  		Wrap(New("error"), "error2"),
    79  		"%v",
    80  		"error2: error",
    81  	}, {
    82  		Wrap(New("error"), "error2"),
    83  		"%+v",
    84  		"error\n" +
    85  			"github.com/mailgun/holster/v4/errors.TestFormatWrap\n" +
    86  			"\t.+/errors/format_test.go:82",
    87  	}, {
    88  		Wrap(io.EOF, "error"),
    89  		"%s",
    90  		"error: EOF",
    91  	}, {
    92  		Wrap(io.EOF, "error"),
    93  		"%v",
    94  		"error: EOF",
    95  	}, {
    96  		Wrap(io.EOF, "error"),
    97  		"%+v",
    98  		"EOF\n" +
    99  			"error\n" +
   100  			"github.com/mailgun/holster/v4/errors.TestFormatWrap\n" +
   101  			"\t.+/errors/format_test.go:96",
   102  	}, {
   103  		Wrap(Wrap(io.EOF, "error1"), "error2"),
   104  		"%+v",
   105  		"EOF\n" +
   106  			"error1\n" +
   107  			"github.com/mailgun/holster/v4/errors.TestFormatWrap\n" +
   108  			"\t.+/errors/format_test.go:103\n",
   109  	}, {
   110  		Wrap(New("error with space"), "context"),
   111  		"%q",
   112  		`"context: error with space"`,
   113  	}}
   114  
   115  	for i, tt := range tests {
   116  		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
   117  	}
   118  }
   119  
   120  func TestFormatWrapf(t *testing.T) {
   121  	tests := []struct {
   122  		error
   123  		format string
   124  		want   string
   125  	}{{
   126  		Wrapf(io.EOF, "error%d", 2),
   127  		"%s",
   128  		"error2: EOF",
   129  	}, {
   130  		Wrapf(io.EOF, "error%d", 2),
   131  		"%v",
   132  		"error2: EOF",
   133  	}, {
   134  		Wrapf(io.EOF, "error%d", 2),
   135  		"%+v",
   136  		"EOF\n" +
   137  			"error2\n" +
   138  			"github.com/mailgun/holster/v4/errors.TestFormatWrapf\n" +
   139  			"\t.+/errors/format_test.go:134",
   140  	}, {
   141  		Wrapf(New("error"), "error%d", 2),
   142  		"%s",
   143  		"error2: error",
   144  	}, {
   145  		Wrapf(New("error"), "error%d", 2),
   146  		"%v",
   147  		"error2: error",
   148  	}, {
   149  		Wrapf(New("error"), "error%d", 2),
   150  		"%+v",
   151  		"error\n" +
   152  			"github.com/mailgun/holster/v4/errors.TestFormatWrapf\n" +
   153  			"\t.+/errors/format_test.go:149",
   154  	}}
   155  
   156  	for i, tt := range tests {
   157  		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
   158  	}
   159  }
   160  
   161  func TestFormatWithStack(t *testing.T) {
   162  	tests := []struct {
   163  		error
   164  		format string
   165  		want   []string
   166  	}{{
   167  		WithStack(io.EOF),
   168  		"%s",
   169  		[]string{"EOF"},
   170  	}, {
   171  		WithStack(io.EOF),
   172  		"%v",
   173  		[]string{"EOF"},
   174  	}, {
   175  		WithStack(io.EOF),
   176  		"%+v",
   177  		[]string{"EOF",
   178  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   179  				"\t.+/errors/format_test.go:175"},
   180  	}, {
   181  		WithStack(New("error")),
   182  		"%s",
   183  		[]string{"error"},
   184  	}, {
   185  		WithStack(New("error")),
   186  		"%v",
   187  		[]string{"error"},
   188  	}, {
   189  		WithStack(New("error")),
   190  		"%+v",
   191  		[]string{"error",
   192  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   193  				"\t.+/errors/format_test.go:189",
   194  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   195  				"\t.+/errors/format_test.go:189"},
   196  	}, {
   197  		WithStack(WithStack(io.EOF)),
   198  		"%+v",
   199  		[]string{"EOF",
   200  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   201  				"\t.+/errors/format_test.go:197",
   202  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   203  				"\t.+/errors/format_test.go:197"},
   204  	}, {
   205  		WithStack(WithStack(Wrapf(io.EOF, "message"))),
   206  		"%+v",
   207  		[]string{"EOF",
   208  			"message",
   209  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   210  				"\t.+/errors/format_test.go:205",
   211  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   212  				"\t.+/errors/format_test.go:205",
   213  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   214  				"\t.+/errors/format_test.go:205"},
   215  	}, {
   216  		WithStack(Errorf("error%d", 1)),
   217  		"%+v",
   218  		[]string{"error1",
   219  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   220  				"\t.+/errors/format_test.go:216",
   221  			"github.com/mailgun/holster/v4/errors.TestFormatWithStack\n" +
   222  				"\t.+/errors/format_test.go:216"},
   223  	}}
   224  
   225  	for i, tt := range tests {
   226  		testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
   227  	}
   228  }
   229  
   230  func TestFormatWithMessage(t *testing.T) {
   231  	tests := []struct {
   232  		error
   233  		format string
   234  		want   []string
   235  	}{{
   236  		WithMessage(New("error"), "error2"),
   237  		"%s",
   238  		[]string{"error2: error"},
   239  	}, {
   240  		WithMessage(New("error"), "error2"),
   241  		"%v",
   242  		[]string{"error2: error"},
   243  	}, {
   244  		WithMessage(New("error"), "error2"),
   245  		"%+v",
   246  		[]string{
   247  			"error",
   248  			"github.com/mailgun/holster/v4/errors.TestFormatWithMessage\n" +
   249  				"\t.+/errors/format_test.go:244",
   250  			"error2"},
   251  	}, {
   252  		WithMessage(io.EOF, "addition1"),
   253  		"%s",
   254  		[]string{"addition1: EOF"},
   255  	}, {
   256  		WithMessage(io.EOF, "addition1"),
   257  		"%v",
   258  		[]string{"addition1: EOF"},
   259  	}, {
   260  		WithMessage(io.EOF, "addition1"),
   261  		"%+v",
   262  		[]string{"EOF", "addition1"},
   263  	}, {
   264  		WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
   265  		"%v",
   266  		[]string{"addition2: addition1: EOF"},
   267  	}, {
   268  		WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
   269  		"%+v",
   270  		[]string{"EOF", "addition1", "addition2"},
   271  	}, {
   272  		Wrap(WithMessage(io.EOF, "error1"), "error2"),
   273  		"%+v",
   274  		[]string{"EOF", "error1", "error2",
   275  			"github.com/mailgun/holster/v4/errors.TestFormatWithMessage\n" +
   276  				"\t.+/errors/format_test.go:272"},
   277  	}, {
   278  		WithMessage(Errorf("error%d", 1), "error2"),
   279  		"%+v",
   280  		[]string{"error1",
   281  			"github.com/mailgun/holster/v4/errors.TestFormatWithMessage\n" +
   282  				"\t.+/errors/format_test.go:278",
   283  			"error2"},
   284  	}, {
   285  		WithMessage(WithStack(io.EOF), "error"),
   286  		"%+v",
   287  		[]string{
   288  			"EOF",
   289  			"github.com/mailgun/holster/v4/errors.TestFormatWithMessage\n" +
   290  				"\t.+/errors/format_test.go:285",
   291  			"error"},
   292  	}, {
   293  		WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
   294  		"%+v",
   295  		[]string{
   296  			"EOF",
   297  			"github.com/mailgun/holster/v4/errors.TestFormatWithMessage\n" +
   298  				"\t.+/errors/format_test.go:293",
   299  			"inside-error",
   300  			"github.com/mailgun/holster/v4/errors.TestFormatWithMessage\n" +
   301  				"\t.+/errors/format_test.go:293",
   302  			"outside-error"},
   303  	}}
   304  
   305  	for i, tt := range tests {
   306  		testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
   307  	}
   308  }
   309  
   310  func TestFormatGeneric(t *testing.T) {
   311  	t.Skip("TODO: fix https://github.com/mailgun/holster/issues/153")
   312  
   313  	starts := []struct {
   314  		err  error
   315  		want []string
   316  	}{
   317  		{New("new-error"), []string{
   318  			"new-error",
   319  			"github.com/mailgun/holster/v4/errors.TestFormatGeneric\n" +
   320  				"\t.+/errors/format_test.go:315"},
   321  		}, {Errorf("errorf-error"), []string{
   322  			"errorf-error",
   323  			"github.com/mailgun/holster/v4/errors.TestFormatGeneric\n" +
   324  				"\t.+/errors/format_test.go:319"},
   325  		}, {errors.New("errors-new-error"), []string{
   326  			"errors-new-error"},
   327  		},
   328  	}
   329  
   330  	wrappers := []wrapper{
   331  		{
   332  			func(err error) error { return WithMessage(err, "with-message") },
   333  			[]string{"with-message"},
   334  		}, {
   335  			WithStack,
   336  			[]string{
   337  				"github.com/mailgun/holster/v4/errors.(func·002|TestFormatGeneric.func2)\n\t" +
   338  					".+/errors/format_test.go:333",
   339  			},
   340  		}, {
   341  			func(err error) error { return Wrap(err, "wrap-error") },
   342  			[]string{
   343  				"wrap-error",
   344  				"github.com/mailgun/holster/v4/errors.(func·003|TestFormatGeneric.func3)\n\t" +
   345  					".+/errors/format_test.go:339",
   346  			},
   347  		}, {
   348  			func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
   349  			[]string{
   350  				"wrapf-error1",
   351  				"github.com/mailgun/holster/v4/errors.(func·004|TestFormatGeneric.func4)\n\t" +
   352  					".+/errors/format_test.go:346",
   353  			},
   354  		},
   355  	}
   356  
   357  	for i := range starts {
   358  		err := starts[i].err
   359  		want := starts[i].want
   360  		testFormatCompleteCompare(t, i, err, "%+v", want, false)
   361  		testGenericRecursive(t, err, want, wrappers, 3)
   362  	}
   363  }
   364  
   365  func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
   366  	got := fmt.Sprintf(format, arg)
   367  	gotLines := strings.Split(got, "\n")
   368  	wantLines := strings.Split(want, "\n")
   369  
   370  	if len(wantLines) > len(gotLines) {
   371  		t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
   372  		return
   373  	}
   374  
   375  	for i, w := range wantLines {
   376  		match, err := regexp.MatchString(w, gotLines[i])
   377  		if err != nil {
   378  			t.Fatal(err)
   379  		}
   380  		if !match {
   381  			t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
   382  		}
   383  	}
   384  }
   385  
   386  var stackLineR = regexp.MustCompile(`\.`)
   387  
   388  // parseBlocks parses input into a slice, where:
   389  //   - incase entry contains a newline, its a stacktrace
   390  //   - incase entry contains no newline, its a solo line.
   391  //
   392  // Detecting stack boundaries only works incase the WithStack-calls are
   393  // to be found on the same line, thats why it is optionally here.
   394  //
   395  // Example use:
   396  //
   397  //	for _, e := range blocks {
   398  //	  if strings.ContainsAny(e, "\n") {
   399  //	    // Match as stack
   400  //	  } else {
   401  //	    // Match as line
   402  //	  }
   403  //	}
   404  func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
   405  	var blocks []string
   406  
   407  	stack := ""
   408  	wasStack := false
   409  	lines := map[string]bool{} // already found lines
   410  
   411  	for _, l := range strings.Split(input, "\n") {
   412  		isStackLine := stackLineR.MatchString(l)
   413  
   414  		switch {
   415  		case !isStackLine && wasStack:
   416  			blocks = append(blocks, stack, l)
   417  			stack = ""
   418  			lines = map[string]bool{}
   419  		case isStackLine:
   420  			if wasStack {
   421  				// Detecting two stacks after another, possible cause lines match in
   422  				// our tests due to WithStack(WithStack(io.EOF)) on same line.
   423  				if detectStackboundaries {
   424  					if lines[l] {
   425  						if stack == "" {
   426  							return nil, errors.New("len of block must not be zero here")
   427  						}
   428  
   429  						blocks = append(blocks, stack)
   430  						stack = l
   431  						lines = map[string]bool{l: true}
   432  						continue
   433  					}
   434  				}
   435  
   436  				stack = stack + "\n" + l
   437  			} else {
   438  				stack = l
   439  			}
   440  			lines[l] = true
   441  		case !isStackLine && !wasStack:
   442  			blocks = append(blocks, l)
   443  		default:
   444  			return nil, errors.New("must not happen")
   445  		}
   446  
   447  		wasStack = isStackLine
   448  	}
   449  
   450  	// Use up stack
   451  	if stack != "" {
   452  		blocks = append(blocks, stack)
   453  	}
   454  	return blocks, nil
   455  }
   456  
   457  func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
   458  	gotStr := fmt.Sprintf(format, arg)
   459  
   460  	got, err := parseBlocks(gotStr, detectStackBoundaries)
   461  	if err != nil {
   462  		t.Fatal(err)
   463  	}
   464  
   465  	if len(got) != len(want) {
   466  		t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
   467  			n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
   468  	}
   469  
   470  	for i := range got {
   471  		if strings.ContainsAny(want[i], "\n") {
   472  			// Match as stack
   473  			match, err := regexp.MatchString(want[i], got[i])
   474  			if err != nil {
   475  				t.Fatal(err)
   476  			}
   477  			if !match {
   478  				t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
   479  					n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
   480  			}
   481  		} else if got[i] != want[i] {
   482  			// Match as message
   483  			t.Fatalf("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])
   484  		}
   485  	}
   486  }
   487  
   488  type wrapper struct {
   489  	wrap func(err error) error
   490  	want []string
   491  }
   492  
   493  func prettyBlocks(blocks []string, prefix ...string) string {
   494  	return "   " + strings.Join(blocks, "\n   ")
   495  }
   496  
   497  func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
   498  	if len(beforeWant) == 0 {
   499  		panic("beforeWant must not be empty")
   500  	}
   501  	for _, w := range list {
   502  		if len(w.want) == 0 {
   503  			panic("want must not be empty")
   504  		}
   505  
   506  		err := w.wrap(beforeErr)
   507  
   508  		// Copy required cause append(beforeWant, ..) modified beforeWant subtly.
   509  		beforeCopy := make([]string, len(beforeWant))
   510  		copy(beforeCopy, beforeWant)
   511  
   512  		beforeWant := beforeCopy
   513  		last := len(beforeWant) - 1
   514  		var want []string
   515  
   516  		// Merge two stacks behind each other.
   517  		if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
   518  			want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
   519  		} else {
   520  			want = append(beforeWant, w.want...)
   521  		}
   522  
   523  		testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
   524  		if maxDepth > 0 {
   525  			testGenericRecursive(t, err, want, list, maxDepth-1)
   526  		}
   527  	}
   528  }