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  }