github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/api/errors/errors_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes 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 errors 18 19 import ( 20 "errors" 21 "fmt" 22 "net/http" 23 "reflect" 24 "testing" 25 26 metav1 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/apis/meta/v1" 27 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime" 28 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime/schema" 29 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/validation/field" 30 ) 31 32 func resource(resource string) schema.GroupResource { 33 return schema.GroupResource{Group: "", Resource: resource} 34 } 35 func kind(kind string) schema.GroupKind { 36 return schema.GroupKind{Group: "", Kind: kind} 37 } 38 39 func TestErrorNew(t *testing.T) { 40 err := NewAlreadyExists(resource("tests"), "1") 41 if !IsAlreadyExists(err) { 42 t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) 43 } 44 if IsConflict(err) { 45 t.Errorf("expected to not be %s", metav1.StatusReasonConflict) 46 } 47 if IsNotFound(err) { 48 t.Errorf(fmt.Sprintf("expected to not be %s", metav1.StatusReasonNotFound)) 49 } 50 if IsInvalid(err) { 51 t.Errorf("expected to not be %s", metav1.StatusReasonInvalid) 52 } 53 if IsBadRequest(err) { 54 t.Errorf("expected to not be %s", metav1.StatusReasonBadRequest) 55 } 56 if IsForbidden(err) { 57 t.Errorf("expected to not be %s", metav1.StatusReasonForbidden) 58 } 59 if IsServerTimeout(err) { 60 t.Errorf("expected to not be %s", metav1.StatusReasonServerTimeout) 61 } 62 if IsMethodNotSupported(err) { 63 t.Errorf("expected to not be %s", metav1.StatusReasonMethodNotAllowed) 64 } 65 66 if !IsConflict(NewConflict(resource("tests"), "2", errors.New("message"))) { 67 t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) 68 } 69 if !IsNotFound(NewNotFound(resource("tests"), "3")) { 70 t.Errorf("expected to be %s", metav1.StatusReasonNotFound) 71 } 72 if !IsInvalid(NewInvalid(kind("Test"), "2", nil)) { 73 t.Errorf("expected to be %s", metav1.StatusReasonInvalid) 74 } 75 if !IsBadRequest(NewBadRequest("reason")) { 76 t.Errorf("expected to be %s", metav1.StatusReasonBadRequest) 77 } 78 if !IsForbidden(NewForbidden(resource("tests"), "2", errors.New("reason"))) { 79 t.Errorf("expected to be %s", metav1.StatusReasonForbidden) 80 } 81 if !IsUnauthorized(NewUnauthorized("reason")) { 82 t.Errorf("expected to be %s", metav1.StatusReasonUnauthorized) 83 } 84 if !IsServerTimeout(NewServerTimeout(resource("tests"), "reason", 0)) { 85 t.Errorf("expected to be %s", metav1.StatusReasonServerTimeout) 86 } 87 if !IsMethodNotSupported(NewMethodNotSupported(resource("foos"), "delete")) { 88 t.Errorf("expected to be %s", metav1.StatusReasonMethodNotAllowed) 89 } 90 91 if !IsAlreadyExists(NewGenerateNameConflict(resource("tests"), "3", 1)) { 92 t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) 93 } 94 if time, ok := SuggestsClientDelay(NewGenerateNameConflict(resource("tests"), "3", 1)); time != 1 || !ok { 95 t.Errorf("unexpected %d", time) 96 } 97 98 if time, ok := SuggestsClientDelay(NewServerTimeout(resource("tests"), "doing something", 10)); time != 10 || !ok { 99 t.Errorf("unexpected %d", time) 100 } 101 if time, ok := SuggestsClientDelay(NewServerTimeout(resource("tests"), "doing something", 0)); time != 0 || !ok { 102 t.Errorf("unexpected %d", time) 103 } 104 if time, ok := SuggestsClientDelay(NewTimeoutError("test reason", 10)); time != 10 || !ok { 105 t.Errorf("unexpected %d", time) 106 } 107 if time, ok := SuggestsClientDelay(NewTooManyRequests("doing something", 10)); time != 10 || !ok { 108 t.Errorf("unexpected %d", time) 109 } 110 if time, ok := SuggestsClientDelay(NewTooManyRequests("doing something", 1)); time != 1 || !ok { 111 t.Errorf("unexpected %d", time) 112 } 113 if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, "get", resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok { 114 t.Errorf("unexpected %d", time) 115 } 116 if time, ok := SuggestsClientDelay(NewGenericServerResponse(500, "get", resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok { 117 t.Errorf("unexpected %d", time) 118 } 119 if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, "get", resource("tests"), "test", "doing something", 0, true)); time != 0 || ok { 120 t.Errorf("unexpected %d", time) 121 } 122 } 123 124 func TestNewInvalid(t *testing.T) { 125 testCases := []struct { 126 Err *field.Error 127 Details *metav1.StatusDetails 128 Msg string 129 }{ 130 { 131 field.Duplicate(field.NewPath("field[0].name"), "bar"), 132 &metav1.StatusDetails{ 133 Kind: "Kind", 134 Name: "name", 135 Causes: []metav1.StatusCause{{ 136 Type: metav1.CauseTypeFieldValueDuplicate, 137 Field: "field[0].name", 138 }}, 139 }, 140 `Kind "name" is invalid: field[0].name: Duplicate value: "bar"`, 141 }, 142 { 143 field.Invalid(field.NewPath("field[0].name"), "bar", "detail"), 144 &metav1.StatusDetails{ 145 Kind: "Kind", 146 Name: "name", 147 Causes: []metav1.StatusCause{{ 148 Type: metav1.CauseTypeFieldValueInvalid, 149 Field: "field[0].name", 150 }}, 151 }, 152 `Kind "name" is invalid: field[0].name: Invalid value: "bar": detail`, 153 }, 154 { 155 field.NotFound(field.NewPath("field[0].name"), "bar"), 156 &metav1.StatusDetails{ 157 Kind: "Kind", 158 Name: "name", 159 Causes: []metav1.StatusCause{{ 160 Type: metav1.CauseTypeFieldValueNotFound, 161 Field: "field[0].name", 162 }}, 163 }, 164 `Kind "name" is invalid: field[0].name: Not found: "bar"`, 165 }, 166 { 167 field.NotSupported(field.NewPath("field[0].name"), "bar", nil), 168 &metav1.StatusDetails{ 169 Kind: "Kind", 170 Name: "name", 171 Causes: []metav1.StatusCause{{ 172 Type: metav1.CauseTypeFieldValueNotSupported, 173 Field: "field[0].name", 174 }}, 175 }, 176 `Kind "name" is invalid: field[0].name: Unsupported value: "bar"`, 177 }, 178 { 179 field.Required(field.NewPath("field[0].name"), ""), 180 &metav1.StatusDetails{ 181 Kind: "Kind", 182 Name: "name", 183 Causes: []metav1.StatusCause{{ 184 Type: metav1.CauseTypeFieldValueRequired, 185 Field: "field[0].name", 186 }}, 187 }, 188 `Kind "name" is invalid: field[0].name: Required value`, 189 }, 190 { 191 nil, 192 &metav1.StatusDetails{ 193 Kind: "Kind", 194 Name: "name", 195 Causes: []metav1.StatusCause{}, 196 }, 197 `Kind "name" is invalid`, 198 }, 199 } 200 for i, testCase := range testCases { 201 vErr, expected := testCase.Err, testCase.Details 202 if vErr != nil && expected != nil { 203 expected.Causes[0].Message = vErr.ErrorBody() 204 } 205 var errList field.ErrorList 206 if vErr != nil { 207 errList = append(errList, vErr) 208 } 209 err := NewInvalid(kind("Kind"), "name", errList) 210 status := err.ErrStatus 211 if status.Code != 422 || status.Reason != metav1.StatusReasonInvalid { 212 t.Errorf("%d: unexpected status: %#v", i, status) 213 } 214 if !reflect.DeepEqual(expected, status.Details) { 215 t.Errorf("%d: expected %#v, got %#v", i, expected, status.Details) 216 } 217 if testCase.Msg != status.Message { 218 t.Errorf("%d: expected\n%s\ngot\n%s", i, testCase.Msg, status.Message) 219 } 220 } 221 } 222 223 func TestReasonForError(t *testing.T) { 224 if e, a := metav1.StatusReasonUnknown, ReasonForError(nil); e != a { 225 t.Errorf("unexpected reason type: %#v", a) 226 } 227 } 228 229 type TestType struct{} 230 231 func (obj *TestType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } 232 func (obj *TestType) DeepCopyObject() runtime.Object { 233 if obj == nil { 234 return nil 235 } 236 clone := *obj 237 return &clone 238 } 239 240 func TestFromObject(t *testing.T) { 241 table := []struct { 242 obj runtime.Object 243 message string 244 }{ 245 {&metav1.Status{Message: "foobar"}, "foobar"}, 246 {&TestType{}, "unexpected object: &{}"}, 247 } 248 249 for _, item := range table { 250 if e, a := item.message, FromObject(item.obj).Error(); e != a { 251 t.Errorf("Expected %v, got %v", e, a) 252 } 253 } 254 } 255 256 func TestReasonForErrorSupportsWrappedErrors(t *testing.T) { 257 testCases := []struct { 258 name string 259 err error 260 expectedReason metav1.StatusReason 261 }{ 262 { 263 name: "Direct match", 264 err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonUnauthorized}}, 265 expectedReason: metav1.StatusReasonUnauthorized, 266 }, 267 { 268 name: "No match", 269 err: errors.New("some other error"), 270 expectedReason: metav1.StatusReasonUnknown, 271 }, 272 { 273 name: "Nested match", 274 err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonAlreadyExists}})), 275 expectedReason: metav1.StatusReasonAlreadyExists, 276 }, 277 { 278 name: "Nested, no match", 279 err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), 280 expectedReason: metav1.StatusReasonUnknown, 281 }, 282 { 283 name: "Nil", 284 expectedReason: metav1.StatusReasonUnknown, 285 }, 286 } 287 288 for _, tc := range testCases { 289 t.Run(tc.name, func(t *testing.T) { 290 if result := ReasonForError(tc.err); result != tc.expectedReason { 291 t.Errorf("expected reason: %q, but got known reason: %q", tc.expectedReason, result) 292 } 293 }) 294 } 295 } 296 297 func TestIsTooManyRequestsSupportsWrappedErrors(t *testing.T) { 298 testCases := []struct { 299 name string 300 err error 301 expectMatch bool 302 }{ 303 { 304 name: "Direct match via status reason", 305 err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonTooManyRequests}}, 306 expectMatch: true, 307 }, 308 { 309 name: "Direct match via status code", 310 err: &StatusError{ErrStatus: metav1.Status{Code: http.StatusTooManyRequests}}, 311 expectMatch: true, 312 }, 313 { 314 name: "No match", 315 err: &StatusError{}, 316 expectMatch: false, 317 }, 318 { 319 name: "Nested match via status reason", 320 err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonTooManyRequests}}), 321 expectMatch: true, 322 }, 323 { 324 name: "Nested match via status code", 325 err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusTooManyRequests}}), 326 expectMatch: true, 327 }, 328 { 329 name: "Nested,no match", 330 err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusNotFound}}), 331 expectMatch: false, 332 }, 333 { 334 name: "Nil", 335 expectMatch: false, 336 }, 337 } 338 339 for _, tc := range testCases { 340 if result := IsTooManyRequests(tc.err); result != tc.expectMatch { 341 t.Errorf("Expect match %t, got match %t", tc.expectMatch, result) 342 } 343 } 344 } 345 func TestIsRequestEntityTooLargeErrorSupportsWrappedErrors(t *testing.T) { 346 testCases := []struct { 347 name string 348 err error 349 expectMatch bool 350 }{ 351 { 352 name: "Direct match via status reason", 353 err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonRequestEntityTooLarge}}, 354 expectMatch: true, 355 }, 356 { 357 name: "Direct match via status code", 358 err: &StatusError{ErrStatus: metav1.Status{Code: http.StatusRequestEntityTooLarge}}, 359 expectMatch: true, 360 }, 361 { 362 name: "No match", 363 err: &StatusError{}, 364 expectMatch: false, 365 }, 366 { 367 name: "Nested match via status reason", 368 err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonRequestEntityTooLarge}}), 369 expectMatch: true, 370 }, 371 { 372 name: "Nested match via status code", 373 err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusRequestEntityTooLarge}}), 374 expectMatch: true, 375 }, 376 { 377 name: "Nested,no match", 378 err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusNotFound}}), 379 expectMatch: false, 380 }, 381 { 382 name: "Nil", 383 expectMatch: false, 384 }, 385 } 386 387 for _, tc := range testCases { 388 if result := IsRequestEntityTooLargeError(tc.err); result != tc.expectMatch { 389 t.Errorf("Expect match %t, got match %t", tc.expectMatch, result) 390 } 391 } 392 } 393 394 func TestIsUnexpectedServerError(t *testing.T) { 395 unexpectedServerErr := func() error { 396 return &StatusError{ 397 ErrStatus: metav1.Status{ 398 Details: &metav1.StatusDetails{ 399 Causes: []metav1.StatusCause{{Type: metav1.CauseTypeUnexpectedServerResponse}}, 400 }, 401 }, 402 } 403 } 404 testCases := []struct { 405 name string 406 err error 407 expectMatch bool 408 }{ 409 { 410 name: "Direct match", 411 err: unexpectedServerErr(), 412 expectMatch: true, 413 }, 414 { 415 name: "No match", 416 err: errors.New("some other error"), 417 expectMatch: false, 418 }, 419 { 420 name: "Nested match", 421 err: fmt.Errorf("wrapping: %w", unexpectedServerErr()), 422 expectMatch: true, 423 }, 424 { 425 name: "Nested, no match", 426 err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), 427 expectMatch: false, 428 }, 429 { 430 name: "Nil", 431 expectMatch: false, 432 }, 433 } 434 435 for _, tc := range testCases { 436 t.Run(tc.name, func(t *testing.T) { 437 if result := IsUnexpectedServerError(tc.err); result != tc.expectMatch { 438 t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) 439 } 440 }) 441 } 442 } 443 444 func TestIsUnexpectedObjectError(t *testing.T) { 445 unexpectedObjectErr := func() error { 446 return &UnexpectedObjectError{} 447 } 448 testCases := []struct { 449 name string 450 err error 451 expectMatch bool 452 }{ 453 { 454 name: "Direct match", 455 err: unexpectedObjectErr(), 456 expectMatch: true, 457 }, 458 { 459 name: "No match", 460 err: errors.New("some other error"), 461 expectMatch: false, 462 }, 463 { 464 name: "Nested match", 465 err: fmt.Errorf("wrapping: %w", unexpectedObjectErr()), 466 expectMatch: true, 467 }, 468 { 469 name: "Nested, no match", 470 err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), 471 expectMatch: false, 472 }, 473 { 474 name: "Nil", 475 expectMatch: false, 476 }, 477 } 478 479 for _, tc := range testCases { 480 t.Run(tc.name, func(t *testing.T) { 481 if result := IsUnexpectedObjectError(tc.err); result != tc.expectMatch { 482 t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) 483 } 484 }) 485 } 486 } 487 488 func TestSuggestsClientDelaySupportsWrapping(t *testing.T) { 489 suggestsClientDelayErr := func() error { 490 return &StatusError{ 491 ErrStatus: metav1.Status{ 492 Reason: metav1.StatusReasonServerTimeout, 493 Details: &metav1.StatusDetails{}, 494 }, 495 } 496 } 497 testCases := []struct { 498 name string 499 err error 500 expectMatch bool 501 }{ 502 { 503 name: "Direct match", 504 err: suggestsClientDelayErr(), 505 expectMatch: true, 506 }, 507 { 508 name: "No match", 509 err: errors.New("some other error"), 510 expectMatch: false, 511 }, 512 { 513 name: "Nested match", 514 err: fmt.Errorf("wrapping: %w", suggestsClientDelayErr()), 515 expectMatch: true, 516 }, 517 { 518 name: "Nested, no match", 519 err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), 520 expectMatch: false, 521 }, 522 { 523 name: "Nil", 524 expectMatch: false, 525 }, 526 } 527 528 for _, tc := range testCases { 529 t.Run(tc.name, func(t *testing.T) { 530 if _, result := SuggestsClientDelay(tc.err); result != tc.expectMatch { 531 t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) 532 } 533 }) 534 } 535 } 536 537 func TestIsErrorTypesByReasonAndCode(t *testing.T) { 538 testCases := []struct { 539 name string 540 knownReason metav1.StatusReason 541 otherReason metav1.StatusReason 542 otherReasonConsidered bool 543 code int32 544 fn func(error) bool 545 }{ 546 { 547 name: "IsRequestEntityTooLarge", 548 knownReason: metav1.StatusReasonRequestEntityTooLarge, 549 otherReason: metav1.StatusReasonForbidden, 550 otherReasonConsidered: false, 551 code: http.StatusRequestEntityTooLarge, 552 fn: IsRequestEntityTooLargeError, 553 }, { 554 name: "TooManyRequests", 555 knownReason: metav1.StatusReasonTooManyRequests, 556 otherReason: metav1.StatusReasonForbidden, 557 otherReasonConsidered: false, 558 code: http.StatusTooManyRequests, 559 fn: IsTooManyRequests, 560 }, { 561 name: "Forbidden", 562 knownReason: metav1.StatusReasonForbidden, 563 otherReason: metav1.StatusReasonNotFound, 564 otherReasonConsidered: true, 565 code: http.StatusForbidden, 566 fn: IsForbidden, 567 }, { 568 name: "NotFound", 569 knownReason: metav1.StatusReasonNotFound, 570 otherReason: metav1.StatusReasonForbidden, 571 otherReasonConsidered: true, 572 code: http.StatusNotFound, 573 fn: IsNotFound, 574 }, 575 } 576 577 for _, tc := range testCases { 578 t.Run(tc.name, func(t *testing.T) { 579 t.Run("by known reason", func(t *testing.T) { 580 err := &StatusError{ 581 metav1.Status{ 582 Reason: tc.knownReason, 583 }, 584 } 585 586 got := tc.fn(err) 587 if !got { 588 t.Errorf("expected reason %s to match", tc.knownReason) 589 } 590 }) 591 592 t.Run("by code and unknown reason", func(t *testing.T) { 593 err := &StatusError{ 594 metav1.Status{ 595 Reason: metav1.StatusReasonUnknown, // this could be _any_ reason that isn't in knownReasons. 596 Code: tc.code, 597 }, 598 } 599 600 got := tc.fn(err) 601 if !got { 602 t.Errorf("expected code %d with reason %s to match", tc.code, tc.otherReason) 603 } 604 }) 605 606 if !tc.otherReasonConsidered { 607 return 608 } 609 610 t.Run("by code and other known reason", func(t *testing.T) { 611 err := &StatusError{ 612 metav1.Status{ 613 Reason: tc.otherReason, 614 Code: tc.code, 615 }, 616 } 617 618 got := tc.fn(err) 619 if got { 620 t.Errorf("expected code %d with reason %s to not match", tc.code, tc.otherReason) 621 } 622 }) 623 624 }) 625 626 } 627 } 628 629 func TestStatusCauseSupportsWrappedErrors(t *testing.T) { 630 err := &StatusError{ErrStatus: metav1.Status{ 631 Details: &metav1.StatusDetails{ 632 Causes: []metav1.StatusCause{{Type: "SomeCause"}}, 633 }, 634 }} 635 636 if cause, ok := StatusCause(nil, "SomeCause"); ok { 637 t.Errorf("expected no cause for nil, got %v: %#v", ok, cause) 638 } 639 if cause, ok := StatusCause(errors.New("boom"), "SomeCause"); ok { 640 t.Errorf("expected no cause for wrong type, got %v: %#v", ok, cause) 641 } 642 643 if cause, ok := StatusCause(err, "Other"); ok { 644 t.Errorf("expected no cause for wrong name, got %v: %#v", ok, cause) 645 } 646 if cause, ok := StatusCause(err, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { 647 t.Errorf("expected cause, got %v: %#v", ok, cause) 648 } 649 650 wrapped := fmt.Errorf("once: %w", err) 651 if cause, ok := StatusCause(wrapped, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { 652 t.Errorf("expected cause when wrapped, got %v: %#v", ok, cause) 653 } 654 655 nested := fmt.Errorf("twice: %w", wrapped) 656 if cause, ok := StatusCause(nested, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { 657 t.Errorf("expected cause when nested, got %v: %#v", ok, cause) 658 } 659 } 660 661 func BenchmarkIsAlreadyExistsWrappedErrors(b *testing.B) { 662 err := NewAlreadyExists(schema.GroupResource{}, "") 663 wrapped := fmt.Errorf("once: %w", err) 664 665 b.Run("Nil", func(b *testing.B) { 666 for i := 0; i < b.N; i++ { 667 IsAlreadyExists(nil) 668 } 669 }) 670 671 b.Run("Bare", func(b *testing.B) { 672 for i := 0; i < b.N; i++ { 673 IsAlreadyExists(err) 674 } 675 }) 676 677 b.Run("Wrapped", func(b *testing.B) { 678 for i := 0; i < b.N; i++ { 679 IsAlreadyExists(wrapped) 680 } 681 }) 682 } 683 684 func BenchmarkIsNotFoundWrappedErrors(b *testing.B) { 685 err := NewNotFound(schema.GroupResource{}, "") 686 wrapped := fmt.Errorf("once: %w", err) 687 688 b.Run("Nil", func(b *testing.B) { 689 for i := 0; i < b.N; i++ { 690 IsNotFound(nil) 691 } 692 }) 693 694 b.Run("Bare", func(b *testing.B) { 695 for i := 0; i < b.N; i++ { 696 IsNotFound(err) 697 } 698 }) 699 700 b.Run("Wrapped", func(b *testing.B) { 701 for i := 0; i < b.N; i++ { 702 IsNotFound(wrapped) 703 } 704 }) 705 }