github.com/mckael/restic@v0.8.3/internal/backend/backend_error.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"io/ioutil"
     7  	"math/rand"
     8  	"sync"
     9  
    10  	"github.com/restic/restic/internal/errors"
    11  	"github.com/restic/restic/internal/restic"
    12  )
    13  
    14  // ErrorBackend is used to induce errors into various function calls and test
    15  // the retry functions.
    16  type ErrorBackend struct {
    17  	FailSave     float32
    18  	FailSaveRead float32
    19  	FailLoad     float32
    20  	FailStat     float32
    21  	restic.Backend
    22  
    23  	r *rand.Rand
    24  	m sync.Mutex
    25  }
    26  
    27  // statically ensure that ErrorBackend implements restic.Backend.
    28  var _ restic.Backend = &ErrorBackend{}
    29  
    30  // NewErrorBackend wraps be with a backend that returns errors according to
    31  // given probabilities.
    32  func NewErrorBackend(be restic.Backend, seed int64) *ErrorBackend {
    33  	return &ErrorBackend{
    34  		Backend: be,
    35  		r:       rand.New(rand.NewSource(seed)),
    36  	}
    37  }
    38  
    39  func (be *ErrorBackend) fail(p float32) bool {
    40  	be.m.Lock()
    41  	v := be.r.Float32()
    42  	be.m.Unlock()
    43  
    44  	return v < p
    45  }
    46  
    47  // Save stores the data in the backend under the given handle.
    48  func (be *ErrorBackend) Save(ctx context.Context, h restic.Handle, rd io.Reader) error {
    49  	if be.fail(be.FailSave) {
    50  		return errors.Errorf("Save(%v) random error induced", h)
    51  	}
    52  
    53  	if be.fail(be.FailSaveRead) {
    54  		_, err := io.CopyN(ioutil.Discard, rd, be.r.Int63n(1000))
    55  		if err != nil {
    56  			return err
    57  		}
    58  
    59  		return errors.Errorf("Save(%v) random error with partial read induced", h)
    60  	}
    61  
    62  	return be.Backend.Save(ctx, h, rd)
    63  }
    64  
    65  // Load returns a reader that yields the contents of the file at h at the
    66  // given offset. If length is larger than zero, only a portion of the file
    67  // is returned. rd must be closed after use. If an error is returned, the
    68  // ReadCloser must be nil.
    69  func (be *ErrorBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) error {
    70  	if be.fail(be.FailLoad) {
    71  		return errors.Errorf("Load(%v, %v, %v) random error induced", h, length, offset)
    72  	}
    73  
    74  	return be.Backend.Load(ctx, h, length, offset, consumer)
    75  }
    76  
    77  // Stat returns information about the File identified by h.
    78  func (be *ErrorBackend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) {
    79  	if be.fail(be.FailLoad) {
    80  		return restic.FileInfo{}, errors.Errorf("Stat(%v) random error induced", h)
    81  	}
    82  
    83  	return be.Stat(ctx, h)
    84  }