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:
paul cannon 2019-09-24 13:06:22 -05:00 committed by Jennifer Li Johnson
parent 72d407559e
commit 94651921c3
14 changed files with 124 additions and 88 deletions

View File

@ -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 {

View File

@ -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()

View File

@ -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) {

View File

@ -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)))

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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),

View File

@ -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,

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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

View File

@ -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"),

View File

@ -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 {