github.com/cockroachdb/errors@v1.11.1/markers/markers_test.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. See the License for the specific language governing 13 // permissions and limitations under the License. 14 15 package markers_test 16 17 import ( 18 "context" 19 "errors" 20 goErr "errors" 21 "fmt" 22 "io" 23 "net" 24 "strings" 25 "testing" 26 27 "github.com/cockroachdb/errors/errbase" 28 "github.com/cockroachdb/errors/markers" 29 "github.com/cockroachdb/errors/testutils" 30 pkgErr "github.com/pkg/errors" 31 ) 32 33 // This test demonstrates that Is() returns true if passed the same 34 // error reference twice, and that errors that are structurally 35 // different appear different via Is(). 36 func TestLocalErrorEquivalence(t *testing.T) { 37 tt := testutils.T{T: t} 38 39 err1 := errors.New("hello") 40 err2 := errors.New("world") 41 var nilErr error 42 43 tt.Check(!markers.Is(err1, err2)) 44 tt.Check(markers.Is(err1, err1)) 45 tt.Check(markers.Is(err2, err2)) 46 tt.Check(!markers.Is(err1, nilErr)) 47 tt.Check(markers.Is(nilErr, nilErr)) 48 tt.Check(!markers.Is(nilErr, err1)) 49 } 50 51 // This test demonstrates that Is() returns true if 52 // two errors are structurally equivalent. 53 func TestStructuralEquivalence(t *testing.T) { 54 tt := testutils.T{T: t} 55 56 err1 := errors.New("hello") 57 err2 := errors.New("hello") 58 59 tt.Check(markers.Is(err1, err2)) 60 } 61 62 // This test demonstrates that both the error type and package path 63 // are used to ascertain equivalence. 64 func TestErrorTypeEquivalence(t *testing.T) { 65 tt := testutils.T{T: t} 66 67 err1 := errors.New("hello") 68 err2 := pkgErr.New("hello") 69 err3 := &fundamental{msg: "hello"} 70 71 tt.Check(!markers.Is(err1, err2)) 72 tt.Check(!markers.Is(err2, err3)) 73 } 74 75 // fundamental is a local error type, but it has the 76 // same name as the type in github.com/pkg/errors. 77 type fundamental struct { 78 msg string 79 } 80 81 func (e *fundamental) Error() string { return e.msg } 82 83 func network(err error) error { 84 enc := errbase.EncodeError(context.Background(), err) 85 return errbase.DecodeError(context.Background(), enc) 86 } 87 88 // This test demonstrates that the equivalence 89 // of errors is preserved over the network. 90 func TestRemoteErrorEquivalence(t *testing.T) { 91 tt := testutils.T{T: t} 92 93 err1 := errors.New("hello") 94 err2 := errors.New("world") 95 96 newErr1 := network(err1) 97 98 tt.Check(markers.Is(err1, newErr1)) 99 tt.Check(markers.Is(newErr1, err1)) 100 tt.Check(!markers.Is(err2, newErr1)) 101 } 102 103 // This test demonstrates that it is possible to recognize standard 104 // errors that have been sent over the network. 105 func TestStandardErrorRemoteEquivalence(t *testing.T) { 106 tt := testutils.T{T: t} 107 108 err1 := io.EOF 109 err2 := context.DeadlineExceeded 110 111 newErr1 := network(err1) 112 113 tt.Check(markers.Is(err1, newErr1)) 114 tt.Check(markers.Is(newErr1, err1)) 115 tt.Check(!markers.Is(err2, newErr1)) 116 } 117 118 // This test demonstrates that it is possible to recognize standard 119 // errors that have been sent over the network. 120 func TestStandardFmtErrorRemoteEquivalence(t *testing.T) { 121 tt := testutils.T{T: t} 122 123 err1 := fmt.Errorf("hello") 124 err2 := fmt.Errorf("world") 125 126 newErr1 := network(err1) 127 128 tt.Check(markers.Is(err1, newErr1)) 129 tt.Check(markers.Is(newErr1, err1)) 130 tt.Check(!markers.Is(err2, newErr1)) 131 tt.Check(!markers.Is(newErr1, err2)) 132 } 133 134 // This test demonstrates that when the error library does not know 135 // how to encode an error, it still knows that it is different from 136 // other errors of different types, even though the message may be the 137 // same. 138 func TestUnknownErrorTypeDifference(t *testing.T) { 139 tt := testutils.T{T: t} 140 141 err1 := &fundamental{msg: "hello"} 142 err2 := &fundamental2{msg: "hello"} 143 144 tt.Check(!markers.Is(err1, err2)) 145 146 newErr1 := network(err1) 147 148 tt.Check(markers.Is(err1, newErr1)) 149 150 newErr2 := network(err2) 151 152 tt.Check(!markers.Is(newErr1, newErr2)) 153 } 154 155 // fundamental2 is a local error type, and 156 // like fundamental above it is not known to the 157 // library (no decoders registered, no proto encoding). 158 type fundamental2 struct { 159 msg string 160 } 161 162 func (e *fundamental2) Error() string { return e.msg } 163 164 // This test demonstrates that the error library preserves 165 // the type difference between known errors of different types. 166 func TestKnownErrorTypeDifference(t *testing.T) { 167 tt := testutils.T{T: t} 168 169 err1 := errors.New("hello") 170 err2 := pkgErr.New("hello") 171 172 tt.Check(!markers.Is(err1, err2)) 173 174 newErr1 := network(err1) 175 newErr2 := network(err2) 176 177 tt.Check(markers.Is(err1, newErr1)) 178 tt.Check(markers.Is(err2, newErr2)) 179 180 tt.Check(!markers.Is(newErr1, newErr2)) 181 } 182 183 func TestStandardFmtSingleWrapRemoteEquivalence(t *testing.T) { 184 tt := testutils.T{T: t} 185 186 err1 := fmt.Errorf("hello %w", goErr.New("world")) 187 err2 := fmt.Errorf("hello %w", goErr.New("earth")) 188 189 newErr1 := network(err1) 190 191 tt.Check(markers.Is(err1, newErr1)) 192 tt.Check(markers.Is(newErr1, err1)) 193 tt.Check(!markers.Is(err2, newErr1)) 194 tt.Check(!markers.Is(newErr1, err2)) 195 } 196 197 // This test demonstrates that two errors that are structurally 198 // different can be made to become equivalent by using the same 199 // marker. 200 func TestMarkerDrivenEquivalence(t *testing.T) { 201 tt := testutils.T{T: t} 202 203 err1 := errors.New("hello") 204 err2 := errors.New("world") 205 206 tt.Check(!markers.Is(err1, err2)) 207 208 m := errors.New("mark") 209 err1w := markers.Mark(err1, m) 210 err2w := markers.Mark(err2, m) 211 212 tt.Check(markers.Is(err1w, m)) 213 tt.Check(markers.Is(err2w, m)) 214 215 tt.Check(markers.Is(err1w, err2w)) 216 } 217 218 // This test demonstrates that equivalence can be "peeked" through 219 // behind multiple layers of wrapping. 220 func TestWrappedEquivalence(t *testing.T) { 221 tt := testutils.T{T: t} 222 223 err1 := errors.New("hello") 224 err2 := pkgErr.Wrap(errors.New("hello"), "world") 225 226 tt.Check(markers.Is(err2, err1)) 227 228 m2 := errors.New("m2") 229 err2w := markers.Mark(err2, m2) 230 231 tt.Check(markers.Is(err2w, err1)) 232 } 233 234 // This test demonstrates that equivalence can be "peeked" through 235 // behind multiple layers of wrapping. 236 func TestGoErrWrappedEquivalence(t *testing.T) { 237 tt := testutils.T{T: t} 238 239 err1 := errors.New("hello") 240 err2 := fmt.Errorf("an error %w", err1) 241 242 tt.Check(markers.Is(err2, err1)) 243 244 m2 := errors.New("m2") 245 err2w := markers.Mark(err2, m2) 246 247 tt.Check(markers.Is(err2w, m2)) 248 } 249 250 // This test demonstrates that equivalence can be "peeked" through 251 // behind multiple layers of wrapping. 252 func TestGoMultiErrWrappedEquivalence(t *testing.T) { 253 tt := testutils.T{T: t} 254 255 err1 := errors.New("hello") 256 err2 := errors.New("world") 257 err3 := fmt.Errorf("an error %w and %w", err1, err2) 258 259 tt.Check(markers.Is(err3, err1)) 260 tt.Check(markers.Is(err3, err2)) 261 262 m3 := errors.New("m3") 263 err3w := markers.Mark(err3, m3) 264 265 tt.Check(markers.Is(err3w, m3)) 266 267 err4 := fmt.Errorf("error: %w", err3) 268 269 tt.Check(markers.Is(err4, err1)) 270 tt.Check(markers.Is(err4, err2)) 271 } 272 273 type myErr struct{ msg string } 274 275 func (e *myErr) Error() string { 276 return e.msg 277 } 278 279 // This test demonstrates that it is possible to recognize standard 280 // multierrors that have been sent over the network. 281 func TestStandardFmtMultierrorRemoteEquivalence(t *testing.T) { 282 tt := testutils.T{T: t} 283 284 err1 := fmt.Errorf("hello %w %w", goErr.New("world"), goErr.New("one")) 285 err2 := fmt.Errorf("hello %w %w", goErr.New("world"), goErr.New("two")) 286 287 newErr1 := network(err1) 288 289 tt.Check(markers.Is(err1, newErr1)) 290 tt.Check(markers.Is(newErr1, err1)) 291 tt.Check(!markers.Is(err2, newErr1)) 292 tt.Check(!markers.Is(newErr1, err2)) 293 294 // Check multiple levels of causal nesting 295 err3 := fmt.Errorf("err: %w", goErr.Join(err1, err2, &myErr{msg: "hi"})) 296 newErr3 := network(err3) 297 myErrV := &myErr{msg: "hi"} 298 299 tt.Check(markers.Is(err3, newErr3)) 300 tt.Check(markers.Is(newErr3, err3)) 301 302 tt.Check(markers.Is(err3, myErrV)) 303 tt.Check(markers.Is(newErr3, myErrV)) 304 } 305 306 type myMultiError struct{ cause error } 307 308 func (e myMultiError) Error() string { return e.cause.Error() } 309 func (e myMultiError) Unwrap() []error { return []error{e.cause} } 310 311 type myOtherMultiError struct{ cause error } 312 313 func (e myOtherMultiError) Error() string { return e.cause.Error() } 314 func (e myOtherMultiError) Unwrap() []error { return []error{e.cause} } 315 316 func TestDifferentMultiErrorTypesCompareDifferentOverNetwork(t *testing.T) { 317 tt := testutils.T{T: t} 318 319 base := goErr.New("woo") 320 e1 := myMultiError{base} 321 e2 := myOtherMultiError{base} 322 323 tt.Check(!markers.Is(e1, e2)) 324 325 de1 := network(e1) 326 de2 := network(e2) 327 328 tt.Check(!markers.Is(de1, de2)) 329 } 330 331 // This test demonstrates that errors from the join 332 // and fmt constructors are properly considered as distinct. 333 func TestStandardFmtMultierrorRemoteRecursiveEquivalence(t *testing.T) { 334 tt := testutils.T{T: t} 335 336 baseErr := goErr.New("world") 337 err1 := fmt.Errorf("%w %w", baseErr, baseErr) 338 err2 := goErr.Join(baseErr, baseErr) 339 340 tt.Check(markers.Is(err1, baseErr)) 341 tt.Check(!markers.Is(err1, err2)) 342 tt.Check(!markers.Is(err2, err1)) 343 344 newErr1 := network(err1) 345 newErr2 := network(err2) 346 347 tt.Check(markers.Is(newErr1, baseErr)) 348 tt.Check(markers.Is(newErr2, baseErr)) 349 tt.Check(!markers.Is(newErr1, newErr2)) 350 tt.Check(!markers.Is(err1, newErr2)) 351 tt.Check(!markers.Is(err2, newErr1)) 352 tt.Check(!markers.Is(newErr2, err1)) 353 tt.Check(!markers.Is(newErr1, err2)) 354 } 355 356 // This check verifies that IsAny() works. 357 func TestIsAny(t *testing.T) { 358 tt := testutils.T{T: t} 359 360 err1 := errors.New("hello") 361 err2 := errors.New("world") 362 err3 := pkgErr.Wrap(err1, "world") 363 err4 := pkgErr.Wrap(err2, "universe") 364 var nilErr error 365 366 tt.Check(markers.IsAny(err1, err1)) 367 tt.Check(!markers.IsAny(err1, err2, err3, err4)) 368 tt.Check(markers.IsAny(err3, err1)) 369 tt.Check(markers.IsAny(err3, err3)) 370 tt.Check(markers.IsAny(err3, err2, err1)) 371 tt.Check(markers.IsAny(err3, err2, nilErr, err1)) 372 tt.Check(markers.IsAny(nilErr, err2, nilErr, err1)) 373 tt.Check(!markers.IsAny(nilErr, err2, err1)) 374 } 375 376 // This test demonstrates that two errors that are structurally 377 // equivalent can be made to become non-equivalent through markers.Is() 378 // by using markers. 379 func TestMarkerDrivenDifference(t *testing.T) { 380 tt := testutils.T{T: t} 381 382 err1 := errors.New("hello") 383 err2 := errors.New("hello") 384 385 tt.Check(markers.Is(err1, err2)) 386 387 m1 := errors.New("m1") 388 m2 := errors.New("m2") 389 390 err1w := markers.Mark(err1, m1) 391 err2w := markers.Mark(err2, m2) 392 393 tt.Check(markers.Is(err1w, m1)) 394 tt.Check(markers.Is(err2w, m2)) 395 396 tt.Check(!markers.Is(err1w, err2w)) 397 } 398 399 // This test demonstrates that error differences introduced 400 // via Mark() are preserved across the network. 401 func TestRemoteMarkerEquivalence(t *testing.T) { 402 tt := testutils.T{T: t} 403 404 mark := errors.New("mark") 405 406 err1 := errors.New("hello") 407 err1w := markers.Mark(err1, mark) 408 409 newErr1w := network(err1w) 410 411 tt.Check(markers.Is(err1w, newErr1w)) 412 413 err2 := errors.New("world") 414 err2w := markers.Mark(err2, mark) 415 416 tt.Check(markers.Is(newErr1w, err2w)) 417 } 418 419 type testError struct { 420 msg string 421 } 422 423 func (e *testError) Error() string { 424 return e.msg 425 } 426 427 func TestHasType(t *testing.T) { 428 tt := testutils.T{T: t} 429 base := &testError{msg: "hmm"} 430 wrapped := pkgErr.Wrap(base, "boom") 431 432 tt.Check(!markers.HasType(base, nil)) 433 tt.Check(!markers.HasType(wrapped, nil)) 434 435 tt.Check(markers.HasType(base, (*testError)(nil))) 436 tt.Check(markers.HasType(wrapped, (*testError)(nil))) 437 438 // nil errors don't contain any types, not even nil. 439 tt.Check(!markers.HasType(nil, nil)) 440 } 441 442 type testErrorInterface interface { 443 foo() 444 } 445 446 func (e *testError) foo() {} 447 448 func TestIsInterface(t *testing.T) { 449 tt := testutils.T{T: t} 450 base := &testError{msg: "hmm"} 451 wrapped := pkgErr.Wrap(base, "boom") 452 453 tt.Check(markers.HasInterface(base, (*testErrorInterface)(nil))) 454 tt.Check(markers.HasInterface(wrapped, (*testErrorInterface)(nil))) 455 456 tt.Check(!markers.HasInterface(base, (*net.Error)(nil))) 457 tt.Check(!markers.HasInterface(wrapped, (*net.Error)(nil))) 458 459 // nil errors don't contain any interfaces, not even nil. 460 tt.Check(!markers.HasInterface(nil, (*net.Error)(nil))) 461 } 462 463 // This test is used in the RFC. 464 func TestLocalLocalEquivalence(t *testing.T) { 465 tt := testutils.T{T: t} 466 467 err1 := errors.New("hello") 468 err2 := errors.New("hello") 469 err3 := errors.New("world") 470 471 // Different errors are different via markers.Is(). 472 tt.Check(!markers.Is(err1, err3)) 473 474 // Errors are equivalent to themselves. 475 tt.Check(markers.Is(err1, err1)) 476 tt.Check(markers.Is(err2, err2)) 477 tt.Check(markers.Is(err3, err3)) 478 479 m := errors.New("mark") 480 err1w := markers.Mark(err1, m) 481 err3w := markers.Mark(err3, m) 482 483 // Shared marks introduce explicit equivalence. 484 tt.Check(markers.Is(err1w, m)) 485 tt.Check(markers.Is(err3w, m)) 486 tt.Check(markers.Is(err3w, err1w)) 487 488 m2 := errors.New("m2") 489 err2w := markers.Mark(err2, m2) 490 491 // Different marks introduce explicit non-equivalence, 492 // even when the underlying errors are equivalent. 493 tt.Check(!markers.Is(err2w, err1w)) 494 } 495 496 // This test is used in the RFC. 497 func TestLocalRemoteEquivalence(t *testing.T) { 498 tt := testutils.T{T: t} 499 500 err1 := errors.New("hello") 501 err2 := errors.New("hello") 502 err3 := errors.New("world") 503 504 err1dec := network(err1) 505 err2dec := network(err2) 506 err3dec := network(err3) 507 508 // Equivalence is preserved across the network. 509 tt.Check(markers.Is(err1, err1dec) && markers.Is(err1dec, err1)) 510 tt.Check(markers.Is(err2, err2dec) && markers.Is(err2dec, err2)) 511 tt.Check(markers.Is(err3, err3dec) && markers.Is(err3dec, err3)) 512 513 // Non-equivalence is preserved across the network. 514 tt.Check(!markers.Is(err1dec, err3)) 515 tt.Check(!markers.Is(err2dec, err3)) 516 517 // "m" makes err1w and err3w equivalent. 518 m := errors.New("mark") 519 err1w := markers.Mark(err1, m) 520 err3w := markers.Mark(err3, m) 521 // "m2" makes err1w and err2w non-equivalent even though err2 and err1 are. 522 m2 := errors.New("m2") 523 err2w := markers.Mark(err2, m2) 524 525 err1decw := network(err1w) 526 err2decw := network(err2w) 527 err3decw := network(err3w) 528 529 // Equivalence is preserved across the network. 530 tt.Check(markers.Is(err1decw, err1w) && markers.Is(err1w, err1decw)) 531 tt.Check(markers.Is(err2decw, err2w) && markers.Is(err2w, err2decw)) 532 tt.Check(markers.Is(err3decw, err3w) && markers.Is(err3w, err3decw)) 533 tt.Check(markers.Is(err1decw, err3w) && markers.Is(err3decw, err1w)) 534 535 // Non-equivalence is preserved across the network. 536 tt.Check(!markers.Is(err1w, err2decw) && !markers.Is(err2w, err1decw)) 537 } 538 539 // This test is used in the RFC. 540 func TestRemoteRemoteEquivalence(t *testing.T) { 541 tt := testutils.T{T: t} 542 543 err1 := errors.New("hello") 544 err2 := errors.New("hello") 545 err3 := errors.New("world") 546 547 err1dec := network(err1) 548 err2dec := network(err2) 549 err3dec := network(err3) 550 err1decOther := network(err1) 551 err2decOther := network(err2) 552 err3decOther := network(err3) 553 554 // Equivalence is preserved across the network. 555 tt.Check(markers.Is(err1dec, err1decOther) && 556 markers.Is(err2dec, err2decOther) && 557 markers.Is(err3dec, err3decOther)) 558 tt.Check(markers.Is(err1dec, err2decOther)) 559 560 // Non-equivalence is preserved across the network. 561 tt.Check(!markers.Is(err1dec, err3decOther) && !markers.Is(err2dec, err3dec)) 562 563 // "m" makes err1w and err3w equivalent. 564 m := errors.New("mark") 565 err1w := markers.Mark(err1, m) 566 err3w := markers.Mark(err3, m) 567 // "m2" makes err1w and err2w non-equivalent even though err2 and err1 are. 568 m2 := errors.New("m2") 569 err2w := markers.Mark(err2, m2) 570 571 err1decw := network(err1w) 572 err2decw := network(err2w) 573 err3decw := network(err3w) 574 err1decwOther := network(err1w) 575 err2decwOther := network(err2w) 576 err3decwOther := network(err3w) 577 578 // Equivalence is preserved across the network. 579 tt.Check(markers.Is(err1decw, err1decwOther) && markers.Is(err1decwOther, err1decw)) 580 tt.Check(markers.Is(err2decw, err2decwOther) && markers.Is(err2decwOther, err2decw)) 581 tt.Check(markers.Is(err3decw, err3decwOther) && markers.Is(err3decwOther, err3decw)) 582 583 tt.Check(markers.Is(err1decw, err3decwOther) && markers.Is(err3decw, err1decwOther)) 584 585 // Non-equivalence is preserved across the network. 586 tt.Check(!markers.Is(err1decw, err2decwOther) && !markers.Is(err2decw, err1decwOther)) 587 } 588 589 // This test demonstrates why it is important to use all the types of the 590 // causes and not just the type of the first layer of wrapper. 591 func TestMaskedErrorEquivalence(t *testing.T) { 592 tt := testutils.T{T: t} 593 594 // The reference error in some library is constructed using errors.Wrap around some reference 595 // error with a simple message and a given type. 596 refErr := pkgErr.Wrap(&myErrType1{msg: "world"}, "hello") 597 598 // Somewhere else another error gets wrapped, the error has actually 599 // a different type, but it happens to have the same message. 600 someErr := pkgErr.WithStack(&myErrType2{msg: "hello: world"}) 601 602 // because `Wrap` wraps with the same Go type as `WithStack`, we would have a problem 603 // if we only compared the outer type. 604 605 // However, the library does the right thing. 606 tt.Check(!markers.Is(someErr, refErr)) 607 608 // Even so across the network. 609 otherErr := network(someErr) 610 tt.Check(!markers.Is(otherErr, refErr)) 611 } 612 613 type myErrType1 struct{ msg string } 614 615 func (e *myErrType1) Error() string { return e.msg } 616 617 type myErrType2 struct{ msg string } 618 619 func (e *myErrType2) Error() string { return e.msg } 620 621 func TestFormat(t *testing.T) { 622 tt := testutils.T{t} 623 624 refErr := goErr.New("foo") 625 const woo = `woo` 626 const waawoo = `waa: woo` 627 testCases := []struct { 628 name string 629 err error 630 expFmtSimple string 631 expFmtVerbose string 632 }{ 633 {"marked", 634 markers.Mark(goErr.New("woo"), refErr), 635 woo, ` 636 woo 637 (1) forced error mark 638 | "foo" 639 | errors/*errors.errorString:: 640 Wraps: (2) woo 641 Error types: (1) *markers.withMark (2) *errors.errorString`}, 642 643 {"marked + wrapper", 644 markers.Mark(&werrFmt{goErr.New("woo"), "waa"}, refErr), 645 waawoo, ` 646 waa: woo 647 (1) forced error mark 648 | "foo" 649 | errors/*errors.errorString:: 650 Wraps: (2) waa 651 | -- this is waa's 652 | multi-line payload 653 Wraps: (3) woo 654 Error types: (1) *markers.withMark (2) *markers_test.werrFmt (3) *errors.errorString`}, 655 656 {"wrapper + marked", 657 &werrFmt{markers.Mark(goErr.New("woo"), refErr), "waa"}, 658 waawoo, ` 659 waa: woo 660 (1) waa 661 | -- this is waa's 662 | multi-line payload 663 Wraps: (2) forced error mark 664 | "foo" 665 | errors/*errors.errorString:: 666 Wraps: (3) woo 667 Error types: (1) *markers_test.werrFmt (2) *markers.withMark (3) *errors.errorString`}, 668 } 669 670 for _, test := range testCases { 671 tt.Run(test.name, func(tt testutils.T) { 672 err := test.err 673 674 // %s is simple formatting 675 tt.CheckStringEqual(fmt.Sprintf("%s", err), test.expFmtSimple) 676 677 // %v is simple formatting too, for compatibility with the past. 678 tt.CheckStringEqual(fmt.Sprintf("%v", err), test.expFmtSimple) 679 680 // %q is always like %s but quotes the result. 681 ref := fmt.Sprintf("%q", test.expFmtSimple) 682 tt.CheckStringEqual(fmt.Sprintf("%q", err), ref) 683 684 // %+v is the verbose mode. 685 refV := strings.TrimPrefix(test.expFmtVerbose, "\n") 686 spv := fmt.Sprintf("%+v", err) 687 tt.CheckStringEqual(spv, refV) 688 }) 689 } 690 } 691 692 type werrFmt struct { 693 cause error 694 msg string 695 } 696 697 var _ errbase.Formatter = (*werrFmt)(nil) 698 699 func (e *werrFmt) Error() string { return fmt.Sprintf("%s: %v", e.msg, e.cause) } 700 func (e *werrFmt) Unwrap() error { return e.cause } 701 func (e *werrFmt) Format(s fmt.State, verb rune) { errbase.FormatError(e, s, verb) } 702 func (e *werrFmt) FormatError(p errbase.Printer) error { 703 p.Print(e.msg) 704 if p.Detail() { 705 p.Printf("-- this is %s's\nmulti-line payload", e.msg) 706 } 707 return e.cause 708 } 709 710 func TestInvalidError(t *testing.T) { 711 tt := testutils.T{T: t} 712 713 err := &invalidError{} 714 errRef := errors.New("hello") 715 tt.Check(!markers.Is(err, errRef)) 716 tt.Check(markers.Is(err, err)) 717 tt.Check(markers.HasType(err, (*invalidError)(nil))) 718 } 719 720 type invalidError struct { 721 emptyRef error 722 } 723 724 func (e *invalidError) Error() string { return e.emptyRef.Error() } 725 func (e *invalidError) Cause() error { return e.emptyRef } 726 727 func TestDelegateToIsMethod(t *testing.T) { 728 tt := testutils.T{T: t} 729 730 efoo := &errWithIs{msg: "foo", seecret: "foo"} 731 efoo2 := &errWithIs{msg: "foo", seecret: "bar"} 732 ebar := &errWithIs{msg: "bar", seecret: "foo"} 733 734 tt.Check(markers.Is(efoo, efoo2)) // equality based on message 735 tt.Check(markers.Is(efoo, ebar)) // equality based on method 736 tt.Check(!markers.Is(efoo2, ebar)) // neither msg nor method 737 738 tt.Check(markers.IsAny(efoo, efoo2, ebar)) 739 tt.Check(markers.IsAny(efoo2, ebar, efoo)) 740 tt.Check(!markers.IsAny(efoo2, ebar, errors.New("other"))) 741 } 742 743 type errWithIs struct { 744 msg string 745 seecret string 746 } 747 748 func (e *errWithIs) Error() string { return e.msg } 749 750 func (e *errWithIs) Is(o error) bool { 751 if ex, ok := o.(*errWithIs); ok { 752 return e.seecret == ex.seecret 753 } 754 return false 755 } 756 757 func TestCompareUncomparable(t *testing.T) { 758 tt := testutils.T{T: t} 759 760 err1 := errors.New("hello") 761 var nilErr error 762 f := []string{"woo"} 763 tt.Check(markers.Is(errorUncomparable{f}, errorUncomparable{})) 764 tt.Check(markers.IsAny(errorUncomparable{f}, errorUncomparable{})) 765 tt.Check(markers.IsAny(errorUncomparable{f}, nilErr, errorUncomparable{})) 766 tt.Check(!markers.Is(errorUncomparable{f}, &errorUncomparable{})) 767 tt.Check(!markers.IsAny(errorUncomparable{f}, &errorUncomparable{})) 768 tt.Check(!markers.IsAny(errorUncomparable{f}, nilErr, &errorUncomparable{})) 769 tt.Check(markers.Is(&errorUncomparable{f}, errorUncomparable{})) 770 tt.Check(markers.IsAny(&errorUncomparable{f}, errorUncomparable{})) 771 tt.Check(markers.IsAny(&errorUncomparable{f}, nilErr, errorUncomparable{})) 772 tt.Check(!markers.Is(&errorUncomparable{f}, &errorUncomparable{})) 773 tt.Check(!markers.IsAny(&errorUncomparable{f}, &errorUncomparable{})) 774 tt.Check(!markers.IsAny(&errorUncomparable{f}, nilErr, &errorUncomparable{})) 775 tt.Check(!markers.Is(errorUncomparable{f}, err1)) 776 tt.Check(!markers.IsAny(errorUncomparable{f}, err1)) 777 tt.Check(!markers.IsAny(errorUncomparable{f}, nilErr, err1)) 778 tt.Check(!markers.Is(&errorUncomparable{f}, err1)) 779 tt.Check(!markers.IsAny(&errorUncomparable{f}, err1)) 780 tt.Check(!markers.IsAny(&errorUncomparable{f}, nilErr, err1)) 781 } 782 783 type errorUncomparable struct { 784 f []string 785 } 786 787 func (e errorUncomparable) Error() string { 788 return fmt.Sprintf("uncomparable error %d", len(e.f)) 789 } 790 791 func (errorUncomparable) Is(target error) bool { 792 _, ok := target.(errorUncomparable) 793 return ok 794 }