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 }