vitess.io/vitess@v0.16.2/go/vt/vterrors/errors_test.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vterrors
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"math/rand"
    25  	"reflect"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  
    31  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    32  )
    33  
    34  func TestWrapNil(t *testing.T) {
    35  	got := Wrap(nil, "no error")
    36  	if got != nil {
    37  		t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got)
    38  	}
    39  }
    40  
    41  func TestWrap(t *testing.T) {
    42  	tests := []struct {
    43  		err         error
    44  		message     string
    45  		wantMessage string
    46  		wantCode    vtrpcpb.Code
    47  	}{
    48  		{io.EOF, "read error", "read error: EOF", vtrpcpb.Code_UNKNOWN},
    49  		{New(vtrpcpb.Code_ALREADY_EXISTS, "oops"), "client error", "client error: oops", vtrpcpb.Code_ALREADY_EXISTS},
    50  	}
    51  
    52  	for _, tt := range tests {
    53  		got := Wrap(tt.err, tt.message)
    54  		if got.Error() != tt.wantMessage {
    55  			t.Errorf("Wrap(%v, %q): got: [%v], want [%v]", tt.err, tt.message, got, tt.wantMessage)
    56  		}
    57  		if Code(got) != tt.wantCode {
    58  			t.Errorf("Wrap(%v, %v): got: [%v], want [%v]", tt.err, tt, Code(got), tt.wantCode)
    59  		}
    60  	}
    61  }
    62  
    63  func TestUnwrap(t *testing.T) {
    64  	tests := []struct {
    65  		err       error
    66  		isWrapped bool
    67  	}{
    68  		{fmt.Errorf("some error: %d", 17), false},
    69  		{errors.New("some new error"), false},
    70  		{Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "some msg %d", 19), false},
    71  		{Wrapf(errors.New("some wrapped error"), "some msg"), true},
    72  		{nil, false},
    73  	}
    74  
    75  	for _, tt := range tests {
    76  		t.Run(fmt.Sprintf("%v", tt.err), func(t *testing.T) {
    77  			{
    78  				wasWrapped, unwrapped := Unwrap(tt.err)
    79  				assert.Equal(t, tt.isWrapped, wasWrapped)
    80  				if !wasWrapped {
    81  					assert.Equal(t, tt.err, unwrapped)
    82  				}
    83  			}
    84  			{
    85  				wrapped := Wrap(tt.err, "some message")
    86  				wasWrapped, unwrapped := Unwrap(wrapped)
    87  				assert.Equal(t, wasWrapped, (tt.err != nil))
    88  				assert.Equal(t, tt.err, unwrapped)
    89  			}
    90  		})
    91  	}
    92  }
    93  
    94  func TestUnwrapAll(t *testing.T) {
    95  	tests := []struct {
    96  		err error
    97  	}{
    98  		{fmt.Errorf("some error: %d", 17)},
    99  		{errors.New("some new error")},
   100  		{Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "some msg %d", 19)},
   101  		{nil},
   102  	}
   103  
   104  	for _, tt := range tests {
   105  		t.Run(fmt.Sprintf("%v", tt.err), func(t *testing.T) {
   106  			{
   107  				// see that unwrapping a non-wrapped error just returns the same error
   108  				unwrapped := UnwrapAll(tt.err)
   109  				assert.Equal(t, tt.err, unwrapped)
   110  			}
   111  			{
   112  				// see that unwrapping a 5-times wrapped error returns the original error
   113  				wrapped := tt.err
   114  				for range rand.Perm(5) {
   115  					wrapped = Wrap(wrapped, "some message")
   116  				}
   117  				unwrapped := UnwrapAll(wrapped)
   118  				assert.Equal(t, tt.err, unwrapped)
   119  			}
   120  		})
   121  	}
   122  
   123  }
   124  
   125  type nilError struct{}
   126  
   127  func (nilError) Error() string { return "nil error" }
   128  
   129  func TestRootCause(t *testing.T) {
   130  	x := New(vtrpcpb.Code_FAILED_PRECONDITION, "error")
   131  	tests := []struct {
   132  		err  error
   133  		want error
   134  	}{{
   135  		// nil error is nil
   136  		err:  nil,
   137  		want: nil,
   138  	}, {
   139  		// explicit nil error is nil
   140  		err:  (error)(nil),
   141  		want: nil,
   142  	}, {
   143  		// typed nil is nil
   144  		err:  (*nilError)(nil),
   145  		want: (*nilError)(nil),
   146  	}, {
   147  		// uncaused error is unaffected
   148  		err:  io.EOF,
   149  		want: io.EOF,
   150  	}, {
   151  		// caused error returns cause
   152  		err:  Wrap(io.EOF, "ignored"),
   153  		want: io.EOF,
   154  	}, {
   155  		err:  x, // return from errors.New
   156  		want: x,
   157  	}}
   158  
   159  	for i, tt := range tests {
   160  		got := RootCause(tt.err)
   161  		if !reflect.DeepEqual(got, tt.want) {
   162  			t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
   163  		}
   164  	}
   165  }
   166  
   167  func TestCause(t *testing.T) {
   168  	x := New(vtrpcpb.Code_FAILED_PRECONDITION, "error")
   169  	tests := []struct {
   170  		err  error
   171  		want error
   172  	}{{
   173  		// nil error is nil
   174  		err:  nil,
   175  		want: nil,
   176  	}, {
   177  		// uncaused error is nil
   178  		err:  io.EOF,
   179  		want: nil,
   180  	}, {
   181  		// caused error returns cause
   182  		err:  Wrap(io.EOF, "ignored"),
   183  		want: io.EOF,
   184  	}, {
   185  		err:  x, // return from errors.New
   186  		want: nil,
   187  	}}
   188  
   189  	for i, tt := range tests {
   190  		got := Cause(tt.err)
   191  		if !reflect.DeepEqual(got, tt.want) {
   192  			t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
   193  		}
   194  	}
   195  }
   196  
   197  func TestWrapfNil(t *testing.T) {
   198  	got := Wrapf(nil, "no error")
   199  	if got != nil {
   200  		t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got)
   201  	}
   202  }
   203  
   204  func TestWrapf(t *testing.T) {
   205  	tests := []struct {
   206  		err     error
   207  		message string
   208  		want    string
   209  	}{
   210  		{io.EOF, "read error", "read error: EOF"},
   211  		{Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"},
   212  		{Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"},
   213  	}
   214  
   215  	for _, tt := range tests {
   216  		got := Wrapf(tt.err, tt.message).Error()
   217  		if got != tt.want {
   218  			t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
   219  		}
   220  	}
   221  }
   222  
   223  func TestErrorf(t *testing.T) {
   224  	tests := []struct {
   225  		err  error
   226  		want string
   227  	}{
   228  		{Errorf(vtrpcpb.Code_DATA_LOSS, "read error without format specifiers"), "read error without format specifiers"},
   229  		{Errorf(vtrpcpb.Code_DATA_LOSS, "read error with %d format specifier", 1), "read error with 1 format specifier"},
   230  	}
   231  
   232  	for _, tt := range tests {
   233  		got := tt.err.Error()
   234  		if got != tt.want {
   235  			t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want)
   236  		}
   237  	}
   238  }
   239  
   240  func innerMost() error {
   241  	return Wrap(io.ErrNoProgress, "oh noes")
   242  }
   243  
   244  func middle() error {
   245  	return innerMost()
   246  }
   247  
   248  func outer() error {
   249  	return middle()
   250  }
   251  
   252  func TestStackFormat(t *testing.T) {
   253  	err := outer()
   254  	got := fmt.Sprintf("%v", err)
   255  
   256  	assertContains(t, got, "innerMost", false)
   257  	assertContains(t, got, "middle", false)
   258  	assertContains(t, got, "outer", false)
   259  
   260  	logErrStacks = true
   261  	defer func() { logErrStacks = false }()
   262  	got = fmt.Sprintf("%v", err)
   263  	assertContains(t, got, "innerMost", true)
   264  	assertContains(t, got, "middle", true)
   265  	assertContains(t, got, "outer", true)
   266  }
   267  
   268  // errors.New, etc values are not expected to be compared by value
   269  // but the change in errors#27 made them incomparable. Assert that
   270  // various kinds of errors have a functional equality operator, even
   271  // if the result of that equality is always false.
   272  func TestErrorEquality(t *testing.T) {
   273  	vals := []error{
   274  		nil,
   275  		io.EOF,
   276  		errors.New("EOF"),
   277  		New(vtrpcpb.Code_ALREADY_EXISTS, "EOF"),
   278  		Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "EOF"),
   279  		Wrap(io.EOF, "EOF"),
   280  		Wrapf(io.EOF, "EOF%d", 2),
   281  	}
   282  
   283  	for i := range vals {
   284  		for j := range vals {
   285  			_ = vals[i] == vals[j] // mustn't panic
   286  		}
   287  	}
   288  }
   289  
   290  func TestCreation(t *testing.T) {
   291  	testcases := []struct {
   292  		in, want vtrpcpb.Code
   293  	}{{
   294  		in:   vtrpcpb.Code_CANCELED,
   295  		want: vtrpcpb.Code_CANCELED,
   296  	}, {
   297  		in:   vtrpcpb.Code_UNKNOWN,
   298  		want: vtrpcpb.Code_UNKNOWN,
   299  	}}
   300  	for _, tcase := range testcases {
   301  		if got := Code(New(tcase.in, "")); got != tcase.want {
   302  			t.Errorf("Code(New(%v)): %v, want %v", tcase.in, got, tcase.want)
   303  		}
   304  		if got := Code(Errorf(tcase.in, "")); got != tcase.want {
   305  			t.Errorf("Code(Errorf(%v)): %v, want %v", tcase.in, got, tcase.want)
   306  		}
   307  	}
   308  }
   309  
   310  func TestCode(t *testing.T) {
   311  	testcases := []struct {
   312  		in   error
   313  		want vtrpcpb.Code
   314  	}{{
   315  		in:   nil,
   316  		want: vtrpcpb.Code_OK,
   317  	}, {
   318  		in:   errors.New("generic"),
   319  		want: vtrpcpb.Code_UNKNOWN,
   320  	}, {
   321  		in:   New(vtrpcpb.Code_CANCELED, "generic"),
   322  		want: vtrpcpb.Code_CANCELED,
   323  	}, {
   324  		in:   context.Canceled,
   325  		want: vtrpcpb.Code_CANCELED,
   326  	}, {
   327  		in:   context.DeadlineExceeded,
   328  		want: vtrpcpb.Code_DEADLINE_EXCEEDED,
   329  	}}
   330  	for _, tcase := range testcases {
   331  		if got := Code(tcase.in); got != tcase.want {
   332  			t.Errorf("Code(%v): %v, want %v", tcase.in, got, tcase.want)
   333  		}
   334  	}
   335  }
   336  
   337  func TestWrapping(t *testing.T) {
   338  	err1 := Errorf(vtrpcpb.Code_UNAVAILABLE, "foo")
   339  	err2 := Wrapf(err1, "bar")
   340  	err3 := Wrapf(err2, "baz")
   341  	errorWithoutStack := fmt.Sprintf("%v", err3)
   342  
   343  	logErrStacks = true
   344  	errorWithStack := fmt.Sprintf("%v", err3)
   345  	logErrStacks = false
   346  
   347  	assertEquals(t, err3.Error(), "baz: bar: foo")
   348  	assertContains(t, errorWithoutStack, "foo", true)
   349  	assertContains(t, errorWithoutStack, "bar", true)
   350  	assertContains(t, errorWithoutStack, "baz", true)
   351  	assertContains(t, errorWithoutStack, "TestWrapping", false)
   352  
   353  	assertContains(t, errorWithStack, "foo", true)
   354  	assertContains(t, errorWithStack, "bar", true)
   355  	assertContains(t, errorWithStack, "baz", true)
   356  	assertContains(t, errorWithStack, "TestWrapping", true)
   357  
   358  }
   359  
   360  func assertContains(t *testing.T, s, substring string, contains bool) {
   361  	t.Helper()
   362  	if doesContain := strings.Contains(s, substring); doesContain != contains {
   363  		t.Errorf("string `%v` contains `%v`: %v, want %v", s, substring, doesContain, contains)
   364  	}
   365  }
   366  
   367  func assertEquals(t *testing.T, a, b any) {
   368  	if a != b {
   369  		t.Fatalf("expected [%s] to be equal to [%s]", a, b)
   370  	}
   371  }