storage/testsuite: pass ctx in to bulk setup methods
to make them cancelable. Also, * rename BulkDelete->BulkDeleteAll this leaves room for a new method `BulkDelete(items storage.Items)` that does a bulk deletion of a specified list of items, as opposed to deleting _everything_. such a method would be used in the `cleanupItems()` function found in utils.go, because when individual deletes are fairly slow, that step takes way too long during tests. * use BulkDelete method if available nothing currently provides `BulkDelete(items storage.Items) error`, but we made use of it with the Bigtable testing and code, and may make use of it again when adding new kv backends. * and eliminate the global context in test_iterate.go Change-Id: I171c7a3818beffbad969b131e98b9bbe3f324bf2
This commit is contained in:
parent
72d407559e
commit
94651921c3
@ -19,8 +19,6 @@ import (
|
|||||||
"storj.io/storj/storage/testsuite"
|
"storj.io/storj/storage/testsuite"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx = context.Background() // test context
|
|
||||||
|
|
||||||
func TestSuite(t *testing.T) {
|
func TestSuite(t *testing.T) {
|
||||||
tempdir, err := ioutil.TempDir("", "storj-bolt")
|
tempdir, err := ioutil.TempDir("", "storj-bolt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -93,7 +91,7 @@ type boltLongBenchmarkStore struct {
|
|||||||
dirPath string
|
dirPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *boltLongBenchmarkStore) BulkImport(iter storage.Iterator) (err error) {
|
func (store *boltLongBenchmarkStore) BulkImport(ctx context.Context, iter storage.Iterator) (err error) {
|
||||||
// turn off syncing during import
|
// turn off syncing during import
|
||||||
oldval := store.db.NoSync
|
oldval := store.db.NoSync
|
||||||
store.db.NoSync = true
|
store.db.NoSync = true
|
||||||
@ -109,7 +107,7 @@ func (store *boltLongBenchmarkStore) BulkImport(iter storage.Iterator) (err erro
|
|||||||
return store.db.Sync()
|
return store.db.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *boltLongBenchmarkStore) BulkDelete() error {
|
func (store *boltLongBenchmarkStore) BulkDeleteAll(ctx context.Context) error {
|
||||||
// do nothing here; everything will be cleaned up later after the test completes. it's not
|
// do nothing here; everything will be cleaned up later after the test completes. it's not
|
||||||
// worth it to wait for BoltDB to remove every key, one by one, and we can't just
|
// worth it to wait for BoltDB to remove every key, one by one, and we can't just
|
||||||
// os.RemoveAll() the whole test directory at this point because those files are still open
|
// os.RemoveAll() the whole test directory at this point because those files are still open
|
||||||
@ -117,6 +115,9 @@ func (store *boltLongBenchmarkStore) BulkDelete() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ testsuite.BulkImporter = &boltLongBenchmarkStore{}
|
||||||
|
var _ testsuite.BulkCleaner = &boltLongBenchmarkStore{}
|
||||||
|
|
||||||
func BenchmarkSuiteLong(b *testing.B) {
|
func BenchmarkSuiteLong(b *testing.B) {
|
||||||
tempdir, err := ioutil.TempDir("", "storj-bolt")
|
tempdir, err := ioutil.TempDir("", "storj-bolt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package postgreskv
|
package postgreskv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -56,14 +57,17 @@ type pgAltLongBenchmarkStore struct {
|
|||||||
*AlternateClient
|
*AlternateClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *pgAltLongBenchmarkStore) BulkImport(iter storage.Iterator) error {
|
func (store *pgAltLongBenchmarkStore) BulkImport(ctx context.Context, iter storage.Iterator) error {
|
||||||
return bulkImport(store.pgConn, iter)
|
return bulkImport(store.pgConn, iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *pgAltLongBenchmarkStore) BulkDelete() error {
|
func (store *pgAltLongBenchmarkStore) BulkDeleteAll(ctx context.Context) error {
|
||||||
return bulkDelete(store.pgConn)
|
return bulkDeleteAll(store.pgConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ testsuite.BulkImporter = &pgAltLongBenchmarkStore{}
|
||||||
|
var _ testsuite.BulkCleaner = &pgAltLongBenchmarkStore{}
|
||||||
|
|
||||||
func BenchmarkSuiteLongAlt(b *testing.B) {
|
func BenchmarkSuiteLongAlt(b *testing.B) {
|
||||||
store, cleanup := newTestAlternatePostgres(b)
|
store, cleanup := newTestAlternatePostgres(b)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
@ -126,7 +126,7 @@ func bulkImport(db *sql.DB, iter storage.Iterator) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bulkDelete(db *sql.DB) error {
|
func bulkDeleteAll(db *sql.DB) error {
|
||||||
_, err := db.Exec("TRUNCATE pathdata")
|
_, err := db.Exec("TRUNCATE pathdata")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errs.New("Failed to TRUNCATE pathdata table: %v", err)
|
return errs.New("Failed to TRUNCATE pathdata table: %v", err)
|
||||||
@ -142,8 +142,8 @@ func (store *pgLongBenchmarkStore) BulkImport(iter storage.Iterator) error {
|
|||||||
return bulkImport(store.pgConn, iter)
|
return bulkImport(store.pgConn, iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *pgLongBenchmarkStore) BulkDelete() error {
|
func (store *pgLongBenchmarkStore) BulkDeleteAll() error {
|
||||||
return bulkDelete(store.pgConn)
|
return bulkDeleteAll(store.pgConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkSuiteLong(b *testing.B) {
|
func BenchmarkSuiteLong(b *testing.B) {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +40,9 @@ func RunBenchmarks(b *testing.B, store storage.KeyValueStore) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer cleanupItems(store, items)
|
ctx := testcontext.New(b)
|
||||||
|
defer ctx.Cleanup()
|
||||||
|
defer cleanupItems(b, ctx, store, items)
|
||||||
|
|
||||||
b.Run("Put", func(b *testing.B) {
|
b.Run("Put", func(b *testing.B) {
|
||||||
b.SetBytes(int64(len(items)))
|
b.SetBytes(int64(len(items)))
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -166,29 +167,20 @@ func openTestData(tb testing.TB) *KVInputIterator {
|
|||||||
return inputIter
|
return inputIter
|
||||||
}
|
}
|
||||||
|
|
||||||
// BulkImporter identifies KV storage facilities that can do bulk importing of items more
|
|
||||||
// efficiently than inserting one-by-one.
|
|
||||||
type BulkImporter interface {
|
|
||||||
BulkImport(storage.Iterator) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// BulkCleaner identifies KV storage facilities that can delete all items efficiently.
|
|
||||||
type BulkCleaner interface {
|
|
||||||
BulkDelete() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkPathOperationsInLargeDb runs the "long benchmarks" suite for KeyValueStore instances.
|
// BenchmarkPathOperationsInLargeDb runs the "long benchmarks" suite for KeyValueStore instances.
|
||||||
func BenchmarkPathOperationsInLargeDb(b *testing.B, store storage.KeyValueStore) {
|
func BenchmarkPathOperationsInLargeDb(b *testing.B, store storage.KeyValueStore) {
|
||||||
if *longBenchmarksData == "" {
|
if *longBenchmarksData == "" {
|
||||||
b.Skip("Long benchmarks not enabled.")
|
b.Skip("Long benchmarks not enabled.")
|
||||||
}
|
}
|
||||||
|
|
||||||
initStore(b, store)
|
ctx := testcontext.New(b)
|
||||||
|
defer ctx.Cleanup()
|
||||||
|
initStore(b, ctx, store)
|
||||||
|
|
||||||
doTest := func(name string, testFunc func(*testing.B, storage.KeyValueStore)) {
|
doTest := func(name string, testFunc func(*testing.B, *testcontext.Context, storage.KeyValueStore)) {
|
||||||
b.Run(name, func(bb *testing.B) {
|
b.Run(name, func(bb *testing.B) {
|
||||||
for i := 0; i < bb.N; i++ {
|
for i := 0; i < bb.N; i++ {
|
||||||
testFunc(bb, store)
|
testFunc(bb, ctx, store)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -201,12 +193,12 @@ func BenchmarkPathOperationsInLargeDb(b *testing.B, store storage.KeyValueStore)
|
|||||||
doTest("TopRecursiveStartAt", topRecursiveStartAt)
|
doTest("TopRecursiveStartAt", topRecursiveStartAt)
|
||||||
doTest("TopNonRecursive", topNonRecursive)
|
doTest("TopNonRecursive", topNonRecursive)
|
||||||
|
|
||||||
cleanupStore(b, store)
|
cleanupStore(b, ctx, store)
|
||||||
}
|
}
|
||||||
|
|
||||||
func importBigPathset(tb testing.TB, store storage.KeyValueStore) {
|
func importBigPathset(tb testing.TB, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
// make sure this is an empty db, or else refuse to run
|
// make sure this is an empty db, or else refuse to run
|
||||||
if !isEmptyKVStore(tb, store) {
|
if !isEmptyKVStore(tb, ctx, store) {
|
||||||
tb.Fatal("Provided KeyValueStore is not empty. The long benchmarks are destructive. Not running!")
|
tb.Fatal("Provided KeyValueStore is not empty. The long benchmarks are destructive. Not running!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +212,7 @@ func importBigPathset(tb testing.TB, store storage.KeyValueStore) {
|
|||||||
importer, ok := store.(BulkImporter)
|
importer, ok := store.(BulkImporter)
|
||||||
if ok {
|
if ok {
|
||||||
tb.Log("Performing bulk import...")
|
tb.Log("Performing bulk import...")
|
||||||
err := importer.BulkImport(inputIter)
|
err := importer.BulkImport(ctx, inputIter)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStr := "Provided KeyValueStore failed to import data"
|
errStr := "Provided KeyValueStore failed to import data"
|
||||||
@ -249,7 +241,7 @@ func importBigPathset(tb testing.TB, store storage.KeyValueStore) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initStore(b *testing.B, store storage.KeyValueStore) {
|
func initStore(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
|
|
||||||
if !*noInitDb {
|
if !*noInitDb {
|
||||||
@ -260,17 +252,17 @@ func initStore(b *testing.B, store storage.KeyValueStore) {
|
|||||||
// so we'll at least log it.
|
// so we'll at least log it.
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
tStart := time.Now()
|
tStart := time.Now()
|
||||||
importBigPathset(b, store)
|
importBigPathset(b, ctx, store)
|
||||||
b.Logf("importing took %s", time.Since(tStart).String())
|
b.Logf("importing took %s", time.Since(tStart).String())
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupStore(b *testing.B, store storage.KeyValueStore) {
|
func cleanupStore(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
if !*noCleanDb {
|
if !*noCleanDb {
|
||||||
tStart := time.Now()
|
tStart := time.Now()
|
||||||
cleanupBigPathset(b, store)
|
cleanupBigPathset(b, ctx, store)
|
||||||
b.Logf("cleanup took %s", time.Since(tStart).String())
|
b.Logf("cleanup took %s", time.Since(tStart).String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,7 +275,7 @@ type verifyOpts struct {
|
|||||||
expectLastKey storage.Key
|
expectLastKey storage.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchAndVerifyIteration(b *testing.B, store storage.KeyValueStore, opts *verifyOpts) {
|
func benchAndVerifyIteration(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore, opts *verifyOpts) {
|
||||||
problems := 0
|
problems := 0
|
||||||
iteration := 0
|
iteration := 0
|
||||||
|
|
||||||
@ -311,7 +303,7 @@ func benchAndVerifyIteration(b *testing.B, store storage.KeyValueStore, opts *ve
|
|||||||
lookupSize := opts.batchSize
|
lookupSize := opts.batchSize
|
||||||
|
|
||||||
for iteration = 1; iteration <= opts.doIterations; iteration++ {
|
for iteration = 1; iteration <= opts.doIterations; iteration++ {
|
||||||
results, err := iterateItems(store, opts.iterateOpts, lookupSize)
|
results, err := iterateItems(ctx, store, opts.iterateOpts, lookupSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("Failed to call iterateItems(): %v", err)
|
fatalf("Failed to call iterateItems(): %v", err)
|
||||||
}
|
}
|
||||||
@ -377,7 +369,7 @@ func benchAndVerifyIteration(b *testing.B, store storage.KeyValueStore, opts *ve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deepRecursive(b *testing.B, store storage.KeyValueStore) {
|
func deepRecursive(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
opts := &verifyOpts{
|
opts := &verifyOpts{
|
||||||
iterateOpts: storage.IterateOptions{
|
iterateOpts: storage.IterateOptions{
|
||||||
Prefix: storage.Key(largestLevel2Directory),
|
Prefix: storage.Key(largestLevel2Directory),
|
||||||
@ -397,10 +389,10 @@ func deepRecursive(b *testing.B, store storage.KeyValueStore) {
|
|||||||
// where $1 = largestLevel2Directory, $2 = doIterations, and $3 = batchSize
|
// where $1 = largestLevel2Directory, $2 = doIterations, and $3 = batchSize
|
||||||
opts.expectLastKey = storage.Key("Peronosporales/hateless/tod/extrastate/firewood/renomination/cletch/herotheism/aluminiferous/nub")
|
opts.expectLastKey = storage.Key("Peronosporales/hateless/tod/extrastate/firewood/renomination/cletch/herotheism/aluminiferous/nub")
|
||||||
|
|
||||||
benchAndVerifyIteration(b, store, opts)
|
benchAndVerifyIteration(b, ctx, store, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deepNonRecursive(b *testing.B, store storage.KeyValueStore) {
|
func deepNonRecursive(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
opts := &verifyOpts{
|
opts := &verifyOpts{
|
||||||
iterateOpts: storage.IterateOptions{
|
iterateOpts: storage.IterateOptions{
|
||||||
Prefix: storage.Key(largestLevel2Directory),
|
Prefix: storage.Key(largestLevel2Directory),
|
||||||
@ -422,10 +414,10 @@ func deepNonRecursive(b *testing.B, store storage.KeyValueStore) {
|
|||||||
// where $1 is largestLevel2Directory
|
// where $1 is largestLevel2Directory
|
||||||
opts.expectLastKey = storage.Key("Peronosporales/hateless/xerophily/")
|
opts.expectLastKey = storage.Key("Peronosporales/hateless/xerophily/")
|
||||||
|
|
||||||
benchAndVerifyIteration(b, store, opts)
|
benchAndVerifyIteration(b, ctx, store, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shallowRecursive(b *testing.B, store storage.KeyValueStore) {
|
func shallowRecursive(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
opts := &verifyOpts{
|
opts := &verifyOpts{
|
||||||
iterateOpts: storage.IterateOptions{
|
iterateOpts: storage.IterateOptions{
|
||||||
Prefix: storage.Key(largestSingleDirectory),
|
Prefix: storage.Key(largestSingleDirectory),
|
||||||
@ -451,10 +443,10 @@ func shallowRecursive(b *testing.B, store storage.KeyValueStore) {
|
|||||||
opts.doIterations = 74
|
opts.doIterations = 74
|
||||||
opts.batchSize = 251
|
opts.batchSize = 251
|
||||||
|
|
||||||
benchAndVerifyIteration(b, store, opts)
|
benchAndVerifyIteration(b, ctx, store, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shallowNonRecursive(b *testing.B, store storage.KeyValueStore) {
|
func shallowNonRecursive(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
opts := &verifyOpts{
|
opts := &verifyOpts{
|
||||||
iterateOpts: storage.IterateOptions{
|
iterateOpts: storage.IterateOptions{
|
||||||
Prefix: storage.Key(largestSingleDirectory),
|
Prefix: storage.Key(largestSingleDirectory),
|
||||||
@ -476,10 +468,10 @@ func shallowNonRecursive(b *testing.B, store storage.KeyValueStore) {
|
|||||||
// where $1 = largestSingleDirectory
|
// where $1 = largestSingleDirectory
|
||||||
opts.expectLastKey = storage.Key("Peronosporales/hateless/tod/unricht/sniveling/Puyallup/élite")
|
opts.expectLastKey = storage.Key("Peronosporales/hateless/tod/unricht/sniveling/Puyallup/élite")
|
||||||
|
|
||||||
benchAndVerifyIteration(b, store, opts)
|
benchAndVerifyIteration(b, ctx, store, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func topRecursiveLimit(b *testing.B, store storage.KeyValueStore) {
|
func topRecursiveLimit(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
opts := &verifyOpts{
|
opts := &verifyOpts{
|
||||||
iterateOpts: storage.IterateOptions{
|
iterateOpts: storage.IterateOptions{
|
||||||
Recurse: true,
|
Recurse: true,
|
||||||
@ -498,10 +490,10 @@ func topRecursiveLimit(b *testing.B, store storage.KeyValueStore) {
|
|||||||
// where $1 = expectCount
|
// where $1 = expectCount
|
||||||
opts.expectLastKey = storage.Key("nonresuscitation/synchronically/bechern/hemangiomatosis")
|
opts.expectLastKey = storage.Key("nonresuscitation/synchronically/bechern/hemangiomatosis")
|
||||||
|
|
||||||
benchAndVerifyIteration(b, store, opts)
|
benchAndVerifyIteration(b, ctx, store, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func topRecursiveStartAt(b *testing.B, store storage.KeyValueStore) {
|
func topRecursiveStartAt(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
opts := &verifyOpts{
|
opts := &verifyOpts{
|
||||||
iterateOpts: storage.IterateOptions{
|
iterateOpts: storage.IterateOptions{
|
||||||
Recurse: true,
|
Recurse: true,
|
||||||
@ -523,10 +515,10 @@ func topRecursiveStartAt(b *testing.B, store storage.KeyValueStore) {
|
|||||||
// where $1 = iterateOpts.First and $2 = expectCount
|
// where $1 = iterateOpts.First and $2 = expectCount
|
||||||
opts.expectLastKey = storage.Key("raptured/heathbird/histrionism/vermifugous/barefaced/beechdrops/lamber/phlegmatic/blended/Gershon/scallop/burglarproof/incompensated/allanite/alehouse/embroilment/lienotoxin/monotonically/cumbersomeness")
|
opts.expectLastKey = storage.Key("raptured/heathbird/histrionism/vermifugous/barefaced/beechdrops/lamber/phlegmatic/blended/Gershon/scallop/burglarproof/incompensated/allanite/alehouse/embroilment/lienotoxin/monotonically/cumbersomeness")
|
||||||
|
|
||||||
benchAndVerifyIteration(b, store, opts)
|
benchAndVerifyIteration(b, ctx, store, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func topNonRecursive(b *testing.B, store storage.KeyValueStore) {
|
func topNonRecursive(b *testing.B, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
opts := &verifyOpts{
|
opts := &verifyOpts{
|
||||||
iterateOpts: storage.IterateOptions{
|
iterateOpts: storage.IterateOptions{
|
||||||
Recurse: false,
|
Recurse: false,
|
||||||
@ -545,10 +537,10 @@ func topNonRecursive(b *testing.B, store storage.KeyValueStore) {
|
|||||||
// ) x order by fp desc limit 1;
|
// ) x order by fp desc limit 1;
|
||||||
opts.expectLastKey = storage.Key("vejoces")
|
opts.expectLastKey = storage.Key("vejoces")
|
||||||
|
|
||||||
benchAndVerifyIteration(b, store, opts)
|
benchAndVerifyIteration(b, ctx, store, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupBigPathset(tb testing.TB, store storage.KeyValueStore) {
|
func cleanupBigPathset(tb testing.TB, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
if *noCleanDb {
|
if *noCleanDb {
|
||||||
tb.Skip("Instructed not to clean up this KeyValueStore after long benchmarks are complete.")
|
tb.Skip("Instructed not to clean up this KeyValueStore after long benchmarks are complete.")
|
||||||
}
|
}
|
||||||
@ -556,7 +548,7 @@ func cleanupBigPathset(tb testing.TB, store storage.KeyValueStore) {
|
|||||||
cleaner, ok := store.(BulkCleaner)
|
cleaner, ok := store.(BulkCleaner)
|
||||||
if ok {
|
if ok {
|
||||||
tb.Log("Performing bulk cleanup...")
|
tb.Log("Performing bulk cleanup...")
|
||||||
err := cleaner.BulkDelete()
|
err := cleaner.BulkDeleteAll(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tb.Fatalf("Provided KeyValueStore failed to perform bulk delete: %v", err)
|
tb.Fatalf("Provided KeyValueStore failed to perform bulk delete: %v", err)
|
||||||
|
@ -14,26 +14,29 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunTests runs common storage.KeyValueStore tests
|
// RunTests runs common storage.KeyValueStore tests
|
||||||
func RunTests(t *testing.T, store storage.KeyValueStore) {
|
func RunTests(t *testing.T, store storage.KeyValueStore) {
|
||||||
// store = storelogger.NewTest(t, store)
|
// store = storelogger.NewTest(t, store)
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
defer ctx.Cleanup()
|
||||||
|
|
||||||
t.Run("CRUD", func(t *testing.T) { testCRUD(t, store) })
|
t.Run("CRUD", func(t *testing.T) { testCRUD(t, ctx, store) })
|
||||||
t.Run("Constraints", func(t *testing.T) { testConstraints(t, store) })
|
t.Run("Constraints", func(t *testing.T) { testConstraints(t, ctx, store) })
|
||||||
t.Run("Iterate", func(t *testing.T) { testIterate(t, store) })
|
t.Run("Iterate", func(t *testing.T) { testIterate(t, ctx, store) })
|
||||||
t.Run("IterateAll", func(t *testing.T) { testIterateAll(t, store) })
|
t.Run("IterateAll", func(t *testing.T) { testIterateAll(t, ctx, store) })
|
||||||
t.Run("Prefix", func(t *testing.T) { testPrefix(t, store) })
|
t.Run("Prefix", func(t *testing.T) { testPrefix(t, ctx, store) })
|
||||||
|
|
||||||
t.Run("List", func(t *testing.T) { testList(t, store) })
|
t.Run("List", func(t *testing.T) { testList(t, ctx, store) })
|
||||||
t.Run("ListV2", func(t *testing.T) { testListV2(t, store) })
|
t.Run("ListV2", func(t *testing.T) { testListV2(t, ctx, store) })
|
||||||
|
|
||||||
t.Run("Parallel", func(t *testing.T) { testParallel(t, store) })
|
t.Run("Parallel", func(t *testing.T) { testParallel(t, ctx, store) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func testConstraints(t *testing.T, store storage.KeyValueStore) {
|
func testConstraints(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
var items storage.Items
|
var items storage.Items
|
||||||
for i := 0; i < storage.LookupLimit+5; i++ {
|
for i := 0; i < storage.LookupLimit+5; i++ {
|
||||||
items = append(items, storage.ListItem{
|
items = append(items, storage.ListItem{
|
||||||
@ -53,7 +56,7 @@ func testConstraints(t *testing.T, store storage.KeyValueStore) {
|
|||||||
if err := group.Wait(); err != nil {
|
if err := group.Wait(); err != nil {
|
||||||
t.Fatalf("Put failed: %v", err)
|
t.Fatalf("Put failed: %v", err)
|
||||||
}
|
}
|
||||||
defer cleanupItems(store, items)
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
t.Run("Put Empty", func(t *testing.T) {
|
t.Run("Put Empty", func(t *testing.T) {
|
||||||
var key storage.Key
|
var key storage.Key
|
||||||
|
@ -8,10 +8,11 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testCRUD(t *testing.T, store storage.KeyValueStore) {
|
func testCRUD(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
items := storage.Items{
|
items := storage.Items{
|
||||||
// newItem("0", "", false), //TODO: broken
|
// newItem("0", "", false), //TODO: broken
|
||||||
newItem("\x00", "\x00", false),
|
newItem("\x00", "\x00", false),
|
||||||
@ -25,7 +26,7 @@ func testCRUD(t *testing.T, store storage.KeyValueStore) {
|
|||||||
newItem("öö", "üü", false),
|
newItem("öö", "üü", false),
|
||||||
}
|
}
|
||||||
rand.Shuffle(len(items), items.Swap)
|
rand.Shuffle(len(items), items.Swap)
|
||||||
defer cleanupItems(store, items)
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
t.Run("Put", func(t *testing.T) {
|
t.Run("Put", func(t *testing.T) {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
|
@ -4,16 +4,14 @@
|
|||||||
package testsuite
|
package testsuite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx = context.Background() // test context
|
func testIterate(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
|
|
||||||
func testIterate(t *testing.T, store storage.KeyValueStore) {
|
|
||||||
items := storage.Items{
|
items := storage.Items{
|
||||||
newItem("a", "a", false),
|
newItem("a", "a", false),
|
||||||
newItem("b/1", "b/1", false),
|
newItem("b/1", "b/1", false),
|
||||||
@ -27,12 +25,13 @@ func testIterate(t *testing.T, store storage.KeyValueStore) {
|
|||||||
newItem("h", "h", false),
|
newItem("h", "h", false),
|
||||||
}
|
}
|
||||||
rand.Shuffle(len(items), items.Swap)
|
rand.Shuffle(len(items), items.Swap)
|
||||||
defer cleanupItems(store, items)
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
if err := storage.PutAll(ctx, store, items...); err != nil {
|
if err := storage.PutAll(ctx, store, items...); err != nil {
|
||||||
t.Fatalf("failed to setup: %v", err)
|
t.Fatalf("failed to setup: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testIterations(t, store, []iterationTest{
|
testIterations(t, ctx, store, []iterationTest{
|
||||||
{"no limits",
|
{"no limits",
|
||||||
storage.IterateOptions{}, storage.Items{
|
storage.IterateOptions{}, storage.Items{
|
||||||
newItem("a", "a", false),
|
newItem("a", "a", false),
|
||||||
|
@ -7,10 +7,11 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testIterateAll(t *testing.T, store storage.KeyValueStore) {
|
func testIterateAll(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
items := storage.Items{
|
items := storage.Items{
|
||||||
newItem("a", "a", false),
|
newItem("a", "a", false),
|
||||||
newItem("b/1", "b/1", false),
|
newItem("b/1", "b/1", false),
|
||||||
@ -24,12 +25,13 @@ func testIterateAll(t *testing.T, store storage.KeyValueStore) {
|
|||||||
newItem("h", "h", false),
|
newItem("h", "h", false),
|
||||||
}
|
}
|
||||||
rand.Shuffle(len(items), items.Swap)
|
rand.Shuffle(len(items), items.Swap)
|
||||||
defer cleanupItems(store, items)
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
if err := storage.PutAll(ctx, store, items...); err != nil {
|
if err := storage.PutAll(ctx, store, items...); err != nil {
|
||||||
t.Fatalf("failed to setup: %v", err)
|
t.Fatalf("failed to setup: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testIterations(t, store, []iterationTest{
|
testIterations(t, ctx, store, []iterationTest{
|
||||||
{"no limits",
|
{"no limits",
|
||||||
storage.IterateOptions{
|
storage.IterateOptions{
|
||||||
Recurse: true,
|
Recurse: true,
|
||||||
|
@ -10,10 +10,11 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testList(t *testing.T, store storage.KeyValueStore) {
|
func testList(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
items := storage.Items{
|
items := storage.Items{
|
||||||
newItem("path/0", "\x00\xFF\x00", false),
|
newItem("path/0", "\x00\xFF\x00", false),
|
||||||
newItem("path/1", "\x01\xFF\x01", false),
|
newItem("path/1", "\x01\xFF\x01", false),
|
||||||
@ -23,8 +24,8 @@ func testList(t *testing.T, store storage.KeyValueStore) {
|
|||||||
newItem("path/5", "\x05\xFF\x05", false),
|
newItem("path/5", "\x05\xFF\x05", false),
|
||||||
}
|
}
|
||||||
rand.Shuffle(len(items), items.Swap)
|
rand.Shuffle(len(items), items.Swap)
|
||||||
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
defer cleanupItems(store, items)
|
|
||||||
if err := storage.PutAll(ctx, store, items...); err != nil {
|
if err := storage.PutAll(ctx, store, items...); err != nil {
|
||||||
t.Fatalf("failed to setup: %v", err)
|
t.Fatalf("failed to setup: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,11 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testListV2(t *testing.T, store storage.KeyValueStore) {
|
func testListV2(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
items := storage.Items{
|
items := storage.Items{
|
||||||
newItem("music/a-song1.mp3", "1", false),
|
newItem("music/a-song1.mp3", "1", false),
|
||||||
newItem("music/a-song2.mp3", "2", false),
|
newItem("music/a-song2.mp3", "2", false),
|
||||||
@ -25,7 +26,8 @@ func testListV2(t *testing.T, store storage.KeyValueStore) {
|
|||||||
newItem("videos/movie.mkv", "7", false),
|
newItem("videos/movie.mkv", "7", false),
|
||||||
}
|
}
|
||||||
rand.Shuffle(len(items), items.Swap)
|
rand.Shuffle(len(items), items.Swap)
|
||||||
defer cleanupItems(store, items)
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
if err := storage.PutAll(ctx, store, items...); err != nil {
|
if err := storage.PutAll(ctx, store, items...); err != nil {
|
||||||
t.Fatalf("failed to setup: %v", err)
|
t.Fatalf("failed to setup: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -9,17 +9,18 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testParallel(t *testing.T, store storage.KeyValueStore) {
|
func testParallel(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
items := storage.Items{
|
items := storage.Items{
|
||||||
newItem("a", "1", false),
|
newItem("a", "1", false),
|
||||||
newItem("b", "2", false),
|
newItem("b", "2", false),
|
||||||
newItem("c", "3", false),
|
newItem("c", "3", false),
|
||||||
}
|
}
|
||||||
rand.Shuffle(len(items), items.Swap)
|
rand.Shuffle(len(items), items.Swap)
|
||||||
defer cleanupItems(store, items)
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
for idx := range items {
|
for idx := range items {
|
||||||
i := idx
|
i := idx
|
||||||
|
@ -7,10 +7,11 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testPrefix(t *testing.T, store storage.KeyValueStore) {
|
func testPrefix(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore) {
|
||||||
items := storage.Items{
|
items := storage.Items{
|
||||||
newItem("x-a", "a", false),
|
newItem("x-a", "a", false),
|
||||||
newItem("x-b/1", "b/1", false),
|
newItem("x-b/1", "b/1", false),
|
||||||
@ -24,12 +25,13 @@ func testPrefix(t *testing.T, store storage.KeyValueStore) {
|
|||||||
newItem("y-h", "h", false),
|
newItem("y-h", "h", false),
|
||||||
}
|
}
|
||||||
rand.Shuffle(len(items), items.Swap)
|
rand.Shuffle(len(items), items.Swap)
|
||||||
defer cleanupItems(store, items)
|
defer cleanupItems(t, ctx, store, items)
|
||||||
|
|
||||||
if err := storage.PutAll(ctx, store, items...); err != nil {
|
if err := storage.PutAll(ctx, store, items...); err != nil {
|
||||||
t.Fatalf("failed to setup: %v", err)
|
t.Fatalf("failed to setup: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testIterations(t, store, []iterationTest{
|
testIterations(t, ctx, store, []iterationTest{
|
||||||
{"prefix x dash b slash",
|
{"prefix x dash b slash",
|
||||||
storage.IterateOptions{
|
storage.IterateOptions{
|
||||||
Prefix: storage.Key("x-"), First: storage.Key("x-b"),
|
Prefix: storage.Key("x-"), First: storage.Key("x-b"),
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
|
||||||
|
"storj.io/storj/private/testcontext"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,11 +22,35 @@ func newItem(key, value string, isPrefix bool) storage.ListItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupItems(store storage.KeyValueStore, items storage.Items) {
|
func cleanupItems(t testing.TB, ctx *testcontext.Context, store storage.KeyValueStore, items storage.Items) {
|
||||||
|
bulkDeleter, ok := store.(BulkDeleter)
|
||||||
|
if ok {
|
||||||
|
err := bulkDeleter.BulkDelete(ctx, items)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not do bulk cleanup of items: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
_ = store.Delete(ctx, item.Key)
|
_ = store.Delete(ctx, item.Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BulkImporter identifies KV storage facilities that can do bulk importing of items more
|
||||||
|
// efficiently than inserting one-by-one.
|
||||||
|
type BulkImporter interface {
|
||||||
|
BulkImport(context.Context, storage.Iterator) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BulkDeleter identifies KV storage facilities that can delete multiple items efficiently.
|
||||||
|
type BulkDeleter interface {
|
||||||
|
BulkDelete(context.Context, storage.Items) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BulkCleaner identifies KV storage facilities that can delete all items efficiently.
|
||||||
|
type BulkCleaner interface {
|
||||||
|
BulkDeleteAll(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
type iterationTest struct {
|
type iterationTest struct {
|
||||||
Name string
|
Name string
|
||||||
@ -33,10 +58,10 @@ type iterationTest struct {
|
|||||||
Expected storage.Items
|
Expected storage.Items
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIterations(t *testing.T, store storage.KeyValueStore, tests []iterationTest) {
|
func testIterations(t *testing.T, ctx *testcontext.Context, store storage.KeyValueStore, tests []iterationTest) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
items, err := iterateItems(store, test.Options, -1)
|
items, err := iterateItems(ctx, store, test.Options, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s: %v", test.Name, err)
|
t.Errorf("%s: %v", test.Name, err)
|
||||||
continue
|
continue
|
||||||
@ -47,7 +72,7 @@ func testIterations(t *testing.T, store storage.KeyValueStore, tests []iteration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEmptyKVStore(tb testing.TB, store storage.KeyValueStore) bool {
|
func isEmptyKVStore(tb testing.TB, ctx *testcontext.Context, store storage.KeyValueStore) bool {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
keys, err := store.List(ctx, storage.Key(""), 1)
|
keys, err := store.List(ctx, storage.Key(""), 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -69,7 +94,7 @@ func (collect *collector) include(ctx context.Context, it storage.Iterator) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func iterateItems(store storage.KeyValueStore, opts storage.IterateOptions, limit int) (storage.Items, error) {
|
func iterateItems(ctx *testcontext.Context, store storage.KeyValueStore, opts storage.IterateOptions, limit int) (storage.Items, error) {
|
||||||
collect := &collector{Limit: limit}
|
collect := &collector{Limit: limit}
|
||||||
err := store.Iterate(ctx, opts, collect.include)
|
err := store.Iterate(ctx, opts, collect.include)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user