go.uber.org/yarpc@v1.72.1/yarpcerrors/errors_test.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package yarpcerrors
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"fmt"
    27  	"testing"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  var (
    34  	_codeToErrorConstructor = map[Code]func(string, ...interface{}) error{
    35  		CodeCancelled:          CancelledErrorf,
    36  		CodeUnknown:            UnknownErrorf,
    37  		CodeInvalidArgument:    InvalidArgumentErrorf,
    38  		CodeDeadlineExceeded:   DeadlineExceededErrorf,
    39  		CodeNotFound:           NotFoundErrorf,
    40  		CodeAlreadyExists:      AlreadyExistsErrorf,
    41  		CodePermissionDenied:   PermissionDeniedErrorf,
    42  		CodeResourceExhausted:  ResourceExhaustedErrorf,
    43  		CodeFailedPrecondition: FailedPreconditionErrorf,
    44  		CodeAborted:            AbortedErrorf,
    45  		CodeOutOfRange:         OutOfRangeErrorf,
    46  		CodeUnimplemented:      UnimplementedErrorf,
    47  		CodeInternal:           InternalErrorf,
    48  		CodeUnavailable:        UnavailableErrorf,
    49  		CodeDataLoss:           DataLossErrorf,
    50  		CodeUnauthenticated:    UnauthenticatedErrorf,
    51  	}
    52  	_codeToIsErrorWithCode = map[Code]func(error) bool{
    53  		CodeCancelled:          IsCancelled,
    54  		CodeUnknown:            IsUnknown,
    55  		CodeInvalidArgument:    IsInvalidArgument,
    56  		CodeDeadlineExceeded:   IsDeadlineExceeded,
    57  		CodeNotFound:           IsNotFound,
    58  		CodeAlreadyExists:      IsAlreadyExists,
    59  		CodePermissionDenied:   IsPermissionDenied,
    60  		CodeResourceExhausted:  IsResourceExhausted,
    61  		CodeFailedPrecondition: IsFailedPrecondition,
    62  		CodeAborted:            IsAborted,
    63  		CodeOutOfRange:         IsOutOfRange,
    64  		CodeUnimplemented:      IsUnimplemented,
    65  		CodeInternal:           IsInternal,
    66  		CodeUnavailable:        IsUnavailable,
    67  		CodeDataLoss:           IsDataLoss,
    68  		CodeUnauthenticated:    IsUnauthenticated,
    69  	}
    70  )
    71  
    72  func TestErrorsString(t *testing.T) {
    73  	testAllErrorConstructors(
    74  		t,
    75  		func(t *testing.T, code Code, errorConstructor func(string, ...interface{}) error) {
    76  			status, ok := errorConstructor("hello %d", 1).(*Status)
    77  			require.True(t, ok)
    78  			require.Equal(t, fmt.Sprintf("code:%s message:hello 1", code.String()), status.Error())
    79  		},
    80  		func(t *testing.T) {
    81  			status, ok := NamedErrorf("foo", "hello %d", 1).(*Status)
    82  			require.True(t, ok)
    83  			require.Equal(t, "code:unknown name:foo message:hello 1", status.Error())
    84  		},
    85  	)
    86  }
    87  
    88  func TestIsYARPCError(t *testing.T) {
    89  	testAllErrorConstructors(
    90  		t,
    91  		func(t *testing.T, code Code, errorConstructor func(string, ...interface{}) error) {
    92  			require.True(t, IsYARPCError(errorConstructor("")))
    93  		},
    94  		func(t *testing.T) {
    95  			require.True(t, IsYARPCError(NamedErrorf("", "")))
    96  		},
    97  	)
    98  }
    99  
   100  func TestErrorCode(t *testing.T) {
   101  	testAllErrorConstructors(
   102  		t,
   103  		func(t *testing.T, code Code, errorConstructor func(string, ...interface{}) error) {
   104  			require.Equal(t, code, ErrorCode(errorConstructor("")))
   105  		},
   106  		func(t *testing.T) {
   107  			require.Equal(t, CodeUnknown, ErrorCode(NamedErrorf("", "")))
   108  		},
   109  	)
   110  }
   111  
   112  func TestErrorName(t *testing.T) {
   113  	testAllErrorConstructors(
   114  		t,
   115  		func(t *testing.T, code Code, errorConstructor func(string, ...interface{}) error) {
   116  			require.Empty(t, ErrorName(errorConstructor("")))
   117  		},
   118  		func(t *testing.T) {
   119  			require.Equal(t, "foo", ErrorName(NamedErrorf("foo", "")))
   120  		},
   121  	)
   122  }
   123  
   124  func TestErrorMessage(t *testing.T) {
   125  	testAllErrorConstructors(
   126  		t,
   127  		func(t *testing.T, code Code, errorConstructor func(string, ...interface{}) error) {
   128  			require.Equal(t, "hello 1", ErrorMessage(errorConstructor("hello %d", 1)))
   129  		},
   130  		func(t *testing.T) {
   131  			require.Equal(t, "hello 1", ErrorMessage(NamedErrorf("foo", "hello %d", 1)))
   132  		},
   133  	)
   134  }
   135  
   136  func TestIsErrorWithCode(t *testing.T) {
   137  	for code, errorConstructor := range _codeToErrorConstructor {
   138  		t.Run(code.String(), func(t *testing.T) {
   139  			isErrorWithCode, ok := _codeToIsErrorWithCode[code]
   140  			require.True(t, ok)
   141  			require.True(t, isErrorWithCode(errorConstructor("")))
   142  		})
   143  	}
   144  }
   145  
   146  func TestNonYARPCErrors(t *testing.T) {
   147  	assert.Equal(t, CodeOK, ErrorCode(nil))
   148  	assert.Equal(t, CodeUnknown, ErrorCode(errors.New("")))
   149  	assert.Equal(t, "", ErrorName(nil))
   150  	assert.Equal(t, "", ErrorName(errors.New("")))
   151  	assert.Equal(t, "", ErrorMessage(nil))
   152  	assert.Equal(t, "", ErrorMessage(errors.New("")))
   153  	assert.Nil(t, FromHeaders(CodeOK, "", ""))
   154  }
   155  
   156  func testAllErrorConstructors(
   157  	t *testing.T,
   158  	errorConstructorFunc func(*testing.T, Code, func(string, ...interface{}) error),
   159  	namedFunc func(*testing.T),
   160  ) {
   161  	for code, errorConstructor := range _codeToErrorConstructor {
   162  		t.Run(code.String(), func(t *testing.T) {
   163  			errorConstructorFunc(t, code, errorConstructor)
   164  		})
   165  	}
   166  	t.Run("Named", namedFunc)
   167  }
   168  
   169  func TestErrUnwrap(t *testing.T) {
   170  	myErr := errors.New("my custom error")
   171  	yErr := AbortedErrorf("wrap my custom err: %w", myErr)
   172  
   173  	assert.Equal(t, FromError(yErr).Message(), "wrap my custom err: my custom error", "unexpected message")
   174  	assert.Equal(t, myErr, errors.Unwrap(yErr), "expected original error")
   175  	assert.Equal(t, myErr, errors.Unwrap(FromError(myErr)), "expected original error")
   176  	assert.True(t, errors.Is(yErr, myErr), "expected original error")
   177  }
   178  
   179  func TestErrUnwrapIs(t *testing.T) {
   180  	t.Run("FromError", func(t *testing.T) {
   181  		err := FromError(context.DeadlineExceeded)
   182  		assert.True(t, errors.Is(err, context.DeadlineExceeded), "errors be errors, yo")
   183  	})
   184  
   185  	t.Run("DeadlineExceededErrorf", func(t *testing.T) {
   186  		err := DeadlineExceededErrorf("Past due: %w", context.DeadlineExceeded)
   187  		assert.True(t, errors.Is(err, context.DeadlineExceeded), "errors be errors, yo")
   188  	})
   189  }
   190  
   191  func TestErrUnwrapNewf(t *testing.T) {
   192  	t.Run("no format", func(t *testing.T) {
   193  		err := Newf(CodeAborted, "not going to do it")
   194  		assert.NoError(t, errors.Unwrap(err))
   195  	})
   196  
   197  	t.Run("formatted with v verb", func(t *testing.T) {
   198  		origErr := errors.New("something broke")
   199  		err := Newf(CodeAborted, "not going to do it: %v", origErr)
   200  		assert.NoError(t, errors.Unwrap(err)) // %v hides the inner error
   201  	})
   202  
   203  	t.Run("wrapped with w verb", func(t *testing.T) {
   204  		origErr := errors.New("something broke")
   205  		err := Newf(CodeAborted, "not going to do it: %w", origErr)
   206  		assert.Equal(t, origErr, errors.Unwrap(err))
   207  	})
   208  }
   209  
   210  func TestErrUnwrapNil(t *testing.T) {
   211  	assert.NotPanics(t, func() {
   212  		var err *Status
   213  		errors.Unwrap(err)
   214  	})
   215  
   216  	assert.NotPanics(t, func() {
   217  		err := &Status{}
   218  		errors.Unwrap(err)
   219  	})
   220  }
   221  
   222  type customYARPCError struct {
   223  	err string
   224  }
   225  
   226  func (e customYARPCError) Error() string {
   227  	return e.err
   228  }
   229  func (e customYARPCError) YARPCError() *Status {
   230  	return FromError(DataLossErrorf(e.err))
   231  }
   232  
   233  func TestFromError(t *testing.T) {
   234  	t.Run("nil", func(t *testing.T) {
   235  		assert.Nil(t, FromError(nil))
   236  	})
   237  
   238  	t.Run("unknown err", func(t *testing.T) {
   239  		st := FromError(errors.New("foo"))
   240  		assert.Equal(t, CodeUnknown.String(), st.Code().String(), "unexpected code")
   241  	})
   242  
   243  	t.Run("wrapped Status", func(t *testing.T) {
   244  		wrappedErr := fmt.Errorf("wrap 2: %w",
   245  			FailedPreconditionErrorf("wrap 1: %w", // yarpc error
   246  				errors.New("inner")))
   247  
   248  		st := FromError(wrappedErr)
   249  		assert.Equal(t, CodeFailedPrecondition.String(), st.Code().String(), "unexpected Code")
   250  		assert.Equal(t, "wrap 1: inner", st.Message())
   251  	})
   252  
   253  	t.Run("wrapped Status interface", func(t *testing.T) {
   254  		st := FromError(fmt.Errorf("wrapped: %w", customYARPCError{err: "custom err"}))
   255  		assert.Equal(t, CodeDataLoss.String(), st.Code().String(), "unexpected Code")
   256  		assert.Equal(t, "custom err", st.Message())
   257  	})
   258  }
   259  
   260  func TestIsStatus(t *testing.T) {
   261  	t.Run("nil", func(t *testing.T) {
   262  		assert.False(t, IsStatus(nil))
   263  	})
   264  
   265  	t.Run("unknown err", func(t *testing.T) {
   266  		err := errors.New("foo")
   267  		assert.False(t, IsStatus(err), "unexpected Status")
   268  	})
   269  
   270  	t.Run("wrapped Status", func(t *testing.T) {
   271  		err := fmt.Errorf("wrap 2: %w",
   272  			FailedPreconditionErrorf("wrap 1: %w", // yarpc error
   273  				errors.New("inner")))
   274  
   275  		assert.True(t, IsStatus(err), "expected YARPC error")
   276  	})
   277  
   278  	t.Run("wrapped Status interface", func(t *testing.T) {
   279  		err := fmt.Errorf("wrapped: %w", customYARPCError{err: "custom err"})
   280  		assert.True(t, IsStatus(err))
   281  	})
   282  }
   283  
   284  func TestErrorWithFmtVerbs(t *testing.T) {
   285  	err := errors.New(`http://foo%s: invalid URL escape "%s"`)
   286  	assert.EqualError(t, UnknownErrorf(err.Error()), FromError(err).Error())
   287  }
   288  
   289  func TestWrapError(t *testing.T) {
   290  	t.Run("nil", func(t *testing.T) {
   291  		var we *wrapError
   292  		assert.Empty(t, we.Error())
   293  		assert.NoError(t, errors.Unwrap(we))
   294  	})
   295  
   296  	t.Run("empty", func(t *testing.T) {
   297  		we := &wrapError{}
   298  		assert.Empty(t, we.Error())
   299  		assert.NoError(t, errors.Unwrap(we))
   300  	})
   301  
   302  	t.Run("full", func(t *testing.T) {
   303  		inner := errors.New("i'm a little error")
   304  		we := &wrapError{err: inner}
   305  		assert.Equal(t, inner.Error(), we.Error())
   306  		assert.Equal(t, inner, errors.Unwrap(we))
   307  	})
   308  }