Files
kopia/internal/blobtesting/concurrent.go
Jarek Kowalski c8fcae93aa logging: refactored logging
This is mostly mechanical and changes how loggers are instantiated.

Logger is now associated with a context, passed around all methods,
(most methods had ctx, but had to add it in a few missing places).

By default Kopia does not produce any logs, but it can be overridden,
either locally for a nested context, by calling

ctx = logging.WithLogger(ctx, newLoggerFunc)

To override logs globally, call logging.SetDefaultLogger(newLoggerFunc)

This refactoring allowed removing dependency from Kopia repo
and go-logging library (the CLI still uses it, though).

It is now also possible to have all test methods emit logs using
t.Logf() so that they show up in failure reports, which should make
debugging of test failures suck less.
2020-02-25 17:24:44 -08:00

157 lines
3.7 KiB
Go

package blobtesting
import (
cryptorand "crypto/rand"
"encoding/hex"
"fmt"
"math/rand"
"strings"
"testing"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/kopia/kopia/internal/testlogging"
"github.com/kopia/kopia/repo/blob"
)
// ConcurrentAccessOptions encapsulates parameters for VerifyConcurrentAccess
type ConcurrentAccessOptions struct {
NumBlobs int // number of shared blos in the pool
Getters int
Putters int
Deleters int
Listers int
Iterations int
RangeGetPercentage int // 0..100 - probability of issuing range get
NonExistentListPrefixPercentage int // probability of issuing non-matching list prefix
}
// VerifyConcurrentAccess tests data races on a repository to ensure only clean errors are returned.
// nolint:gocognit,gocyclo,funlen
func VerifyConcurrentAccess(t *testing.T, st blob.Storage, options ConcurrentAccessOptions) {
t.Helper()
// generate random blob IDs for the pool
var blobs []blob.ID
for i := 0; i < options.NumBlobs; i++ {
blobIDBytes := make([]byte, 32)
cryptorand.Read(blobIDBytes) // nolint:errcheck
blobs = append(blobs, blob.ID(hex.EncodeToString(blobIDBytes)))
}
randomBlobID := func() blob.ID {
return blobs[rand.Intn(len(blobs))]
}
eg, ctx := errgroup.WithContext(testlogging.Context(t))
// start readers that will be reading random blob out of the pool
for i := 0; i < options.Getters; i++ {
eg.Go(func() error {
for i := 0; i < options.Iterations; i++ {
blobID := randomBlobID()
offset := int64(0)
length := int64(-1)
if rand.Intn(100) < options.RangeGetPercentage { //nolint:gomnd
offset = 10
length = 3
}
data, err := st.GetBlob(ctx, blobID, offset, length)
switch err {
case nil:
if got, want := string(data), string(blobID); !strings.HasPrefix(got, want) {
return errors.Wrapf(err, "GetBlob returned invalid data for %v: %v, want prefix of %v", blobID, got, want)
}
case blob.ErrBlobNotFound:
// clean error
default:
return errors.Wrapf(err, "GetBlob %v returned unexpected error", blobID)
}
}
return nil
})
}
// start putters that will be writing random blob out of the pool
for i := 0; i < options.Putters; i++ {
eg.Go(func() error {
for i := 0; i < options.Iterations; i++ {
blobID := randomBlobID()
data := fmt.Sprintf("%v-%v", blobID, rand.Int63())
err := st.PutBlob(ctx, blobID, []byte(data))
switch err {
case nil:
// clean success
default:
return errors.Wrapf(err, "PutBlob %v returned unexpected error", blobID)
}
}
return nil
})
}
// start deleters that will be deleting random blob out of the pool
for i := 0; i < options.Deleters; i++ {
eg.Go(func() error {
for i := 0; i < options.Iterations; i++ {
blobID := randomBlobID()
err := st.DeleteBlob(ctx, blobID)
switch err {
case nil:
// clean success
case blob.ErrBlobNotFound:
// clean error
default:
return errors.Wrapf(err, "DeleteBlob %v returned unexpected error", blobID)
}
}
return nil
})
}
// start listers that will be listing blobs by random prefixes of existing objects.
for i := 0; i < options.Listers; i++ {
eg.Go(func() error {
for i := 0; i < options.Iterations; i++ {
blobID := randomBlobID()
prefix := blobID[0:rand.Intn(len(blobID))]
if rand.Intn(100) < options.NonExistentListPrefixPercentage { //nolint:gomnd
prefix = "zzz"
}
err := st.ListBlobs(ctx, prefix, func(blob.Metadata) error {
return nil
})
switch err {
case nil:
// clean success
default:
return errors.Wrapf(err, "ListBlobs(%v) returned unexpected error", prefix)
}
}
return nil
})
}
if err := eg.Wait(); err != nil {
t.Errorf("unexpected error: %v", err)
}
}