2018-04-13 00:17:11 +08:00
|
|
|
package operations_test
|
|
|
|
|
|
|
|
import (
|
2019-06-17 16:34:30 +08:00
|
|
|
"context"
|
2018-04-13 00:17:11 +08:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2019-07-29 01:47:38 +08:00
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/hash"
|
|
|
|
"github.com/rclone/rclone/fs/operations"
|
|
|
|
"github.com/rclone/rclone/fs/walk"
|
|
|
|
"github.com/rclone/rclone/fstest"
|
2020-10-13 23:22:02 +08:00
|
|
|
"github.com/rclone/rclone/lib/random"
|
2019-12-10 21:28:56 +08:00
|
|
|
"github.com/spf13/pflag"
|
2018-04-13 00:17:11 +08:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2019-12-10 21:28:56 +08:00
|
|
|
// Check flag satisfies the interface
|
|
|
|
var _ pflag.Value = (*operations.DeduplicateMode)(nil)
|
|
|
|
|
2018-04-13 00:17:11 +08:00
|
|
|
func skipIfCantDedupe(t *testing.T, f fs.Fs) {
|
|
|
|
if !f.Features().DuplicateFiles {
|
|
|
|
t.Skip("Can't test deduplicate - no duplicate files possible")
|
|
|
|
}
|
|
|
|
if f.Features().PutUnchecked == nil {
|
|
|
|
t.Skip("Can't test deduplicate - no PutUnchecked")
|
|
|
|
}
|
|
|
|
if f.Features().MergeDirs == nil {
|
|
|
|
t.Skip("Can't test deduplicate - no MergeDirs")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func skipIfNoHash(t *testing.T, f fs.Fs) {
|
|
|
|
if f.Hashes().GetOne() == hash.None {
|
|
|
|
t.Skip("Can't run this test without a hash")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
func skipIfNoModTime(t *testing.T, f fs.Fs) {
|
|
|
|
if f.Precision() >= fs.ModTimeNotSupported {
|
|
|
|
t.Skip("Can't run this test without modtimes")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-13 00:17:11 +08:00
|
|
|
func TestDeduplicateInteractive(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
|
|
|
skipIfNoHash(t, r.Fremote)
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
2018-04-13 00:17:11 +08:00
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3)
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateInteractive, false)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file1)
|
2018-04-13 00:17:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDeduplicateSkip(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
|
|
|
haveHash := r.Fremote.Hashes().GetOne() != hash.None
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
2018-04-13 00:17:11 +08:00
|
|
|
files := []fstest.Item{file1}
|
|
|
|
if haveHash {
|
2019-06-17 16:34:30 +08:00
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
2018-04-13 00:17:11 +08:00
|
|
|
files = append(files, file2)
|
|
|
|
}
|
2019-06-17 16:34:30 +08:00
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is another one", t1)
|
2018-04-13 00:17:11 +08:00
|
|
|
files = append(files, file3)
|
|
|
|
r.CheckWithDuplicates(t, files...)
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateSkip, false)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
r.CheckWithDuplicates(t, file1, file3)
|
|
|
|
}
|
|
|
|
|
2020-06-16 19:39:26 +08:00
|
|
|
func TestDeduplicateSizeOnly(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
2020-11-05 19:33:32 +08:00
|
|
|
ctx := context.Background()
|
|
|
|
ci := fs.GetConfig(ctx)
|
2020-06-16 19:39:26 +08:00
|
|
|
|
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "THIS IS ONE", t1)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is another one", t1)
|
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3)
|
|
|
|
|
2020-11-05 19:33:32 +08:00
|
|
|
ci.SizeOnly = true
|
2020-06-16 19:39:26 +08:00
|
|
|
defer func() {
|
2020-11-05 19:33:32 +08:00
|
|
|
ci.SizeOnly = false
|
2020-06-16 19:39:26 +08:00
|
|
|
}()
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateSkip, false)
|
2020-06-16 19:39:26 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
r.CheckWithDuplicates(t, file1, file3)
|
|
|
|
}
|
|
|
|
|
2018-04-13 00:17:11 +08:00
|
|
|
func TestDeduplicateFirst(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "This is one A", t1)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is one BB", t1)
|
2018-04-13 00:17:11 +08:00
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3)
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateFirst, false)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// list until we get one object
|
|
|
|
var objects, size int64
|
|
|
|
for try := 1; try <= *fstest.ListRetries; try++ {
|
2022-04-06 20:15:07 +08:00
|
|
|
objects, size, _, err = operations.Count(context.Background(), r.Fremote)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
if objects == 1 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}
|
|
|
|
assert.Equal(t, int64(1), objects)
|
|
|
|
if size != file1.Size && size != file2.Size && size != file3.Size {
|
|
|
|
t.Errorf("Size not one of the object sizes %d", size)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDeduplicateNewest(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
2020-10-13 23:22:02 +08:00
|
|
|
skipIfNoModTime(t, r.Fremote)
|
2018-04-13 00:17:11 +08:00
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "This is one too", t2)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is another one", t3)
|
2018-04-13 00:17:11 +08:00
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3)
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateNewest, false)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file3)
|
2018-04-13 00:17:11 +08:00
|
|
|
}
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
func TestDeduplicateNewestByHash(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfNoHash(t, r.Fremote)
|
|
|
|
skipIfNoModTime(t, r.Fremote)
|
|
|
|
contents := random.String(100)
|
|
|
|
|
|
|
|
file1 := r.WriteObject(context.Background(), "one", contents, t1)
|
|
|
|
file2 := r.WriteObject(context.Background(), "also/one", contents, t2)
|
|
|
|
file3 := r.WriteObject(context.Background(), "another", contents, t3)
|
|
|
|
file4 := r.WriteObject(context.Background(), "not-one", "stuff", t3)
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file1, file2, file3, file4)
|
2020-10-13 23:22:02 +08:00
|
|
|
|
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateNewest, true)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file3, file4)
|
2020-10-13 23:22:02 +08:00
|
|
|
}
|
|
|
|
|
2018-04-13 00:17:11 +08:00
|
|
|
func TestDeduplicateOldest(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "This is one too", t2)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is another one", t3)
|
2018-04-13 00:17:11 +08:00
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3)
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateOldest, false)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file1)
|
2018-04-13 00:17:11 +08:00
|
|
|
}
|
|
|
|
|
2018-04-22 05:57:08 +08:00
|
|
|
func TestDeduplicateLargest(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "This is one too", t2)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is another one", t3)
|
2018-04-22 05:57:08 +08:00
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3)
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateLargest, false)
|
2018-04-22 05:57:08 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file3)
|
2018-04-22 05:57:08 +08:00
|
|
|
}
|
|
|
|
|
2020-01-16 21:47:15 +08:00
|
|
|
func TestDeduplicateSmallest(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
|
|
|
|
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one", "This is one too", t2)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one", "This is another one", t3)
|
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3)
|
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateSmallest, false)
|
2020-01-16 21:47:15 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file1)
|
2020-01-16 21:47:15 +08:00
|
|
|
}
|
|
|
|
|
2018-04-13 00:17:11 +08:00
|
|
|
func TestDeduplicateRename(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
skipIfCantDedupe(t, r.Fremote)
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteUncheckedObject(context.Background(), "one.txt", "This is one", t1)
|
|
|
|
file2 := r.WriteUncheckedObject(context.Background(), "one.txt", "This is one too", t2)
|
|
|
|
file3 := r.WriteUncheckedObject(context.Background(), "one.txt", "This is another one", t3)
|
|
|
|
file4 := r.WriteUncheckedObject(context.Background(), "one-1.txt", "This is not a duplicate", t1)
|
2018-10-29 12:05:45 +08:00
|
|
|
r.CheckWithDuplicates(t, file1, file2, file3, file4)
|
2018-04-13 00:17:11 +08:00
|
|
|
|
2020-10-13 23:22:02 +08:00
|
|
|
err := operations.Deduplicate(context.Background(), r.Fremote, operations.DeduplicateRename, false)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
require.NoError(t, walk.ListR(context.Background(), r.Fremote, "", true, -1, walk.ListObjects, func(entries fs.DirEntries) error {
|
2018-04-13 00:17:11 +08:00
|
|
|
entries.ForObject(func(o fs.Object) {
|
|
|
|
remote := o.Remote()
|
|
|
|
if remote != "one-1.txt" &&
|
|
|
|
remote != "one-2.txt" &&
|
2018-10-29 12:05:45 +08:00
|
|
|
remote != "one-3.txt" &&
|
|
|
|
remote != "one-4.txt" {
|
2018-04-13 00:17:11 +08:00
|
|
|
t.Errorf("Bad file name after rename %q", remote)
|
|
|
|
}
|
|
|
|
size := o.Size()
|
2018-10-29 12:05:45 +08:00
|
|
|
if size != file1.Size &&
|
|
|
|
size != file2.Size &&
|
|
|
|
size != file3.Size &&
|
|
|
|
size != file4.Size {
|
2018-04-13 00:17:11 +08:00
|
|
|
t.Errorf("Size not one of the object sizes %d", size)
|
2018-10-29 12:05:45 +08:00
|
|
|
}
|
|
|
|
if remote == "one-1.txt" && size != file4.Size {
|
|
|
|
t.Errorf("Existing non-duplicate file modified %q", remote)
|
2018-04-13 00:17:11 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should really be a unit test, but the test framework there
|
|
|
|
// doesn't have enough tools to make it easy
|
|
|
|
func TestMergeDirs(t *testing.T) {
|
|
|
|
r := fstest.NewRun(t)
|
|
|
|
defer r.Finalise()
|
|
|
|
|
|
|
|
mergeDirs := r.Fremote.Features().MergeDirs
|
|
|
|
if mergeDirs == nil {
|
|
|
|
t.Skip("Can't merge directories")
|
|
|
|
}
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
file1 := r.WriteObject(context.Background(), "dupe1/one.txt", "This is one", t1)
|
|
|
|
file2 := r.WriteObject(context.Background(), "dupe2/two.txt", "This is one too", t2)
|
|
|
|
file3 := r.WriteObject(context.Background(), "dupe3/three.txt", "This is another one", t3)
|
2018-04-13 00:17:11 +08:00
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
objs, dirs, err := walk.GetAll(context.Background(), r.Fremote, "", true, 1)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 3, len(dirs))
|
|
|
|
assert.Equal(t, 0, len(objs))
|
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
err = mergeDirs(context.Background(), dirs)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
file2.Path = "dupe1/two.txt"
|
|
|
|
file3.Path = "dupe1/three.txt"
|
2021-11-09 19:43:36 +08:00
|
|
|
r.CheckRemoteItems(t, file1, file2, file3)
|
2018-04-13 00:17:11 +08:00
|
|
|
|
2019-06-17 16:34:30 +08:00
|
|
|
objs, dirs, err = walk.GetAll(context.Background(), r.Fremote, "", true, 1)
|
2018-04-13 00:17:11 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 1, len(dirs))
|
|
|
|
assert.Equal(t, 0, len(objs))
|
|
|
|
assert.Equal(t, "dupe1", dirs[0].Remote())
|
|
|
|
}
|