github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/fserrors/error_test.go (about)

     1  package fserrors
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/url"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  // withMessage wraps an error with a message
    16  //
    17  // This is for backwards compatibility with the now removed github.com/pkg/errors
    18  type withMessage struct {
    19  	cause error
    20  	msg   string
    21  }
    22  
    23  func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
    24  func (w *withMessage) Cause() error  { return w.cause }
    25  
    26  // wrap returns an error annotating err with a stack trace
    27  // at the point Wrap is called, and the supplied message.
    28  // If err is nil, Wrap returns nil.
    29  func wrap(err error, message string) error {
    30  	if err == nil {
    31  		return nil
    32  	}
    33  	return &withMessage{
    34  		cause: err,
    35  		msg:   message,
    36  	}
    37  }
    38  
    39  var errUseOfClosedNetworkConnection = errors.New("use of closed network connection")
    40  
    41  type myError1 struct {
    42  	Err error
    43  }
    44  
    45  func (e myError1) Error() string { return e.Err.Error() }
    46  
    47  type myError2 struct {
    48  	Err error
    49  }
    50  
    51  func (e *myError2) Error() string {
    52  	if e == nil {
    53  		return "myError2(nil)"
    54  	}
    55  	if e.Err == nil {
    56  		return "myError2{Err: nil}"
    57  	}
    58  	return e.Err.Error()
    59  }
    60  
    61  type myError3 struct {
    62  	Err int
    63  }
    64  
    65  func (e *myError3) Error() string { return "hello" }
    66  
    67  type myError4 struct {
    68  	e error
    69  }
    70  
    71  func (e *myError4) Error() string { return e.e.Error() }
    72  
    73  type myError5 struct{}
    74  
    75  func (e *myError5) Error() string { return "" }
    76  
    77  func (e *myError5) Temporary() bool { return true }
    78  
    79  type errorCause struct {
    80  	e error
    81  }
    82  
    83  func (e *errorCause) Error() string { return fmt.Sprintf("%#v", e) }
    84  
    85  func (e *errorCause) Cause() error { return e.e }
    86  
    87  func TestCause(t *testing.T) {
    88  	e3 := &myError3{3}
    89  	e4 := &myError4{io.EOF}
    90  	e5 := &myError5{}
    91  	eNil1 := &myError2{nil}
    92  	eNil2 := &myError2{Err: (*myError2)(nil)}
    93  	errPotato := errors.New("potato")
    94  	nilCause1 := &errorCause{nil}
    95  	nilCause2 := &errorCause{(*myError2)(nil)}
    96  
    97  	for i, test := range []struct {
    98  		err           error
    99  		wantRetriable bool
   100  		wantErr       error
   101  	}{
   102  		{nil, false, nil},
   103  		{errPotato, false, errPotato},
   104  		{fmt.Errorf("potato: %w", errPotato), false, errPotato},
   105  		{fmt.Errorf("potato2: %w", wrap(errPotato, "potato")), false, errPotato},
   106  		{errUseOfClosedNetworkConnection, false, errUseOfClosedNetworkConnection},
   107  		{eNil1, false, eNil1},
   108  		{eNil2, false, eNil2.Err},
   109  		{myError1{io.EOF}, false, io.EOF},
   110  		{&myError2{io.EOF}, false, io.EOF},
   111  		{e3, false, e3},
   112  		{e4, false, e4},
   113  		{e5, true, e5},
   114  		{&errorCause{errPotato}, false, errPotato},
   115  		{nilCause1, false, nilCause1},
   116  		{nilCause2, false, nilCause2.e},
   117  	} {
   118  		gotRetriable, gotErr := Cause(test.err)
   119  		what := fmt.Sprintf("test #%d: %v", i, test.err)
   120  		assert.Equal(t, test.wantErr, gotErr, what)
   121  		assert.Equal(t, test.wantRetriable, gotRetriable, what)
   122  	}
   123  }
   124  
   125  func TestShouldRetry(t *testing.T) {
   126  	for i, test := range []struct {
   127  		err  error
   128  		want bool
   129  	}{
   130  		{nil, false},
   131  		{errors.New("potato"), false},
   132  		{fmt.Errorf("connection: %w", errUseOfClosedNetworkConnection), true},
   133  		{io.EOF, true},
   134  		{io.ErrUnexpectedEOF, true},
   135  		{&url.Error{Op: "post", URL: "/", Err: io.EOF}, true},
   136  		{&url.Error{Op: "post", URL: "/", Err: errUseOfClosedNetworkConnection}, true},
   137  		{&url.Error{Op: "post", URL: "/", Err: fmt.Errorf("net/http: HTTP/1.x transport connection broken: %v", fmt.Errorf("http: ContentLength=%d with Body length %d", 100663336, 99590598))}, true},
   138  	} {
   139  		got := ShouldRetry(test.err)
   140  		assert.Equal(t, test.want, got, fmt.Sprintf("test #%d: %v", i, test.err))
   141  	}
   142  }
   143  
   144  func TestRetryAfter(t *testing.T) {
   145  	e := NewErrorRetryAfter(time.Second)
   146  	after := e.RetryAfter()
   147  	dt := time.Until(after)
   148  	assert.True(t, dt >= 900*time.Millisecond && dt <= 1100*time.Millisecond)
   149  	assert.True(t, IsRetryAfterError(e))
   150  	assert.False(t, IsRetryAfterError(io.EOF))
   151  	assert.Equal(t, time.Time{}, RetryAfterErrorTime(io.EOF))
   152  	assert.False(t, IsRetryAfterError(nil))
   153  	assert.Contains(t, e.Error(), "try again after")
   154  
   155  	t0 := time.Now()
   156  	err := fmt.Errorf("potato: %w", ErrorRetryAfter(t0))
   157  	assert.Equal(t, t0, RetryAfterErrorTime(err))
   158  	assert.True(t, IsRetryAfterError(err))
   159  	assert.Contains(t, e.Error(), "try again after")
   160  }
   161  
   162  func TestContextError(t *testing.T) {
   163  	var err = io.EOF
   164  	ctx, cancel := context.WithCancel(context.Background())
   165  
   166  	assert.False(t, ContextError(ctx, &err))
   167  	assert.Equal(t, io.EOF, err)
   168  
   169  	cancel()
   170  
   171  	assert.True(t, ContextError(ctx, &err))
   172  	assert.Equal(t, io.EOF, err)
   173  
   174  	err = nil
   175  
   176  	assert.True(t, ContextError(ctx, &err))
   177  	assert.Equal(t, context.Canceled, err)
   178  }