diff --git a/bucket_test.go b/bucket_test.go index 06bd323ab..b60a1b912 100644 --- a/bucket_test.go +++ b/bucket_test.go @@ -464,178 +464,6 @@ func TestBucket_Delete_NonExisting(t *testing.T) { } } -func TestBucket_MoveBucket(t *testing.T) { - testCases := []struct { - name string - srcBucketPath []string - dstBucketPath []string - isInlined bool - expErr error - }{ - { - name: "happy path", - srcBucketPath: []string{"x", "y", "z"}, - dstBucketPath: []string{"a", "b"}, - isInlined: false, - expErr: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(*testing.T) { - db := btesting.MustCreateDB(t) - - // arrange - if err := db.Update(func(tx *bolt.Tx) error { - bk := createBucketIfNotExist(t, tx, tc.srcBucketPath...) - insertRandKeysValuesBucket(t, bk, rand.Intn(4096)) - createBucketIfNotExist(t, tx, tc.dstBucketPath...) - - return nil - }); err != nil { - t.Fatal(err) - } - db.MustCheck() - - // act - //var tmpFile string - if err := db.Update(func(tx *bolt.Tx) error { - srcBucket := retrieveParentBucket(t, tx, tc.srcBucketPath...) - dstBucket := retrieveChildBucket(t, tx, tc.dstBucketPath...) - bucketToMove := tc.srcBucketPath[len(tc.srcBucketPath)-1] - - //// dump bucketToMove to bbolt file for assertion - //bk := srcBucket.Bucket([]byte(bucketToMove)) - //tmpFile = dumpBucketToFile(t, bk) - - mErr := srcBucket.MoveBucket([]byte(bucketToMove), dstBucket) - require.Equal(t, tc.expErr, mErr) - - return nil - }); err != nil { - t.Fatal(err) - } - db.MustCheck() - - // assert - if err := db.View(func(tx *bolt.Tx) error { - srcBucket := retrieveParentBucket(t, tx, tc.srcBucketPath...) - bucketToMove := tc.srcBucketPath[len(tc.srcBucketPath)-1] - - if bk := srcBucket.Bucket([]byte(bucketToMove)); bk != nil { - t.Fatalf("expected childBucket %v to be moved from srcBucket %v", bucketToMove, srcBucket) - } - - dstBucket := retrieveChildBucket(t, tx, tc.dstBucketPath...) - if bk := dstBucket.Bucket([]byte(bucketToMove)); bk == nil { - t.Fatalf("expected childBucket %v to be child of dstBucket %v", bucketToMove, dstBucket) - } - - childBucket := dstBucket.Bucket([]byte(bucketToMove)) - if childBucket == nil { - t.Fatalf("expected subBucket %v to exist within dstBucket %v", bucketToMove, dstBucket) - } - - //bucketOnDisk, err := os.ReadFile(tmpFile) - //if err != nil { - // t.Fatalf("error reading tmp file %v", tmpFile) - //} - //require.Equal(t, bucketOnDisk, childBucket.Write()) - - return nil - }); err != nil { - t.Fatal(err) - } - db.MustCheck() - }) - } -} -func createBucketIfNotExist(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucket { - t.Helper() - - bk, err := tx.CreateBucketIfNotExists([]byte(paths[0])) - if err != nil { - t.Fatalf("error creating bucket %v: %v", paths[0], err) - } - - for _, key := range paths[1:] { - bk, err = bk.CreateBucketIfNotExists([]byte(key)) - if err != nil { - t.Fatalf("error creating bucket %v: %v", key, err) - } - } - - return bk -} - -func retrieveParentBucket(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucket { - paths = paths[:len(paths)-1] - return retrieveChildBucket(t, tx, paths...) -} - -func retrieveChildBucket(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucket { - t.Helper() - - var bk *bolt.Bucket = nil - for _, path := range paths { - if bk == nil { - bk = tx.Bucket([]byte(path)) - } else { - bk = bk.Bucket([]byte(path)) - } - if bk == nil { - t.Fatalf("error retrieving bucket %v within paths %v", path, strings.TrimSuffix(strings.Join(paths, "->"), "->")) - } - } - return bk -} - -func dumpBucketToFile(t testing.TB, bk *bolt.Bucket) string { - tmpFile := tempfile() - f, err := os.Create(tmpFile) - if err != nil { - t.Fatalf("error creating tmp file %v: %v", tmpFile, err) - } - defer f.Close() - - data := bk.Write() - _, err = f.Write(data) - if err != nil { - t.Fatalf("error writing bucket %v to file %v tmpFile", bk, tmpFile) - } - - return tmpFile -} - -func insertRandKeysValuesBucket(t testing.TB, bk *bolt.Bucket, n int) { - - var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - var min, max = 1, 1024 - - for i := 0; i < n; i++ { - // generate rand key/value length - keyLength := rand.Intn(max-min) + min - valLength := rand.Intn(max-min) + min - - keyData := make([]rune, keyLength) - valData := make([]rune, valLength) - - for j := range keyData { - keyData[j] = letters[rand.Intn(len(letters))] - } - - for j := range valData { - valData[j] = letters[rand.Intn(len(letters))] - } - - pErr := bk.Put([]byte(string(keyData)), []byte(string(valData))) - if pErr != nil { - t.Fatalf("error inserting key %v and value %v in bucket %v: %v", string(keyData), string(valData), bk.String(), pErr) - } - } - -} - // Ensure that accessing and updating nested buckets is ok across transactions. func TestBucket_Nested(t *testing.T) { db := btesting.MustCreateDB(t) diff --git a/movebucket_test.go b/movebucket_test.go new file mode 100644 index 000000000..664dfda92 --- /dev/null +++ b/movebucket_test.go @@ -0,0 +1,185 @@ +package bbolt_test + +import ( + "math/rand" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + bolt "go.etcd.io/bbolt" + "go.etcd.io/bbolt/internal/btesting" +) + +func TestBucket_MoveBucket(t *testing.T) { + testCases := []struct { + name string + srcBucketPath []string + dstBucketPath []string + isInlined bool + expErr error + }{ + { + name: "happy path", + srcBucketPath: []string{"x", "y", "z"}, + dstBucketPath: []string{"a", "b"}, + isInlined: false, + expErr: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(*testing.T) { + db := btesting.MustCreateDB(t) + + // arrange + if err := db.Update(func(tx *bolt.Tx) error { + bk := createBucketIfNotExist(t, tx, tc.srcBucketPath...) + insertRandKeysValuesBucket(t, bk, rand.Intn(4096)) + createBucketIfNotExist(t, tx, tc.dstBucketPath...) + + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // act + //var tmpFile string + if err := db.Update(func(tx *bolt.Tx) error { + srcBucket := retrieveParentBucket(t, tx, tc.srcBucketPath...) + dstBucket := retrieveChildBucket(t, tx, tc.dstBucketPath...) + bucketToMove := tc.srcBucketPath[len(tc.srcBucketPath)-1] + + //// dump bucketToMove to bbolt file for assertion + //bk := srcBucket.Bucket([]byte(bucketToMove)) + //tmpFile = dumpBucketToFile(t, bk) + + mErr := srcBucket.MoveBucket([]byte(bucketToMove), dstBucket) + require.Equal(t, tc.expErr, mErr) + + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // assert + if err := db.View(func(tx *bolt.Tx) error { + srcBucket := retrieveParentBucket(t, tx, tc.srcBucketPath...) + bucketToMove := tc.srcBucketPath[len(tc.srcBucketPath)-1] + + if bk := srcBucket.Bucket([]byte(bucketToMove)); bk != nil { + t.Fatalf("expected childBucket %v to be moved from srcBucket %v", bucketToMove, srcBucket) + } + + dstBucket := retrieveChildBucket(t, tx, tc.dstBucketPath...) + if bk := dstBucket.Bucket([]byte(bucketToMove)); bk == nil { + t.Fatalf("expected childBucket %v to be child of dstBucket %v", bucketToMove, dstBucket) + } + + childBucket := dstBucket.Bucket([]byte(bucketToMove)) + if childBucket == nil { + t.Fatalf("expected subBucket %v to exist within dstBucket %v", bucketToMove, dstBucket) + } + + //bucketOnDisk, err := os.ReadFile(tmpFile) + //if err != nil { + // t.Fatalf("error reading tmp file %v", tmpFile) + //} + //require.Equal(t, bucketOnDisk, childBucket.Write()) + + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + }) + } +} +func createBucketIfNotExist(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucket { + t.Helper() + + bk, err := tx.CreateBucketIfNotExists([]byte(paths[0])) + if err != nil { + t.Fatalf("error creating bucket %v: %v", paths[0], err) + } + + for _, key := range paths[1:] { + bk, err = bk.CreateBucketIfNotExists([]byte(key)) + if err != nil { + t.Fatalf("error creating bucket %v: %v", key, err) + } + } + + return bk +} + +func retrieveParentBucket(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucket { + paths = paths[:len(paths)-1] + return retrieveChildBucket(t, tx, paths...) +} + +func retrieveChildBucket(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucket { + t.Helper() + + var bk *bolt.Bucket = nil + for _, path := range paths { + if bk == nil { + bk = tx.Bucket([]byte(path)) + } else { + bk = bk.Bucket([]byte(path)) + } + if bk == nil { + t.Fatalf("error retrieving bucket %v within paths %v", path, strings.TrimSuffix(strings.Join(paths, "->"), "->")) + } + } + return bk +} + +func dumpBucketToFile(t testing.TB, bk *bolt.Bucket) string { + tmpFile := tempfile() + f, err := os.Create(tmpFile) + if err != nil { + t.Fatalf("error creating tmp file %v: %v", tmpFile, err) + } + defer f.Close() + + data := bk.Write() + _, err = f.Write(data) + if err != nil { + t.Fatalf("error writing bucket %v to file %v tmpFile", bk, tmpFile) + } + + return tmpFile +} + +func insertRandKeysValuesBucket(t testing.TB, bk *bolt.Bucket, n int) { + + var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + var min, max = 1, 1024 + + for i := 0; i < n; i++ { + // generate rand key/value length + keyLength := rand.Intn(max-min) + min + valLength := rand.Intn(max-min) + min + + keyData := make([]rune, keyLength) + valData := make([]rune, valLength) + + for j := range keyData { + keyData[j] = letters[rand.Intn(len(letters))] + } + + for j := range valData { + valData[j] = letters[rand.Intn(len(letters))] + } + + pErr := bk.Put([]byte(string(keyData)), []byte(string(valData))) + if pErr != nil { + t.Fatalf("error inserting key %v and value %v in bucket %v: %v", string(keyData), string(valData), bk.String(), pErr) + } + } + +}