github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/sync/sync_test.go (about)

     1  // Test sync/copy/move
     2  
     3  package sync
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"os/exec"
    13  	"runtime"
    14  	"sort"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	mutex "sync" // renamed as "sync" already in use
    20  
    21  	_ "github.com/rclone/rclone/backend/all" // import all backends
    22  	"github.com/rclone/rclone/cmd/bisync/bilib"
    23  	"github.com/rclone/rclone/fs"
    24  	"github.com/rclone/rclone/fs/accounting"
    25  	"github.com/rclone/rclone/fs/filter"
    26  	"github.com/rclone/rclone/fs/fserrors"
    27  	"github.com/rclone/rclone/fs/hash"
    28  	"github.com/rclone/rclone/fs/operations"
    29  	"github.com/rclone/rclone/fstest"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  	"golang.org/x/text/unicode/norm"
    33  )
    34  
    35  // Some times used in the tests
    36  var (
    37  	t1 = fstest.Time("2001-02-03T04:05:06.499999999Z")
    38  	t2 = fstest.Time("2011-12-25T12:59:59.123456789Z")
    39  	t3 = fstest.Time("2011-12-30T12:59:59.000000000Z")
    40  )
    41  
    42  // TestMain drives the tests
    43  func TestMain(m *testing.M) {
    44  	fstest.TestMain(m)
    45  }
    46  
    47  // Check dry run is working
    48  func TestCopyWithDryRun(t *testing.T) {
    49  	ctx := context.Background()
    50  	ctx, ci := fs.AddConfig(ctx)
    51  	r := fstest.NewRun(t)
    52  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
    53  	r.Mkdir(ctx, r.Fremote)
    54  
    55  	ci.DryRun = true
    56  	ctx = predictDstFromLogger(ctx)
    57  	err := CopyDir(ctx, r.Fremote, r.Flocal, false)
    58  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t) // error expected here because dry-run
    59  	require.NoError(t, err)
    60  
    61  	r.CheckLocalItems(t, file1)
    62  	r.CheckRemoteItems(t)
    63  }
    64  
    65  // Now without dry run
    66  func TestCopy(t *testing.T) {
    67  	ctx := context.Background()
    68  	r := fstest.NewRun(t)
    69  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
    70  	_, err := operations.SetDirModTime(ctx, r.Flocal, nil, "sub dir", t2)
    71  	if err != nil && !errors.Is(err, fs.ErrorNotImplemented) {
    72  		require.NoError(t, err)
    73  	}
    74  	r.Mkdir(ctx, r.Fremote)
    75  
    76  	ctx = predictDstFromLogger(ctx)
    77  	err = CopyDir(ctx, r.Fremote, r.Flocal, false)
    78  	require.NoError(t, err)
    79  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
    80  
    81  	r.CheckLocalItems(t, file1)
    82  	r.CheckRemoteItems(t, file1)
    83  
    84  	// Check that the modtimes of the directories are as expected
    85  	r.CheckDirectoryModTimes(t, "sub dir")
    86  }
    87  
    88  func testCopyMetadata(t *testing.T, createEmptySrcDirs bool) {
    89  	ctx := context.Background()
    90  	ctx, ci := fs.AddConfig(ctx)
    91  	ci.Metadata = true
    92  	r := fstest.NewRun(t)
    93  	features := r.Fremote.Features()
    94  
    95  	if !features.ReadMetadata && !features.WriteMetadata && !features.UserMetadata &&
    96  		!features.ReadDirMetadata && !features.WriteDirMetadata && !features.UserDirMetadata {
    97  		t.Skip("Skipping as metadata not supported")
    98  	}
    99  
   100  	const content = "hello metadata world!"
   101  	const dirPath = "metadata sub dir"
   102  	const emptyDirPath = "empty metadata sub dir"
   103  	const filePath = dirPath + "/hello metadata world"
   104  
   105  	fileMetadata := fs.Metadata{
   106  		// System metadata supported by all backends
   107  		"mtime": t1.Format(time.RFC3339Nano),
   108  		// User metadata
   109  		"potato": "jersey",
   110  	}
   111  
   112  	dirMetadata := fs.Metadata{
   113  		// System metadata supported by all backends
   114  		"mtime": t2.Format(time.RFC3339Nano),
   115  		// User metadata
   116  		"potato": "king edward",
   117  	}
   118  
   119  	// Make the directory with metadata - may fall back to Mkdir
   120  	_, err := operations.MkdirMetadata(ctx, r.Flocal, dirPath, dirMetadata)
   121  	require.NoError(t, err)
   122  
   123  	// Make the empty directory with metadata - may fall back to Mkdir
   124  	_, err = operations.MkdirMetadata(ctx, r.Flocal, emptyDirPath, dirMetadata)
   125  	require.NoError(t, err)
   126  
   127  	// Upload the file with metadata
   128  	in := io.NopCloser(bytes.NewBufferString(content))
   129  	_, err = operations.Rcat(ctx, r.Flocal, filePath, in, t1, fileMetadata)
   130  	require.NoError(t, err)
   131  	file1 := fstest.NewItem(filePath, content, t1)
   132  
   133  	// Reset the time of the directory
   134  	_, err = operations.SetDirModTime(ctx, r.Flocal, nil, dirPath, t2)
   135  	if err != nil && !errors.Is(err, fs.ErrorNotImplemented) {
   136  		require.NoError(t, err)
   137  	}
   138  
   139  	ctx = predictDstFromLogger(ctx)
   140  	err = CopyDir(ctx, r.Fremote, r.Flocal, createEmptySrcDirs)
   141  	require.NoError(t, err)
   142  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   143  
   144  	r.CheckLocalItems(t, file1)
   145  	r.CheckRemoteItems(t, file1)
   146  
   147  	// Check that the modtimes of the directories are as expected
   148  	r.CheckDirectoryModTimes(t, dirPath)
   149  
   150  	// Check that the metadata on the directory and file is correct
   151  	if features.ReadMetadata {
   152  		fstest.CheckEntryMetadata(ctx, t, r.Fremote, fstest.NewObject(ctx, t, r.Fremote, filePath), fileMetadata)
   153  	}
   154  	if features.ReadDirMetadata {
   155  		fstest.CheckEntryMetadata(ctx, t, r.Fremote, fstest.NewDirectory(ctx, t, r.Fremote, dirPath), dirMetadata)
   156  	}
   157  	if !createEmptySrcDirs {
   158  		// dir must not exist
   159  		_, err := fstest.NewDirectoryRetries(ctx, t, r.Fremote, emptyDirPath, 1)
   160  		assert.Error(t, err, "Not expecting to find empty directory")
   161  		assert.True(t, errors.Is(err, fs.ErrorDirNotFound), fmt.Sprintf("expecting wrapped %#v not: %#v", fs.ErrorDirNotFound, err))
   162  	} else {
   163  		// dir must exist
   164  		dir := fstest.NewDirectory(ctx, t, r.Fremote, emptyDirPath)
   165  		if features.ReadDirMetadata {
   166  			fstest.CheckEntryMetadata(ctx, t, r.Fremote, dir, dirMetadata)
   167  		}
   168  	}
   169  }
   170  
   171  func TestCopyMetadata(t *testing.T) {
   172  	testCopyMetadata(t, true)
   173  }
   174  
   175  func TestCopyMetadataNoEmptyDirs(t *testing.T) {
   176  	testCopyMetadata(t, false)
   177  }
   178  
   179  func TestCopyMissingDirectory(t *testing.T) {
   180  	ctx := context.Background()
   181  	r := fstest.NewRun(t)
   182  	r.Mkdir(ctx, r.Fremote)
   183  
   184  	nonExistingFs, err := fs.NewFs(ctx, "/non-existing")
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  
   189  	ctx = predictDstFromLogger(ctx)
   190  	err = CopyDir(ctx, r.Fremote, nonExistingFs, false)
   191  	require.Error(t, err)
   192  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   193  }
   194  
   195  // Now with --no-traverse
   196  func TestCopyNoTraverse(t *testing.T) {
   197  	ctx := context.Background()
   198  	ctx, ci := fs.AddConfig(ctx)
   199  	r := fstest.NewRun(t)
   200  
   201  	ci.NoTraverse = true
   202  
   203  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   204  
   205  	ctx = predictDstFromLogger(ctx)
   206  	err := CopyDir(ctx, r.Fremote, r.Flocal, false)
   207  	require.NoError(t, err)
   208  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   209  
   210  	r.CheckLocalItems(t, file1)
   211  	r.CheckRemoteItems(t, file1)
   212  }
   213  
   214  // Now with --check-first
   215  func TestCopyCheckFirst(t *testing.T) {
   216  	ctx := context.Background()
   217  	ctx, ci := fs.AddConfig(ctx)
   218  	r := fstest.NewRun(t)
   219  
   220  	ci.CheckFirst = true
   221  
   222  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   223  
   224  	ctx = predictDstFromLogger(ctx)
   225  	err := CopyDir(ctx, r.Fremote, r.Flocal, false)
   226  	require.NoError(t, err)
   227  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   228  
   229  	r.CheckLocalItems(t, file1)
   230  	r.CheckRemoteItems(t, file1)
   231  }
   232  
   233  // Now with --no-traverse
   234  func TestSyncNoTraverse(t *testing.T) {
   235  	ctx := context.Background()
   236  	ctx, ci := fs.AddConfig(ctx)
   237  	r := fstest.NewRun(t)
   238  
   239  	ci.NoTraverse = true
   240  
   241  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   242  
   243  	accounting.GlobalStats().ResetCounters()
   244  	ctx = predictDstFromLogger(ctx)
   245  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   246  	require.NoError(t, err)
   247  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   248  
   249  	r.CheckLocalItems(t, file1)
   250  	r.CheckRemoteItems(t, file1)
   251  }
   252  
   253  // Test copy with depth
   254  func TestCopyWithDepth(t *testing.T) {
   255  	ctx := context.Background()
   256  	ctx, ci := fs.AddConfig(ctx)
   257  	r := fstest.NewRun(t)
   258  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   259  	file2 := r.WriteFile("hello world2", "hello world2", t2)
   260  
   261  	// Check the MaxDepth too
   262  	ci.MaxDepth = 1
   263  
   264  	ctx = predictDstFromLogger(ctx)
   265  	err := CopyDir(ctx, r.Fremote, r.Flocal, false)
   266  	require.NoError(t, err)
   267  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   268  
   269  	r.CheckLocalItems(t, file1, file2)
   270  	r.CheckRemoteItems(t, file2)
   271  }
   272  
   273  // Test copy with files from
   274  func testCopyWithFilesFrom(t *testing.T, noTraverse bool) {
   275  	ctx := context.Background()
   276  	ctx, ci := fs.AddConfig(ctx)
   277  	r := fstest.NewRun(t)
   278  	file1 := r.WriteFile("potato2", "hello world", t1)
   279  	file2 := r.WriteFile("hello world2", "hello world2", t2)
   280  
   281  	// Set the --files-from equivalent
   282  	f, err := filter.NewFilter(nil)
   283  	require.NoError(t, err)
   284  	require.NoError(t, f.AddFile("potato2"))
   285  	require.NoError(t, f.AddFile("notfound"))
   286  
   287  	// Change the active filter
   288  	ctx = filter.ReplaceConfig(ctx, f)
   289  
   290  	ci.NoTraverse = noTraverse
   291  
   292  	ctx = predictDstFromLogger(ctx)
   293  	err = CopyDir(ctx, r.Fremote, r.Flocal, false)
   294  	require.NoError(t, err)
   295  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   296  
   297  	r.CheckLocalItems(t, file1, file2)
   298  	r.CheckRemoteItems(t, file1)
   299  }
   300  func TestCopyWithFilesFrom(t *testing.T)              { testCopyWithFilesFrom(t, false) }
   301  func TestCopyWithFilesFromAndNoTraverse(t *testing.T) { testCopyWithFilesFrom(t, true) }
   302  
   303  // Test copy empty directories
   304  func TestCopyEmptyDirectories(t *testing.T) {
   305  	ctx := context.Background()
   306  	r := fstest.NewRun(t)
   307  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   308  	_, err := operations.MkdirModTime(ctx, r.Flocal, "sub dir2/sub sub dir2", t2)
   309  	require.NoError(t, err)
   310  	_, err = operations.SetDirModTime(ctx, r.Flocal, nil, "sub dir2", t2)
   311  	require.NoError(t, err)
   312  	r.Mkdir(ctx, r.Fremote)
   313  
   314  	// Set the modtime on "sub dir" to something specific
   315  	// Without this it fails on the CI and in VirtualBox with variances of up to 10mS
   316  	_, err = operations.SetDirModTime(ctx, r.Flocal, nil, "sub dir", t1)
   317  	require.NoError(t, err)
   318  
   319  	ctx = predictDstFromLogger(ctx)
   320  	err = CopyDir(ctx, r.Fremote, r.Flocal, true)
   321  	require.NoError(t, err)
   322  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   323  
   324  	r.CheckRemoteListing(
   325  		t,
   326  		[]fstest.Item{
   327  			file1,
   328  		},
   329  		[]string{
   330  			"sub dir",
   331  			"sub dir2",
   332  			"sub dir2/sub sub dir2",
   333  		},
   334  	)
   335  
   336  	// Check that the modtimes of the directories are as expected
   337  	r.CheckDirectoryModTimes(t, "sub dir", "sub dir2", "sub dir2/sub sub dir2")
   338  }
   339  
   340  // Test copy empty directories when we are configured not to create them
   341  func TestCopyNoEmptyDirectories(t *testing.T) {
   342  	ctx := context.Background()
   343  	r := fstest.NewRun(t)
   344  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   345  	err := operations.Mkdir(ctx, r.Flocal, "sub dir2")
   346  	require.NoError(t, err)
   347  	_, err = operations.MkdirModTime(ctx, r.Flocal, "sub dir2/sub sub dir2", t2)
   348  	require.NoError(t, err)
   349  	r.Mkdir(ctx, r.Fremote)
   350  
   351  	err = CopyDir(ctx, r.Fremote, r.Flocal, false)
   352  	require.NoError(t, err)
   353  
   354  	r.CheckRemoteListing(
   355  		t,
   356  		[]fstest.Item{
   357  			file1,
   358  		},
   359  		[]string{
   360  			"sub dir",
   361  		},
   362  	)
   363  }
   364  
   365  // Test move empty directories
   366  func TestMoveEmptyDirectories(t *testing.T) {
   367  	ctx := context.Background()
   368  	r := fstest.NewRun(t)
   369  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   370  	_, err := operations.MkdirModTime(ctx, r.Flocal, "sub dir2", t2)
   371  	require.NoError(t, err)
   372  	subDir := fstest.NewDirectory(ctx, t, r.Flocal, "sub dir")
   373  	subDirT := subDir.ModTime(ctx)
   374  	r.Mkdir(ctx, r.Fremote)
   375  
   376  	ctx = predictDstFromLogger(ctx)
   377  	err = MoveDir(ctx, r.Fremote, r.Flocal, false, true)
   378  	require.NoError(t, err)
   379  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   380  
   381  	r.CheckRemoteListing(
   382  		t,
   383  		[]fstest.Item{
   384  			file1,
   385  		},
   386  		[]string{
   387  			"sub dir",
   388  			"sub dir2",
   389  		},
   390  	)
   391  
   392  	// Check that the modtimes of the directories are as expected
   393  	r.CheckDirectoryModTimes(t, "sub dir2")
   394  	// Note that "sub dir" mod time is updated when file1 is deleted from it
   395  	// So check it more manually
   396  	got := fstest.NewDirectory(ctx, t, r.Fremote, "sub dir")
   397  	fstest.CheckDirModTime(ctx, t, r.Fremote, got, subDirT)
   398  }
   399  
   400  // Test that --no-update-dir-modtime is working
   401  func TestSyncNoUpdateDirModtime(t *testing.T) {
   402  	r := fstest.NewRun(t)
   403  	if r.Fremote.Features().DirSetModTime == nil {
   404  		t.Skip("Skipping test as backend does not support DirSetModTime")
   405  	}
   406  
   407  	ctx, ci := fs.AddConfig(context.Background())
   408  	ci.NoUpdateDirModTime = true
   409  	const name = "sub dir no update dir modtime"
   410  
   411  	// Set the modtime on name to something specific
   412  	_, err := operations.MkdirModTime(ctx, r.Flocal, name, t1)
   413  	require.NoError(t, err)
   414  
   415  	// Create the remote directory with the current time
   416  	require.NoError(t, r.Fremote.Mkdir(ctx, name))
   417  
   418  	// Read its modification time
   419  	wantT := fstest.NewDirectory(ctx, t, r.Fremote, name).ModTime(ctx)
   420  
   421  	ctx = predictDstFromLogger(ctx)
   422  	err = Sync(ctx, r.Fremote, r.Flocal, true)
   423  	require.NoError(t, err)
   424  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   425  
   426  	r.CheckRemoteListing(
   427  		t,
   428  		[]fstest.Item{},
   429  		[]string{
   430  			name,
   431  		},
   432  	)
   433  
   434  	// Read the new directory modification time - it should not have changed
   435  	gotT := fstest.NewDirectory(ctx, t, r.Fremote, name).ModTime(ctx)
   436  	fstest.AssertTimeEqualWithPrecision(t, name, wantT, gotT, r.Fremote.Precision())
   437  }
   438  
   439  // Test move empty directories when we are not configured to create them
   440  func TestMoveNoEmptyDirectories(t *testing.T) {
   441  	ctx := context.Background()
   442  	r := fstest.NewRun(t)
   443  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   444  	err := operations.Mkdir(ctx, r.Flocal, "sub dir2")
   445  	require.NoError(t, err)
   446  	r.Mkdir(ctx, r.Fremote)
   447  
   448  	err = MoveDir(ctx, r.Fremote, r.Flocal, false, false)
   449  	require.NoError(t, err)
   450  
   451  	r.CheckRemoteListing(
   452  		t,
   453  		[]fstest.Item{
   454  			file1,
   455  		},
   456  		[]string{
   457  			"sub dir",
   458  		},
   459  	)
   460  }
   461  
   462  // Test sync empty directories
   463  func TestSyncEmptyDirectories(t *testing.T) {
   464  	ctx := context.Background()
   465  	r := fstest.NewRun(t)
   466  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   467  	_, err := operations.MkdirModTime(ctx, r.Flocal, "sub dir2", t2)
   468  	require.NoError(t, err)
   469  
   470  	// Set the modtime on "sub dir" to something specific
   471  	// Without this it fails on the CI and in VirtualBox with variances of up to 10mS
   472  	_, err = operations.SetDirModTime(ctx, r.Flocal, nil, "sub dir", t1)
   473  	require.NoError(t, err)
   474  
   475  	r.Mkdir(ctx, r.Fremote)
   476  
   477  	ctx = predictDstFromLogger(ctx)
   478  	err = Sync(ctx, r.Fremote, r.Flocal, true)
   479  	require.NoError(t, err)
   480  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   481  
   482  	r.CheckRemoteListing(
   483  		t,
   484  		[]fstest.Item{
   485  			file1,
   486  		},
   487  		[]string{
   488  			"sub dir",
   489  			"sub dir2",
   490  		},
   491  	)
   492  
   493  	// Check that the modtimes of the directories are as expected
   494  	r.CheckDirectoryModTimes(t, "sub dir", "sub dir2")
   495  }
   496  
   497  // Test delayed mod time setting
   498  func TestSyncSetDelayedModTimes(t *testing.T) {
   499  	ctx := context.Background()
   500  	r := fstest.NewRun(t)
   501  
   502  	if !r.Fremote.Features().DirModTimeUpdatesOnWrite {
   503  		t.Skip("Backend doesn't have DirModTimeUpdatesOnWrite set")
   504  	}
   505  
   506  	// Create directories without timestamps
   507  	require.NoError(t, r.Flocal.Mkdir(ctx, "a1/b1/c1/d1/e1/f1"))
   508  	require.NoError(t, r.Flocal.Mkdir(ctx, "a1/b2/c1/d1/e1/f1"))
   509  	require.NoError(t, r.Flocal.Mkdir(ctx, "a1/b1/c1/d2/e1/f1"))
   510  	require.NoError(t, r.Flocal.Mkdir(ctx, "a1/b1/c1/d2/e1/f2"))
   511  
   512  	dirs := []string{
   513  		"a1",
   514  		"a1/b1",
   515  		"a1/b1/c1",
   516  		"a1/b1/c1/d1",
   517  		"a1/b1/c1/d1/e1",
   518  		"a1/b1/c1/d1/e1/f1",
   519  		"a1/b1/c1/d2",
   520  		"a1/b1/c1/d2/e1",
   521  		"a1/b1/c1/d2/e1/f1",
   522  		"a1/b1/c1/d2/e1/f2",
   523  		"a1/b2",
   524  		"a1/b2/c1",
   525  		"a1/b2/c1/d1",
   526  		"a1/b2/c1/d1/e1",
   527  		"a1/b2/c1/d1/e1/f1",
   528  	}
   529  	r.CheckLocalListing(t, []fstest.Item{}, dirs)
   530  
   531  	// Timestamp the directories in reverse order
   532  	ts := t1
   533  	for i := len(dirs) - 1; i >= 0; i-- {
   534  		dir := dirs[i]
   535  		_, err := operations.SetDirModTime(ctx, r.Flocal, nil, dir, ts)
   536  		require.NoError(t, err)
   537  		ts = ts.Add(time.Minute)
   538  	}
   539  
   540  	r.Mkdir(ctx, r.Fremote)
   541  
   542  	ctx = predictDstFromLogger(ctx)
   543  	err := Sync(ctx, r.Fremote, r.Flocal, true)
   544  	require.NoError(t, err)
   545  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   546  
   547  	r.CheckRemoteListing(t, []fstest.Item{}, dirs)
   548  
   549  	// Check that the modtimes of the directories are as expected
   550  	r.CheckDirectoryModTimes(t, dirs...)
   551  }
   552  
   553  // Test sync empty directories when we are not configured to create them
   554  func TestSyncNoEmptyDirectories(t *testing.T) {
   555  	ctx := context.Background()
   556  	r := fstest.NewRun(t)
   557  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
   558  	err := operations.Mkdir(ctx, r.Flocal, "sub dir2")
   559  	require.NoError(t, err)
   560  	r.Mkdir(ctx, r.Fremote)
   561  
   562  	err = Sync(ctx, r.Fremote, r.Flocal, false)
   563  	require.NoError(t, err)
   564  
   565  	r.CheckRemoteListing(
   566  		t,
   567  		[]fstest.Item{
   568  			file1,
   569  		},
   570  		[]string{
   571  			"sub dir",
   572  		},
   573  	)
   574  }
   575  
   576  // Test a server-side copy if possible, or the backup path if not
   577  func TestServerSideCopy(t *testing.T) {
   578  	ctx := context.Background()
   579  	r := fstest.NewRun(t)
   580  	file1 := r.WriteObject(ctx, "sub dir/hello world", "hello world", t1)
   581  	r.CheckRemoteItems(t, file1)
   582  
   583  	FremoteCopy, _, finaliseCopy, err := fstest.RandomRemote()
   584  	require.NoError(t, err)
   585  	defer finaliseCopy()
   586  	t.Logf("Server side copy (if possible) %v -> %v", r.Fremote, FremoteCopy)
   587  
   588  	ctx = predictDstFromLogger(ctx)
   589  	err = CopyDir(ctx, FremoteCopy, r.Fremote, false)
   590  	require.NoError(t, err)
   591  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   592  
   593  	fstest.CheckItems(t, FremoteCopy, file1)
   594  }
   595  
   596  // Check that if the local file doesn't exist when we copy it up,
   597  // nothing happens to the remote file
   598  func TestCopyAfterDelete(t *testing.T) {
   599  	ctx := context.Background()
   600  	r := fstest.NewRun(t)
   601  	file1 := r.WriteObject(ctx, "sub dir/hello world", "hello world", t1)
   602  	r.CheckLocalItems(t)
   603  	r.CheckRemoteItems(t, file1)
   604  
   605  	err := operations.Mkdir(ctx, r.Flocal, "")
   606  	require.NoError(t, err)
   607  
   608  	ctx = predictDstFromLogger(ctx)
   609  	err = CopyDir(ctx, r.Fremote, r.Flocal, false)
   610  	require.NoError(t, err)
   611  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   612  
   613  	r.CheckLocalItems(t)
   614  	r.CheckRemoteItems(t, file1)
   615  }
   616  
   617  // Check the copy downloading a file
   618  func TestCopyRedownload(t *testing.T) {
   619  	ctx := context.Background()
   620  	r := fstest.NewRun(t)
   621  	file1 := r.WriteObject(ctx, "sub dir/hello world", "hello world", t1)
   622  	r.CheckRemoteItems(t, file1)
   623  
   624  	ctx = predictDstFromLogger(ctx)
   625  	err := CopyDir(ctx, r.Flocal, r.Fremote, false)
   626  	require.NoError(t, err)
   627  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   628  
   629  	// Test with combined precision of local and remote as we copied it there and back
   630  	r.CheckLocalListing(t, []fstest.Item{file1}, nil)
   631  }
   632  
   633  // Create a file and sync it. Change the last modified date and resync.
   634  // If we're only doing sync by size and checksum, we expect nothing to
   635  // to be transferred on the second sync.
   636  func TestSyncBasedOnCheckSum(t *testing.T) {
   637  	ctx := context.Background()
   638  	ctx, ci := fs.AddConfig(ctx)
   639  	r := fstest.NewRun(t)
   640  	ci.CheckSum = true
   641  
   642  	file1 := r.WriteFile("check sum", "-", t1)
   643  	r.CheckLocalItems(t, file1)
   644  
   645  	accounting.GlobalStats().ResetCounters()
   646  	ctx = predictDstFromLogger(ctx)
   647  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   648  	require.NoError(t, err)
   649  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   650  
   651  	// We should have transferred exactly one file.
   652  	assert.Equal(t, toyFileTransfers(r), accounting.GlobalStats().GetTransfers())
   653  	r.CheckRemoteItems(t, file1)
   654  
   655  	// Change last modified date only
   656  	file2 := r.WriteFile("check sum", "-", t2)
   657  	r.CheckLocalItems(t, file2)
   658  
   659  	accounting.GlobalStats().ResetCounters()
   660  	ctx = predictDstFromLogger(ctx)
   661  	err = Sync(ctx, r.Fremote, r.Flocal, false)
   662  	require.NoError(t, err)
   663  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   664  
   665  	// We should have transferred no files
   666  	assert.Equal(t, int64(0), accounting.GlobalStats().GetTransfers())
   667  	r.CheckLocalItems(t, file2)
   668  	r.CheckRemoteItems(t, file1)
   669  }
   670  
   671  // Create a file and sync it. Change the last modified date and the
   672  // file contents but not the size.  If we're only doing sync by size
   673  // only, we expect nothing to to be transferred on the second sync.
   674  func TestSyncSizeOnly(t *testing.T) {
   675  	ctx := context.Background()
   676  	ctx, ci := fs.AddConfig(ctx)
   677  	r := fstest.NewRun(t)
   678  	ci.SizeOnly = true
   679  
   680  	file1 := r.WriteFile("sizeonly", "potato", t1)
   681  	r.CheckLocalItems(t, file1)
   682  
   683  	accounting.GlobalStats().ResetCounters()
   684  	ctx = predictDstFromLogger(ctx)
   685  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   686  	require.NoError(t, err)
   687  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   688  
   689  	// We should have transferred exactly one file.
   690  	assert.Equal(t, toyFileTransfers(r), accounting.GlobalStats().GetTransfers())
   691  	r.CheckRemoteItems(t, file1)
   692  
   693  	// Update mtime, md5sum but not length of file
   694  	file2 := r.WriteFile("sizeonly", "POTATO", t2)
   695  	r.CheckLocalItems(t, file2)
   696  
   697  	accounting.GlobalStats().ResetCounters()
   698  	ctx = predictDstFromLogger(ctx)
   699  	err = Sync(ctx, r.Fremote, r.Flocal, false)
   700  	require.NoError(t, err)
   701  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   702  
   703  	// We should have transferred no files
   704  	assert.Equal(t, int64(0), accounting.GlobalStats().GetTransfers())
   705  	r.CheckLocalItems(t, file2)
   706  	r.CheckRemoteItems(t, file1)
   707  }
   708  
   709  // Create a file and sync it. Keep the last modified date but change
   710  // the size.  With --ignore-size we expect nothing to to be
   711  // transferred on the second sync.
   712  func TestSyncIgnoreSize(t *testing.T) {
   713  	ctx := context.Background()
   714  	ctx, ci := fs.AddConfig(ctx)
   715  	r := fstest.NewRun(t)
   716  	ci.IgnoreSize = true
   717  
   718  	file1 := r.WriteFile("ignore-size", "contents", t1)
   719  	r.CheckLocalItems(t, file1)
   720  
   721  	accounting.GlobalStats().ResetCounters()
   722  	ctx = predictDstFromLogger(ctx)
   723  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   724  	require.NoError(t, err)
   725  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   726  
   727  	// We should have transferred exactly one file.
   728  	assert.Equal(t, toyFileTransfers(r), accounting.GlobalStats().GetTransfers())
   729  	r.CheckRemoteItems(t, file1)
   730  
   731  	// Update size but not date of file
   732  	file2 := r.WriteFile("ignore-size", "longer contents but same date", t1)
   733  	r.CheckLocalItems(t, file2)
   734  
   735  	accounting.GlobalStats().ResetCounters()
   736  	ctx = predictDstFromLogger(ctx)
   737  	err = Sync(ctx, r.Fremote, r.Flocal, false)
   738  	require.NoError(t, err)
   739  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   740  
   741  	// We should have transferred no files
   742  	assert.Equal(t, int64(0), accounting.GlobalStats().GetTransfers())
   743  	r.CheckLocalItems(t, file2)
   744  	r.CheckRemoteItems(t, file1)
   745  }
   746  
   747  func TestSyncIgnoreTimes(t *testing.T) {
   748  	ctx := context.Background()
   749  	ctx, ci := fs.AddConfig(ctx)
   750  	r := fstest.NewRun(t)
   751  	file1 := r.WriteBoth(ctx, "existing", "potato", t1)
   752  	r.CheckRemoteItems(t, file1)
   753  
   754  	accounting.GlobalStats().ResetCounters()
   755  	ctx = predictDstFromLogger(ctx)
   756  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   757  	require.NoError(t, err)
   758  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   759  
   760  	// We should have transferred exactly 0 files because the
   761  	// files were identical.
   762  	assert.Equal(t, int64(0), accounting.GlobalStats().GetTransfers())
   763  
   764  	ci.IgnoreTimes = true
   765  
   766  	accounting.GlobalStats().ResetCounters()
   767  	ctx = predictDstFromLogger(ctx)
   768  	err = Sync(ctx, r.Fremote, r.Flocal, false)
   769  	require.NoError(t, err)
   770  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   771  
   772  	// We should have transferred exactly one file even though the
   773  	// files were identical.
   774  	assert.Equal(t, toyFileTransfers(r), accounting.GlobalStats().GetTransfers())
   775  
   776  	r.CheckLocalItems(t, file1)
   777  	r.CheckRemoteItems(t, file1)
   778  }
   779  
   780  func TestSyncIgnoreExisting(t *testing.T) {
   781  	ctx := context.Background()
   782  	ctx, ci := fs.AddConfig(ctx)
   783  	r := fstest.NewRun(t)
   784  	file1 := r.WriteFile("existing", "potato", t1)
   785  
   786  	ci.IgnoreExisting = true
   787  
   788  	accounting.GlobalStats().ResetCounters()
   789  	ctx = predictDstFromLogger(ctx)
   790  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   791  	require.NoError(t, err)
   792  	r.CheckLocalItems(t, file1)
   793  	r.CheckRemoteItems(t, file1)
   794  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   795  
   796  	// Change everything
   797  	r.WriteFile("existing", "newpotatoes", t2)
   798  	accounting.GlobalStats().ResetCounters()
   799  	ctx = predictDstFromLogger(ctx)
   800  	err = Sync(ctx, r.Fremote, r.Flocal, false)
   801  	require.NoError(t, err)
   802  	// Items should not change
   803  	r.CheckRemoteItems(t, file1)
   804  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   805  }
   806  
   807  func TestSyncIgnoreErrors(t *testing.T) {
   808  	ctx := context.Background()
   809  	ctx, ci := fs.AddConfig(ctx)
   810  	r := fstest.NewRun(t)
   811  	ci.IgnoreErrors = true
   812  	file1 := r.WriteFile("a/potato2", "------------------------------------------------------------", t1)
   813  	file2 := r.WriteObject(ctx, "b/potato", "SMALLER BUT SAME DATE", t2)
   814  	file3 := r.WriteBoth(ctx, "c/non empty space", "AhHa!", t2)
   815  	require.NoError(t, operations.Mkdir(ctx, r.Fremote, "d"))
   816  
   817  	r.CheckLocalListing(
   818  		t,
   819  		[]fstest.Item{
   820  			file1,
   821  			file3,
   822  		},
   823  		[]string{
   824  			"a",
   825  			"c",
   826  		},
   827  	)
   828  	r.CheckRemoteListing(
   829  		t,
   830  		[]fstest.Item{
   831  			file2,
   832  			file3,
   833  		},
   834  		[]string{
   835  			"b",
   836  			"c",
   837  			"d",
   838  		},
   839  	)
   840  
   841  	accounting.GlobalStats().ResetCounters()
   842  	ctx = predictDstFromLogger(ctx)
   843  	_ = fs.CountError(errors.New("boom"))
   844  	assert.NoError(t, Sync(ctx, r.Fremote, r.Flocal, false))
   845  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   846  
   847  	r.CheckLocalListing(
   848  		t,
   849  		[]fstest.Item{
   850  			file1,
   851  			file3,
   852  		},
   853  		[]string{
   854  			"a",
   855  			"c",
   856  		},
   857  	)
   858  	r.CheckRemoteListing(
   859  		t,
   860  		[]fstest.Item{
   861  			file1,
   862  			file3,
   863  		},
   864  		[]string{
   865  			"a",
   866  			"c",
   867  		},
   868  	)
   869  }
   870  
   871  func TestSyncAfterChangingModtimeOnly(t *testing.T) {
   872  	ctx := context.Background()
   873  	ctx, ci := fs.AddConfig(ctx)
   874  	r := fstest.NewRun(t)
   875  	file1 := r.WriteFile("empty space", "-", t2)
   876  	file2 := r.WriteObject(ctx, "empty space", "-", t1)
   877  
   878  	r.CheckLocalItems(t, file1)
   879  	r.CheckRemoteItems(t, file2)
   880  
   881  	ci.DryRun = true
   882  
   883  	accounting.GlobalStats().ResetCounters()
   884  	ctx = predictDstFromLogger(ctx)
   885  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   886  	require.NoError(t, err)
   887  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   888  
   889  	r.CheckLocalItems(t, file1)
   890  	r.CheckRemoteItems(t, file2)
   891  
   892  	ci.DryRun = false
   893  
   894  	accounting.GlobalStats().ResetCounters()
   895  	ctx = predictDstFromLogger(ctx)
   896  	err = Sync(ctx, r.Fremote, r.Flocal, false)
   897  	require.NoError(t, err)
   898  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   899  
   900  	r.CheckLocalItems(t, file1)
   901  	r.CheckRemoteItems(t, file1)
   902  }
   903  
   904  func TestSyncAfterChangingModtimeOnlyWithNoUpdateModTime(t *testing.T) {
   905  	ctx := context.Background()
   906  	ctx, ci := fs.AddConfig(ctx)
   907  	r := fstest.NewRun(t)
   908  
   909  	if r.Fremote.Hashes().Count() == 0 {
   910  		t.Logf("Can't check this if no hashes supported")
   911  		return
   912  	}
   913  
   914  	ci.NoUpdateModTime = true
   915  
   916  	file1 := r.WriteFile("empty space", "-", t2)
   917  	file2 := r.WriteObject(ctx, "empty space", "-", t1)
   918  
   919  	r.CheckLocalItems(t, file1)
   920  	r.CheckRemoteItems(t, file2)
   921  
   922  	accounting.GlobalStats().ResetCounters()
   923  	ctx = predictDstFromLogger(ctx)
   924  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   925  	require.NoError(t, err)
   926  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   927  
   928  	r.CheckLocalItems(t, file1)
   929  	r.CheckRemoteItems(t, file2)
   930  }
   931  
   932  func TestSyncDoesntUpdateModtime(t *testing.T) {
   933  	ctx := context.Background()
   934  	r := fstest.NewRun(t)
   935  	if fs.GetModifyWindow(ctx, r.Fremote) == fs.ModTimeNotSupported {
   936  		t.Skip("Can't run this test on fs which doesn't support mod time")
   937  	}
   938  
   939  	file1 := r.WriteFile("foo", "foo", t2)
   940  	file2 := r.WriteObject(ctx, "foo", "bar", t1)
   941  
   942  	r.CheckLocalItems(t, file1)
   943  	r.CheckRemoteItems(t, file2)
   944  
   945  	accounting.GlobalStats().ResetCounters()
   946  	ctx = predictDstFromLogger(ctx)
   947  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   948  	require.NoError(t, err)
   949  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   950  
   951  	r.CheckLocalItems(t, file1)
   952  	r.CheckRemoteItems(t, file1)
   953  
   954  	// We should have transferred exactly one file, not set the mod time
   955  	assert.Equal(t, toyFileTransfers(r), accounting.GlobalStats().GetTransfers())
   956  }
   957  
   958  func TestSyncAfterAddingAFile(t *testing.T) {
   959  	ctx := context.Background()
   960  	r := fstest.NewRun(t)
   961  	file1 := r.WriteBoth(ctx, "empty space", "-", t2)
   962  	file2 := r.WriteFile("potato", "------------------------------------------------------------", t3)
   963  
   964  	r.CheckLocalItems(t, file1, file2)
   965  	r.CheckRemoteItems(t, file1)
   966  
   967  	accounting.GlobalStats().ResetCounters()
   968  	ctx = predictDstFromLogger(ctx)
   969  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   970  	require.NoError(t, err)
   971  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   972  	r.CheckLocalItems(t, file1, file2)
   973  	r.CheckRemoteItems(t, file1, file2)
   974  }
   975  
   976  func TestSyncAfterChangingFilesSizeOnly(t *testing.T) {
   977  	ctx := context.Background()
   978  	r := fstest.NewRun(t)
   979  	file1 := r.WriteObject(ctx, "potato", "------------------------------------------------------------", t3)
   980  	file2 := r.WriteFile("potato", "smaller but same date", t3)
   981  	r.CheckRemoteItems(t, file1)
   982  	r.CheckLocalItems(t, file2)
   983  
   984  	accounting.GlobalStats().ResetCounters()
   985  	ctx = predictDstFromLogger(ctx)
   986  	err := Sync(ctx, r.Fremote, r.Flocal, false)
   987  	require.NoError(t, err)
   988  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
   989  	r.CheckLocalItems(t, file2)
   990  	r.CheckRemoteItems(t, file2)
   991  }
   992  
   993  // Sync after changing a file's contents, changing modtime but length
   994  // remaining the same
   995  func TestSyncAfterChangingContentsOnly(t *testing.T) {
   996  	ctx := context.Background()
   997  	r := fstest.NewRun(t)
   998  	var file1 fstest.Item
   999  	if r.Fremote.Precision() == fs.ModTimeNotSupported {
  1000  		t.Logf("ModTimeNotSupported so forcing file to be a different size")
  1001  		file1 = r.WriteObject(ctx, "potato", "different size to make sure it syncs", t3)
  1002  	} else {
  1003  		file1 = r.WriteObject(ctx, "potato", "smaller but same date", t3)
  1004  	}
  1005  	file2 := r.WriteFile("potato", "SMALLER BUT SAME DATE", t2)
  1006  	r.CheckRemoteItems(t, file1)
  1007  	r.CheckLocalItems(t, file2)
  1008  
  1009  	accounting.GlobalStats().ResetCounters()
  1010  	ctx = predictDstFromLogger(ctx)
  1011  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  1012  	require.NoError(t, err)
  1013  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1014  	r.CheckLocalItems(t, file2)
  1015  	r.CheckRemoteItems(t, file2)
  1016  }
  1017  
  1018  // Sync after removing a file and adding a file --dry-run
  1019  func TestSyncAfterRemovingAFileAndAddingAFileDryRun(t *testing.T) {
  1020  	ctx := context.Background()
  1021  	ctx, ci := fs.AddConfig(ctx)
  1022  	r := fstest.NewRun(t)
  1023  	file1 := r.WriteFile("potato2", "------------------------------------------------------------", t1)
  1024  	file2 := r.WriteObject(ctx, "potato", "SMALLER BUT SAME DATE", t2)
  1025  	file3 := r.WriteBoth(ctx, "empty space", "-", t2)
  1026  
  1027  	ci.DryRun = true
  1028  	accounting.GlobalStats().ResetCounters()
  1029  	ctx = predictDstFromLogger(ctx)
  1030  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  1031  	ci.DryRun = false
  1032  	require.NoError(t, err)
  1033  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1034  
  1035  	r.CheckLocalItems(t, file3, file1)
  1036  	r.CheckRemoteItems(t, file3, file2)
  1037  }
  1038  
  1039  // Sync after removing a file and adding a file
  1040  func testSyncAfterRemovingAFileAndAddingAFile(ctx context.Context, t *testing.T) {
  1041  	r := fstest.NewRun(t)
  1042  	file1 := r.WriteFile("potato2", "------------------------------------------------------------", t1)
  1043  	file2 := r.WriteObject(ctx, "potato", "SMALLER BUT SAME DATE", t2)
  1044  	file3 := r.WriteBoth(ctx, "empty space", "-", t2)
  1045  	r.CheckRemoteItems(t, file2, file3)
  1046  	r.CheckLocalItems(t, file1, file3)
  1047  
  1048  	accounting.GlobalStats().ResetCounters()
  1049  	ctx = predictDstFromLogger(ctx)
  1050  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  1051  	require.NoError(t, err)
  1052  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1053  	r.CheckLocalItems(t, file1, file3)
  1054  	r.CheckRemoteItems(t, file1, file3)
  1055  }
  1056  
  1057  func TestSyncAfterRemovingAFileAndAddingAFile(t *testing.T) {
  1058  	testSyncAfterRemovingAFileAndAddingAFile(context.Background(), t)
  1059  }
  1060  
  1061  // Sync after removing a file and adding a file
  1062  func testSyncAfterRemovingAFileAndAddingAFileSubDir(ctx context.Context, t *testing.T) {
  1063  	r := fstest.NewRun(t)
  1064  	file1 := r.WriteFile("a/potato2", "------------------------------------------------------------", t1)
  1065  	file2 := r.WriteObject(ctx, "b/potato", "SMALLER BUT SAME DATE", t2)
  1066  	file3 := r.WriteBoth(ctx, "c/non empty space", "AhHa!", t2)
  1067  	require.NoError(t, operations.Mkdir(ctx, r.Fremote, "d"))
  1068  	require.NoError(t, operations.Mkdir(ctx, r.Fremote, "d/e"))
  1069  
  1070  	r.CheckLocalListing(
  1071  		t,
  1072  		[]fstest.Item{
  1073  			file1,
  1074  			file3,
  1075  		},
  1076  		[]string{
  1077  			"a",
  1078  			"c",
  1079  		},
  1080  	)
  1081  	r.CheckRemoteListing(
  1082  		t,
  1083  		[]fstest.Item{
  1084  			file2,
  1085  			file3,
  1086  		},
  1087  		[]string{
  1088  			"b",
  1089  			"c",
  1090  			"d",
  1091  			"d/e",
  1092  		},
  1093  	)
  1094  
  1095  	accounting.GlobalStats().ResetCounters()
  1096  	ctx = predictDstFromLogger(ctx)
  1097  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  1098  	require.NoError(t, err)
  1099  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1100  
  1101  	r.CheckLocalListing(
  1102  		t,
  1103  		[]fstest.Item{
  1104  			file1,
  1105  			file3,
  1106  		},
  1107  		[]string{
  1108  			"a",
  1109  			"c",
  1110  		},
  1111  	)
  1112  	r.CheckRemoteListing(
  1113  		t,
  1114  		[]fstest.Item{
  1115  			file1,
  1116  			file3,
  1117  		},
  1118  		[]string{
  1119  			"a",
  1120  			"c",
  1121  		},
  1122  	)
  1123  }
  1124  
  1125  func TestSyncAfterRemovingAFileAndAddingAFileSubDir(t *testing.T) {
  1126  	testSyncAfterRemovingAFileAndAddingAFileSubDir(context.Background(), t)
  1127  }
  1128  
  1129  // Sync after removing a file and adding a file with IO Errors
  1130  func TestSyncAfterRemovingAFileAndAddingAFileSubDirWithErrors(t *testing.T) {
  1131  	ctx := context.Background()
  1132  	r := fstest.NewRun(t)
  1133  	file1 := r.WriteFile("a/potato2", "------------------------------------------------------------", t1)
  1134  	file2 := r.WriteObject(ctx, "b/potato", "SMALLER BUT SAME DATE", t2)
  1135  	file3 := r.WriteBoth(ctx, "c/non empty space", "AhHa!", t2)
  1136  	require.NoError(t, operations.Mkdir(ctx, r.Fremote, "d"))
  1137  
  1138  	r.CheckLocalListing(
  1139  		t,
  1140  		[]fstest.Item{
  1141  			file1,
  1142  			file3,
  1143  		},
  1144  		[]string{
  1145  			"a",
  1146  			"c",
  1147  		},
  1148  	)
  1149  	r.CheckRemoteListing(
  1150  		t,
  1151  		[]fstest.Item{
  1152  			file2,
  1153  			file3,
  1154  		},
  1155  		[]string{
  1156  			"b",
  1157  			"c",
  1158  			"d",
  1159  		},
  1160  	)
  1161  
  1162  	ctx = predictDstFromLogger(ctx)
  1163  	accounting.GlobalStats().ResetCounters()
  1164  	_ = fs.CountError(errors.New("boom"))
  1165  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  1166  	assert.Equal(t, fs.ErrorNotDeleting, err)
  1167  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1168  
  1169  	r.CheckLocalListing(
  1170  		t,
  1171  		[]fstest.Item{
  1172  			file1,
  1173  			file3,
  1174  		},
  1175  		[]string{
  1176  			"a",
  1177  			"c",
  1178  		},
  1179  	)
  1180  	r.CheckRemoteListing(
  1181  		t,
  1182  		[]fstest.Item{
  1183  			file1,
  1184  			file2,
  1185  			file3,
  1186  		},
  1187  		[]string{
  1188  			"a",
  1189  			"b",
  1190  			"c",
  1191  			"d",
  1192  		},
  1193  	)
  1194  }
  1195  
  1196  // Sync test delete after
  1197  func TestSyncDeleteAfter(t *testing.T) {
  1198  	ctx := context.Background()
  1199  	ci := fs.GetConfig(ctx)
  1200  	// This is the default so we've checked this already
  1201  	// check it is the default
  1202  	require.Equal(t, ci.DeleteMode, fs.DeleteModeAfter, "Didn't default to --delete-after")
  1203  }
  1204  
  1205  // Sync test delete during
  1206  func TestSyncDeleteDuring(t *testing.T) {
  1207  	ctx := context.Background()
  1208  	ctx, ci := fs.AddConfig(ctx)
  1209  	ci.DeleteMode = fs.DeleteModeDuring
  1210  
  1211  	testSyncAfterRemovingAFileAndAddingAFile(ctx, t)
  1212  }
  1213  
  1214  // Sync test delete before
  1215  func TestSyncDeleteBefore(t *testing.T) {
  1216  	ctx := context.Background()
  1217  	ctx, ci := fs.AddConfig(ctx)
  1218  	ci.DeleteMode = fs.DeleteModeBefore
  1219  
  1220  	testSyncAfterRemovingAFileAndAddingAFile(ctx, t)
  1221  }
  1222  
  1223  // Copy test delete before - shouldn't delete anything
  1224  func TestCopyDeleteBefore(t *testing.T) {
  1225  	ctx := context.Background()
  1226  	ctx, ci := fs.AddConfig(ctx)
  1227  	r := fstest.NewRun(t)
  1228  
  1229  	ci.DeleteMode = fs.DeleteModeBefore
  1230  
  1231  	file1 := r.WriteObject(ctx, "potato", "hopefully not deleted", t1)
  1232  	file2 := r.WriteFile("potato2", "hopefully copied in", t1)
  1233  	r.CheckRemoteItems(t, file1)
  1234  	r.CheckLocalItems(t, file2)
  1235  
  1236  	accounting.GlobalStats().ResetCounters()
  1237  	ctx = predictDstFromLogger(ctx)
  1238  	err := CopyDir(ctx, r.Fremote, r.Flocal, false)
  1239  	require.NoError(t, err)
  1240  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1241  
  1242  	r.CheckRemoteItems(t, file1, file2)
  1243  	r.CheckLocalItems(t, file2)
  1244  }
  1245  
  1246  // Test with exclude
  1247  func TestSyncWithExclude(t *testing.T) {
  1248  	ctx := context.Background()
  1249  	r := fstest.NewRun(t)
  1250  	file1 := r.WriteBoth(ctx, "potato2", "------------------------------------------------------------", t1)
  1251  	file2 := r.WriteBoth(ctx, "empty space", "-", t2)
  1252  	file3 := r.WriteFile("enormous", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", t1) // 100 bytes
  1253  	r.CheckRemoteItems(t, file1, file2)
  1254  	r.CheckLocalItems(t, file1, file2, file3)
  1255  
  1256  	fi, err := filter.NewFilter(nil)
  1257  	require.NoError(t, err)
  1258  	fi.Opt.MaxSize = 40
  1259  	ctx = filter.ReplaceConfig(ctx, fi)
  1260  
  1261  	accounting.GlobalStats().ResetCounters()
  1262  	ctx = predictDstFromLogger(ctx)
  1263  	err = Sync(ctx, r.Fremote, r.Flocal, false)
  1264  	require.NoError(t, err)
  1265  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1266  	r.CheckRemoteItems(t, file2, file1)
  1267  
  1268  	// Now sync the other way round and check enormous doesn't get
  1269  	// deleted as it is excluded from the sync
  1270  	accounting.GlobalStats().ResetCounters()
  1271  	ctx = predictDstFromLogger(ctx)
  1272  	err = Sync(ctx, r.Flocal, r.Fremote, false)
  1273  	require.NoError(t, err)
  1274  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1275  	r.CheckLocalItems(t, file2, file1, file3)
  1276  }
  1277  
  1278  // Test with exclude and delete excluded
  1279  func TestSyncWithExcludeAndDeleteExcluded(t *testing.T) {
  1280  	ctx := context.Background()
  1281  	r := fstest.NewRun(t)
  1282  	file1 := r.WriteBoth(ctx, "potato2", "------------------------------------------------------------", t1) // 60 bytes
  1283  	file2 := r.WriteBoth(ctx, "empty space", "-", t2)
  1284  	file3 := r.WriteBoth(ctx, "enormous", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", t1) // 100 bytes
  1285  	r.CheckRemoteItems(t, file1, file2, file3)
  1286  	r.CheckLocalItems(t, file1, file2, file3)
  1287  
  1288  	fi, err := filter.NewFilter(nil)
  1289  	require.NoError(t, err)
  1290  	fi.Opt.MaxSize = 40
  1291  	fi.Opt.DeleteExcluded = true
  1292  	ctx = filter.ReplaceConfig(ctx, fi)
  1293  
  1294  	accounting.GlobalStats().ResetCounters()
  1295  	ctx = predictDstFromLogger(ctx)
  1296  	err = Sync(ctx, r.Fremote, r.Flocal, false)
  1297  	require.NoError(t, err)
  1298  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1299  	r.CheckRemoteItems(t, file2)
  1300  
  1301  	// Check sync the other way round to make sure enormous gets
  1302  	// deleted even though it is excluded
  1303  	accounting.GlobalStats().ResetCounters()
  1304  	ctx = predictDstFromLogger(ctx)
  1305  	err = Sync(ctx, r.Flocal, r.Fremote, false)
  1306  	require.NoError(t, err)
  1307  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1308  	r.CheckLocalItems(t, file2)
  1309  }
  1310  
  1311  // Test with UpdateOlder set
  1312  func TestSyncWithUpdateOlder(t *testing.T) {
  1313  	ctx := context.Background()
  1314  	ctx, ci := fs.AddConfig(ctx)
  1315  	r := fstest.NewRun(t)
  1316  	if fs.GetModifyWindow(ctx, r.Fremote) == fs.ModTimeNotSupported {
  1317  		t.Skip("Can't run this test on fs which doesn't support mod time")
  1318  	}
  1319  	t2plus := t2.Add(time.Second / 2)
  1320  	t2minus := t2.Add(time.Second / 2)
  1321  	oneF := r.WriteFile("one", "one", t1)
  1322  	twoF := r.WriteFile("two", "two", t3)
  1323  	threeF := r.WriteFile("three", "three", t2)
  1324  	fourF := r.WriteFile("four", "four", t2)
  1325  	fiveF := r.WriteFile("five", "five", t2)
  1326  	r.CheckLocalItems(t, oneF, twoF, threeF, fourF, fiveF)
  1327  	oneO := r.WriteObject(ctx, "one", "ONE", t2)
  1328  	twoO := r.WriteObject(ctx, "two", "TWO", t2)
  1329  	threeO := r.WriteObject(ctx, "three", "THREE", t2plus)
  1330  	fourO := r.WriteObject(ctx, "four", "FOURFOUR", t2minus)
  1331  	r.CheckRemoteItems(t, oneO, twoO, threeO, fourO)
  1332  
  1333  	ci.UpdateOlder = true
  1334  	ci.ModifyWindow = fs.ModTimeNotSupported
  1335  
  1336  	ctx = predictDstFromLogger(ctx)
  1337  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  1338  	require.NoError(t, err)
  1339  	r.CheckRemoteItems(t, oneO, twoF, threeO, fourF, fiveF)
  1340  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t) // no modtime
  1341  
  1342  	if r.Fremote.Hashes().Count() == 0 {
  1343  		t.Logf("Skip test with --checksum as no hashes supported")
  1344  		return
  1345  	}
  1346  
  1347  	// now enable checksum
  1348  	ci.CheckSum = true
  1349  
  1350  	err = Sync(ctx, r.Fremote, r.Flocal, false)
  1351  	require.NoError(t, err)
  1352  	r.CheckRemoteItems(t, oneO, twoF, threeF, fourF, fiveF)
  1353  }
  1354  
  1355  // Test with a max transfer duration
  1356  func testSyncWithMaxDuration(t *testing.T, cutoffMode fs.CutoffMode) {
  1357  	ctx := context.Background()
  1358  	ctx, ci := fs.AddConfig(ctx)
  1359  	if *fstest.RemoteName != "" {
  1360  		t.Skip("Skipping test on non local remote")
  1361  	}
  1362  	r := fstest.NewRun(t)
  1363  
  1364  	maxDuration := 250 * time.Millisecond
  1365  	ci.MaxDuration = maxDuration
  1366  	ci.CutoffMode = cutoffMode
  1367  	ci.CheckFirst = true
  1368  	ci.OrderBy = "size"
  1369  	ci.Transfers = 1
  1370  	ci.Checkers = 1
  1371  	bytesPerSecond := 10 * 1024
  1372  	accounting.TokenBucket.SetBwLimit(fs.BwPair{Tx: fs.SizeSuffix(bytesPerSecond), Rx: fs.SizeSuffix(bytesPerSecond)})
  1373  	defer accounting.TokenBucket.SetBwLimit(fs.BwPair{Tx: -1, Rx: -1})
  1374  
  1375  	// write one small file which we expect to transfer and one big one which we don't
  1376  	file1 := r.WriteFile("file1", string(make([]byte, 16)), t1)
  1377  	file2 := r.WriteFile("file2", string(make([]byte, 50*1024)), t1)
  1378  	r.CheckLocalItems(t, file1, file2)
  1379  	r.CheckRemoteItems(t)
  1380  
  1381  	accounting.GlobalStats().ResetCounters()
  1382  	// ctx = predictDstFromLogger(ctx) // not currently supported (but tests do pass for CutoffModeSoft)
  1383  	startTime := time.Now()
  1384  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  1385  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1386  	require.True(t, errors.Is(err, ErrorMaxDurationReached))
  1387  
  1388  	if cutoffMode == fs.CutoffModeHard {
  1389  		r.CheckRemoteItems(t, file1)
  1390  		assert.Equal(t, int64(1), accounting.GlobalStats().GetTransfers())
  1391  	} else {
  1392  		r.CheckRemoteItems(t, file1, file2)
  1393  		assert.Equal(t, int64(2), accounting.GlobalStats().GetTransfers())
  1394  	}
  1395  
  1396  	elapsed := time.Since(startTime)
  1397  	const maxTransferTime = 20 * time.Second
  1398  
  1399  	what := fmt.Sprintf("expecting elapsed time %v between %v and %v", elapsed, maxDuration, maxTransferTime)
  1400  	assert.True(t, elapsed >= maxDuration, what)
  1401  	assert.True(t, elapsed < maxTransferTime, what)
  1402  }
  1403  
  1404  func TestSyncWithMaxDuration(t *testing.T) {
  1405  	t.Run("Hard", func(t *testing.T) {
  1406  		testSyncWithMaxDuration(t, fs.CutoffModeHard)
  1407  	})
  1408  	t.Run("Soft", func(t *testing.T) {
  1409  		testSyncWithMaxDuration(t, fs.CutoffModeSoft)
  1410  	})
  1411  }
  1412  
  1413  // Test with TrackRenames set
  1414  func TestSyncWithTrackRenames(t *testing.T) {
  1415  	ctx := context.Background()
  1416  	ctx, ci := fs.AddConfig(ctx)
  1417  	r := fstest.NewRun(t)
  1418  
  1419  	ci.TrackRenames = true
  1420  	defer func() {
  1421  		ci.TrackRenames = false
  1422  	}()
  1423  
  1424  	haveHash := r.Fremote.Hashes().Overlap(r.Flocal.Hashes()).GetOne() != hash.None
  1425  	canTrackRenames := haveHash && operations.CanServerSideMove(r.Fremote)
  1426  	t.Logf("Can track renames: %v", canTrackRenames)
  1427  
  1428  	f1 := r.WriteFile("potato", "Potato Content", t1)
  1429  	f2 := r.WriteFile("yam", "Yam Content", t2)
  1430  
  1431  	accounting.GlobalStats().ResetCounters()
  1432  	ctx = predictDstFromLogger(ctx)
  1433  	require.NoError(t, Sync(ctx, r.Fremote, r.Flocal, false))
  1434  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1435  
  1436  	r.CheckRemoteItems(t, f1, f2)
  1437  	r.CheckLocalItems(t, f1, f2)
  1438  
  1439  	// Now rename locally.
  1440  	f2 = r.RenameFile(f2, "yaml")
  1441  
  1442  	accounting.GlobalStats().ResetCounters()
  1443  	ctx = predictDstFromLogger(ctx)
  1444  	require.NoError(t, Sync(ctx, r.Fremote, r.Flocal, false))
  1445  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1446  
  1447  	r.CheckRemoteItems(t, f1, f2)
  1448  
  1449  	// Check we renamed something if we should have
  1450  	if canTrackRenames {
  1451  		renames := accounting.GlobalStats().Renames(0)
  1452  		assert.Equal(t, canTrackRenames, renames != 0, fmt.Sprintf("canTrackRenames=%v, renames=%d", canTrackRenames, renames))
  1453  	}
  1454  }
  1455  
  1456  func TestParseRenamesStrategyModtime(t *testing.T) {
  1457  	for _, test := range []struct {
  1458  		in      string
  1459  		want    trackRenamesStrategy
  1460  		wantErr bool
  1461  	}{
  1462  		{"", 0, false},
  1463  		{"modtime", trackRenamesStrategyModtime, false},
  1464  		{"hash", trackRenamesStrategyHash, false},
  1465  		{"size", 0, false},
  1466  		{"modtime,hash", trackRenamesStrategyModtime | trackRenamesStrategyHash, false},
  1467  		{"hash,modtime,size", trackRenamesStrategyModtime | trackRenamesStrategyHash, false},
  1468  		{"size,boom", 0, true},
  1469  	} {
  1470  		got, err := parseTrackRenamesStrategy(test.in)
  1471  		assert.Equal(t, test.want, got, test.in)
  1472  		assert.Equal(t, test.wantErr, err != nil, test.in)
  1473  	}
  1474  }
  1475  
  1476  func TestRenamesStrategyModtime(t *testing.T) {
  1477  	both := trackRenamesStrategyHash | trackRenamesStrategyModtime
  1478  	hash := trackRenamesStrategyHash
  1479  	modTime := trackRenamesStrategyModtime
  1480  
  1481  	assert.True(t, both.hash())
  1482  	assert.True(t, both.modTime())
  1483  	assert.True(t, hash.hash())
  1484  	assert.False(t, hash.modTime())
  1485  	assert.False(t, modTime.hash())
  1486  	assert.True(t, modTime.modTime())
  1487  }
  1488  
  1489  func TestSyncWithTrackRenamesStrategyModtime(t *testing.T) {
  1490  	ctx := context.Background()
  1491  	ctx, ci := fs.AddConfig(ctx)
  1492  	r := fstest.NewRun(t)
  1493  
  1494  	ci.TrackRenames = true
  1495  	ci.TrackRenamesStrategy = "modtime"
  1496  
  1497  	canTrackRenames := operations.CanServerSideMove(r.Fremote) && r.Fremote.Precision() != fs.ModTimeNotSupported
  1498  	t.Logf("Can track renames: %v", canTrackRenames)
  1499  
  1500  	f1 := r.WriteFile("potato", "Potato Content", t1)
  1501  	f2 := r.WriteFile("yam", "Yam Content", t2)
  1502  
  1503  	accounting.GlobalStats().ResetCounters()
  1504  	ctx = predictDstFromLogger(ctx)
  1505  	require.NoError(t, Sync(ctx, r.Fremote, r.Flocal, false))
  1506  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1507  
  1508  	r.CheckRemoteItems(t, f1, f2)
  1509  	r.CheckLocalItems(t, f1, f2)
  1510  
  1511  	// Now rename locally.
  1512  	f2 = r.RenameFile(f2, "yaml")
  1513  
  1514  	accounting.GlobalStats().ResetCounters()
  1515  	ctx = predictDstFromLogger(ctx)
  1516  	require.NoError(t, Sync(ctx, r.Fremote, r.Flocal, false))
  1517  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1518  
  1519  	r.CheckRemoteItems(t, f1, f2)
  1520  
  1521  	// Check we renamed something if we should have
  1522  	if canTrackRenames {
  1523  		renames := accounting.GlobalStats().Renames(0)
  1524  		assert.Equal(t, canTrackRenames, renames != 0, fmt.Sprintf("canTrackRenames=%v, renames=%d", canTrackRenames, renames))
  1525  	}
  1526  }
  1527  
  1528  func TestSyncWithTrackRenamesStrategyLeaf(t *testing.T) {
  1529  	ctx := context.Background()
  1530  	ctx, ci := fs.AddConfig(ctx)
  1531  	r := fstest.NewRun(t)
  1532  
  1533  	ci.TrackRenames = true
  1534  	ci.TrackRenamesStrategy = "leaf"
  1535  
  1536  	canTrackRenames := operations.CanServerSideMove(r.Fremote) && r.Fremote.Precision() != fs.ModTimeNotSupported
  1537  	t.Logf("Can track renames: %v", canTrackRenames)
  1538  
  1539  	f1 := r.WriteFile("potato", "Potato Content", t1)
  1540  	f2 := r.WriteFile("sub/yam", "Yam Content", t2)
  1541  
  1542  	accounting.GlobalStats().ResetCounters()
  1543  	ctx = predictDstFromLogger(ctx)
  1544  	require.NoError(t, Sync(ctx, r.Fremote, r.Flocal, false))
  1545  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1546  
  1547  	r.CheckRemoteItems(t, f1, f2)
  1548  	r.CheckLocalItems(t, f1, f2)
  1549  
  1550  	// Now rename locally.
  1551  	f2 = r.RenameFile(f2, "yam")
  1552  
  1553  	accounting.GlobalStats().ResetCounters()
  1554  	ctx = predictDstFromLogger(ctx)
  1555  	require.NoError(t, Sync(ctx, r.Fremote, r.Flocal, false))
  1556  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1557  
  1558  	r.CheckRemoteItems(t, f1, f2)
  1559  
  1560  	// Check we renamed something if we should have
  1561  	if canTrackRenames {
  1562  		renames := accounting.GlobalStats().Renames(0)
  1563  		assert.Equal(t, canTrackRenames, renames != 0, fmt.Sprintf("canTrackRenames=%v, renames=%d", canTrackRenames, renames))
  1564  	}
  1565  }
  1566  
  1567  func toyFileTransfers(r *fstest.Run) int64 {
  1568  	remote := r.Fremote.Name()
  1569  	transfers := 1
  1570  	if strings.HasPrefix(remote, "TestChunker") && strings.HasSuffix(remote, "S3") {
  1571  		transfers++ // Extra Copy because S3 emulates Move as Copy+Delete.
  1572  	}
  1573  	return int64(transfers)
  1574  }
  1575  
  1576  // Test a server-side move if possible, or the backup path if not
  1577  func testServerSideMove(ctx context.Context, t *testing.T, r *fstest.Run, withFilter, testDeleteEmptyDirs bool) {
  1578  	FremoteMove, _, finaliseMove, err := fstest.RandomRemote()
  1579  	require.NoError(t, err)
  1580  	defer finaliseMove()
  1581  
  1582  	file1 := r.WriteBoth(ctx, "potato2", "------------------------------------------------------------", t1)
  1583  	file2 := r.WriteBoth(ctx, "empty space", "-", t2)
  1584  	file3u := r.WriteBoth(ctx, "potato3", "------------------------------------------------------------ UPDATED", t2)
  1585  
  1586  	if testDeleteEmptyDirs {
  1587  		err := operations.Mkdir(ctx, r.Fremote, "tomatoDir")
  1588  		require.NoError(t, err)
  1589  	}
  1590  
  1591  	r.CheckRemoteItems(t, file2, file1, file3u)
  1592  
  1593  	t.Logf("Server side move (if possible) %v -> %v", r.Fremote, FremoteMove)
  1594  
  1595  	// Write just one file in the new remote
  1596  	r.WriteObjectTo(ctx, FremoteMove, "empty space", "-", t2, false)
  1597  	file3 := r.WriteObjectTo(ctx, FremoteMove, "potato3", "------------------------------------------------------------", t1, false)
  1598  	fstest.CheckItems(t, FremoteMove, file2, file3)
  1599  
  1600  	// Do server-side move
  1601  	accounting.GlobalStats().ResetCounters()
  1602  	// ctx = predictDstFromLogger(ctx) // not currently supported -- doesn't list all contents of dir.
  1603  	err = MoveDir(ctx, FremoteMove, r.Fremote, testDeleteEmptyDirs, false)
  1604  	require.NoError(t, err)
  1605  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1606  
  1607  	if withFilter {
  1608  		r.CheckRemoteItems(t, file2)
  1609  	} else {
  1610  		r.CheckRemoteItems(t)
  1611  	}
  1612  
  1613  	if testDeleteEmptyDirs {
  1614  		r.CheckRemoteListing(t, nil, []string{})
  1615  	}
  1616  
  1617  	fstest.CheckItems(t, FremoteMove, file2, file1, file3u)
  1618  
  1619  	// Create a new empty remote for stuff to be moved into
  1620  	FremoteMove2, _, finaliseMove2, err := fstest.RandomRemote()
  1621  	require.NoError(t, err)
  1622  	defer finaliseMove2()
  1623  
  1624  	if testDeleteEmptyDirs {
  1625  		err := operations.Mkdir(ctx, FremoteMove, "tomatoDir")
  1626  		require.NoError(t, err)
  1627  	}
  1628  
  1629  	// Move it back to a new empty remote, dst does not exist this time
  1630  	accounting.GlobalStats().ResetCounters()
  1631  	// ctx = predictDstFromLogger(ctx)
  1632  	err = MoveDir(ctx, FremoteMove2, FremoteMove, testDeleteEmptyDirs, false)
  1633  	require.NoError(t, err)
  1634  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1635  
  1636  	if withFilter {
  1637  		fstest.CheckItems(t, FremoteMove2, file1, file3u)
  1638  		fstest.CheckItems(t, FremoteMove, file2)
  1639  	} else {
  1640  		fstest.CheckItems(t, FremoteMove2, file2, file1, file3u)
  1641  		fstest.CheckItems(t, FremoteMove)
  1642  	}
  1643  
  1644  	if testDeleteEmptyDirs {
  1645  		fstest.CheckListingWithPrecision(t, FremoteMove, nil, []string{}, fs.GetModifyWindow(ctx, r.Fremote))
  1646  	}
  1647  }
  1648  
  1649  // Test MoveDir on Local
  1650  func TestServerSideMoveLocal(t *testing.T) {
  1651  	ctx := context.Background()
  1652  	r := fstest.NewRun(t)
  1653  	f1 := r.WriteFile("dir1/file1.txt", "hello", t1)
  1654  	f2 := r.WriteFile("dir2/file2.txt", "hello again", t2)
  1655  	r.CheckLocalItems(t, f1, f2)
  1656  
  1657  	dir1, err := fs.NewFs(ctx, r.Flocal.Root()+"/dir1")
  1658  	require.NoError(t, err)
  1659  	dir2, err := fs.NewFs(ctx, r.Flocal.Root()+"/dir2")
  1660  	require.NoError(t, err)
  1661  	err = MoveDir(ctx, dir2, dir1, false, false)
  1662  	require.NoError(t, err)
  1663  }
  1664  
  1665  // Test move
  1666  func TestMoveWithDeleteEmptySrcDirs(t *testing.T) {
  1667  	ctx := context.Background()
  1668  	r := fstest.NewRun(t)
  1669  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
  1670  	file2 := r.WriteFile("nested/sub dir/file", "nested", t1)
  1671  	r.Mkdir(ctx, r.Fremote)
  1672  
  1673  	// run move with --delete-empty-src-dirs
  1674  	ctx = predictDstFromLogger(ctx)
  1675  	err := MoveDir(ctx, r.Fremote, r.Flocal, true, false)
  1676  	require.NoError(t, err)
  1677  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1678  
  1679  	r.CheckLocalListing(
  1680  		t,
  1681  		nil,
  1682  		[]string{},
  1683  	)
  1684  	r.CheckRemoteItems(t, file1, file2)
  1685  }
  1686  
  1687  func TestMoveWithoutDeleteEmptySrcDirs(t *testing.T) {
  1688  	ctx := context.Background()
  1689  	r := fstest.NewRun(t)
  1690  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
  1691  	file2 := r.WriteFile("nested/sub dir/file", "nested", t1)
  1692  	r.Mkdir(ctx, r.Fremote)
  1693  
  1694  	ctx = predictDstFromLogger(ctx)
  1695  	err := MoveDir(ctx, r.Fremote, r.Flocal, false, false)
  1696  	require.NoError(t, err)
  1697  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1698  
  1699  	r.CheckLocalListing(
  1700  		t,
  1701  		nil,
  1702  		[]string{
  1703  			"sub dir",
  1704  			"nested",
  1705  			"nested/sub dir",
  1706  		},
  1707  	)
  1708  	r.CheckRemoteItems(t, file1, file2)
  1709  }
  1710  
  1711  func TestMoveWithIgnoreExisting(t *testing.T) {
  1712  	ctx := context.Background()
  1713  	ctx, ci := fs.AddConfig(ctx)
  1714  	r := fstest.NewRun(t)
  1715  	file1 := r.WriteFile("existing", "potato", t1)
  1716  	file2 := r.WriteFile("existing-b", "tomato", t1)
  1717  
  1718  	ci.IgnoreExisting = true
  1719  
  1720  	accounting.GlobalStats().ResetCounters()
  1721  	// ctx = predictDstFromLogger(ctx)
  1722  	err := MoveDir(ctx, r.Fremote, r.Flocal, false, false)
  1723  	require.NoError(t, err)
  1724  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1725  	r.CheckLocalListing(
  1726  		t,
  1727  		[]fstest.Item{},
  1728  		[]string{},
  1729  	)
  1730  	r.CheckRemoteListing(
  1731  		t,
  1732  		[]fstest.Item{
  1733  			file1,
  1734  			file2,
  1735  		},
  1736  		[]string{},
  1737  	)
  1738  
  1739  	// Recreate first file with modified content
  1740  	file1b := r.WriteFile("existing", "newpotatoes", t2)
  1741  	accounting.GlobalStats().ResetCounters()
  1742  	ctx = predictDstFromLogger(ctx)
  1743  	err = MoveDir(ctx, r.Fremote, r.Flocal, false, false)
  1744  	require.NoError(t, err)
  1745  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1746  	// Source items should still exist in modified state
  1747  	r.CheckLocalListing(
  1748  		t,
  1749  		[]fstest.Item{
  1750  			file1b,
  1751  		},
  1752  		[]string{},
  1753  	)
  1754  	// Dest items should not have changed
  1755  	r.CheckRemoteListing(
  1756  		t,
  1757  		[]fstest.Item{
  1758  			file1,
  1759  			file2,
  1760  		},
  1761  		[]string{},
  1762  	)
  1763  }
  1764  
  1765  // Test a server-side move if possible, or the backup path if not
  1766  func TestServerSideMove(t *testing.T) {
  1767  	ctx := context.Background()
  1768  	r := fstest.NewRun(t)
  1769  	testServerSideMove(ctx, t, r, false, false)
  1770  }
  1771  
  1772  // Test a server-side move if possible, or the backup path if not
  1773  func TestServerSideMoveWithFilter(t *testing.T) {
  1774  	ctx := context.Background()
  1775  	r := fstest.NewRun(t)
  1776  
  1777  	fi, err := filter.NewFilter(nil)
  1778  	require.NoError(t, err)
  1779  	fi.Opt.MinSize = 40
  1780  	ctx = filter.ReplaceConfig(ctx, fi)
  1781  
  1782  	testServerSideMove(ctx, t, r, true, false)
  1783  }
  1784  
  1785  // Test a server-side move if possible
  1786  func TestServerSideMoveDeleteEmptySourceDirs(t *testing.T) {
  1787  	ctx := context.Background()
  1788  	r := fstest.NewRun(t)
  1789  	testServerSideMove(ctx, t, r, false, true)
  1790  }
  1791  
  1792  // Test a server-side move with overlap
  1793  func TestServerSideMoveOverlap(t *testing.T) {
  1794  	ctx := context.Background()
  1795  	r := fstest.NewRun(t)
  1796  
  1797  	if r.Fremote.Features().DirMove != nil {
  1798  		t.Skip("Skipping test as remote supports DirMove")
  1799  	}
  1800  
  1801  	subRemoteName := r.FremoteName + "/rclone-move-test"
  1802  	FremoteMove, err := fs.NewFs(ctx, subRemoteName)
  1803  	require.NoError(t, err)
  1804  
  1805  	file1 := r.WriteObject(ctx, "potato2", "------------------------------------------------------------", t1)
  1806  	r.CheckRemoteItems(t, file1)
  1807  
  1808  	// Subdir move with no filters should return ErrorCantMoveOverlapping
  1809  	// ctx = predictDstFromLogger(ctx)
  1810  	err = MoveDir(ctx, FremoteMove, r.Fremote, false, false)
  1811  	assert.EqualError(t, err, fs.ErrorOverlapping.Error())
  1812  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1813  
  1814  	// Now try with a filter which should also fail with ErrorCantMoveOverlapping
  1815  	fi, err := filter.NewFilter(nil)
  1816  	require.NoError(t, err)
  1817  	fi.Opt.MinSize = 40
  1818  	ctx = filter.ReplaceConfig(ctx, fi)
  1819  
  1820  	// ctx = predictDstFromLogger(ctx)
  1821  	err = MoveDir(ctx, FremoteMove, r.Fremote, false, false)
  1822  	assert.EqualError(t, err, fs.ErrorOverlapping.Error())
  1823  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1824  }
  1825  
  1826  // Test a sync with overlap
  1827  func TestSyncOverlap(t *testing.T) {
  1828  	ctx := context.Background()
  1829  	r := fstest.NewRun(t)
  1830  
  1831  	subRemoteName := r.FremoteName + "/rclone-sync-test"
  1832  	FremoteSync, err := fs.NewFs(ctx, subRemoteName)
  1833  	require.NoError(t, err)
  1834  
  1835  	checkErr := func(err error) {
  1836  		require.Error(t, err)
  1837  		assert.True(t, fserrors.IsFatalError(err))
  1838  		assert.Equal(t, fs.ErrorOverlapping.Error(), err.Error())
  1839  	}
  1840  
  1841  	ctx = predictDstFromLogger(ctx)
  1842  	checkErr(Sync(ctx, FremoteSync, r.Fremote, false))
  1843  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1844  	ctx = predictDstFromLogger(ctx)
  1845  	checkErr(Sync(ctx, r.Fremote, FremoteSync, false))
  1846  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1847  	ctx = predictDstFromLogger(ctx)
  1848  	checkErr(Sync(ctx, r.Fremote, r.Fremote, false))
  1849  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1850  	ctx = predictDstFromLogger(ctx)
  1851  	checkErr(Sync(ctx, FremoteSync, FremoteSync, false))
  1852  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  1853  }
  1854  
  1855  // Test a sync with filtered overlap
  1856  func TestSyncOverlapWithFilter(t *testing.T) {
  1857  	ctx := context.Background()
  1858  	r := fstest.NewRun(t)
  1859  
  1860  	fi, err := filter.NewFilter(nil)
  1861  	require.NoError(t, err)
  1862  	require.NoError(t, fi.Add(false, "/rclone-sync-test/"))
  1863  	require.NoError(t, fi.Add(false, "*/layer2/"))
  1864  	fi.Opt.ExcludeFile = []string{".ignore"}
  1865  	filterCtx := filter.ReplaceConfig(ctx, fi)
  1866  
  1867  	subRemoteName := r.FremoteName + "/rclone-sync-test"
  1868  	FremoteSync, err := fs.NewFs(ctx, subRemoteName)
  1869  	require.NoError(t, FremoteSync.Mkdir(ctx, ""))
  1870  	require.NoError(t, err)
  1871  
  1872  	subRemoteName2 := r.FremoteName + "/rclone-sync-test-include/layer2"
  1873  	FremoteSync2, err := fs.NewFs(ctx, subRemoteName2)
  1874  	require.NoError(t, FremoteSync2.Mkdir(ctx, ""))
  1875  	require.NoError(t, err)
  1876  
  1877  	subRemoteName3 := r.FremoteName + "/rclone-sync-test-ignore-file"
  1878  	FremoteSync3, err := fs.NewFs(ctx, subRemoteName3)
  1879  	require.NoError(t, FremoteSync3.Mkdir(ctx, ""))
  1880  	require.NoError(t, err)
  1881  	r.WriteObject(context.Background(), "rclone-sync-test-ignore-file/.ignore", "-", t1)
  1882  
  1883  	checkErr := func(err error) {
  1884  		require.Error(t, err)
  1885  		assert.True(t, fserrors.IsFatalError(err))
  1886  		assert.Equal(t, fs.ErrorOverlapping.Error(), err.Error())
  1887  		accounting.GlobalStats().ResetCounters()
  1888  	}
  1889  
  1890  	checkNoErr := func(err error) {
  1891  		require.NoError(t, err)
  1892  	}
  1893  
  1894  	accounting.GlobalStats().ResetCounters()
  1895  	filterCtx = predictDstFromLogger(filterCtx)
  1896  	checkNoErr(Sync(filterCtx, FremoteSync, r.Fremote, false))
  1897  	checkErr(Sync(ctx, FremoteSync, r.Fremote, false))
  1898  	checkNoErr(Sync(filterCtx, r.Fremote, FremoteSync, false))
  1899  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1900  	filterCtx = predictDstFromLogger(filterCtx)
  1901  	checkErr(Sync(ctx, r.Fremote, FremoteSync, false))
  1902  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1903  	filterCtx = predictDstFromLogger(filterCtx)
  1904  	checkErr(Sync(filterCtx, r.Fremote, r.Fremote, false))
  1905  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1906  	filterCtx = predictDstFromLogger(filterCtx)
  1907  	checkErr(Sync(ctx, r.Fremote, r.Fremote, false))
  1908  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1909  	filterCtx = predictDstFromLogger(filterCtx)
  1910  	checkErr(Sync(filterCtx, FremoteSync, FremoteSync, false))
  1911  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1912  	filterCtx = predictDstFromLogger(filterCtx)
  1913  	checkErr(Sync(ctx, FremoteSync, FremoteSync, false))
  1914  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1915  	filterCtx = predictDstFromLogger(filterCtx)
  1916  
  1917  	checkNoErr(Sync(filterCtx, FremoteSync2, r.Fremote, false))
  1918  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1919  	filterCtx = predictDstFromLogger(filterCtx)
  1920  	checkErr(Sync(ctx, FremoteSync2, r.Fremote, false))
  1921  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1922  	filterCtx = predictDstFromLogger(filterCtx)
  1923  	checkNoErr(Sync(filterCtx, r.Fremote, FremoteSync2, false))
  1924  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1925  	filterCtx = predictDstFromLogger(filterCtx)
  1926  	checkErr(Sync(ctx, r.Fremote, FremoteSync2, false))
  1927  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1928  	filterCtx = predictDstFromLogger(filterCtx)
  1929  	checkErr(Sync(filterCtx, FremoteSync2, FremoteSync2, false))
  1930  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1931  	filterCtx = predictDstFromLogger(filterCtx)
  1932  	checkErr(Sync(ctx, FremoteSync2, FremoteSync2, false))
  1933  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1934  	filterCtx = predictDstFromLogger(filterCtx)
  1935  
  1936  	checkNoErr(Sync(filterCtx, FremoteSync3, r.Fremote, false))
  1937  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1938  	filterCtx = predictDstFromLogger(filterCtx)
  1939  	checkErr(Sync(ctx, FremoteSync3, r.Fremote, false))
  1940  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1941  	filterCtx = predictDstFromLogger(filterCtx)
  1942  	// Destination is excluded so this test makes no sense
  1943  	// checkNoErr(Sync(filterCtx, r.Fremote, FremoteSync3, false))
  1944  	checkErr(Sync(ctx, r.Fremote, FremoteSync3, false))
  1945  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1946  	filterCtx = predictDstFromLogger(filterCtx)
  1947  	checkErr(Sync(filterCtx, FremoteSync3, FremoteSync3, false))
  1948  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1949  	filterCtx = predictDstFromLogger(filterCtx)
  1950  	checkErr(Sync(ctx, FremoteSync3, FremoteSync3, false))
  1951  	testLoggerVsLsf(filterCtx, r.Fremote, operations.GetLoggerOpt(filterCtx).JSON, t)
  1952  }
  1953  
  1954  // Test with CompareDest set
  1955  func TestSyncCompareDest(t *testing.T) {
  1956  	ctx := context.Background()
  1957  	ctx, ci := fs.AddConfig(ctx)
  1958  	r := fstest.NewRun(t)
  1959  
  1960  	ci.CompareDest = []string{r.FremoteName + "/CompareDest"}
  1961  
  1962  	fdst, err := fs.NewFs(ctx, r.FremoteName+"/dst")
  1963  	require.NoError(t, err)
  1964  
  1965  	// check empty dest, empty compare
  1966  	file1 := r.WriteFile("one", "one", t1)
  1967  	r.CheckLocalItems(t, file1)
  1968  
  1969  	accounting.GlobalStats().ResetCounters()
  1970  	// ctx = predictDstFromLogger(ctx) // not currently supported due to duplicate equal() checks
  1971  	err = Sync(ctx, fdst, r.Flocal, false)
  1972  	// testLoggerVsLsf(ctx, fdst, operations.GetLoggerOpt(ctx).JSON, t)
  1973  	require.NoError(t, err)
  1974  
  1975  	file1dst := file1
  1976  	file1dst.Path = "dst/one"
  1977  
  1978  	r.CheckRemoteItems(t, file1dst)
  1979  
  1980  	// check old dest, empty compare
  1981  	file1b := r.WriteFile("one", "onet2", t2)
  1982  	r.CheckRemoteItems(t, file1dst)
  1983  	r.CheckLocalItems(t, file1b)
  1984  
  1985  	accounting.GlobalStats().ResetCounters()
  1986  	// ctx = predictDstFromLogger(ctx)
  1987  	err = Sync(ctx, fdst, r.Flocal, false)
  1988  	// testLoggerVsLsf(ctx, fdst, operations.GetLoggerOpt(ctx).JSON, t)
  1989  	require.NoError(t, err)
  1990  
  1991  	file1bdst := file1b
  1992  	file1bdst.Path = "dst/one"
  1993  
  1994  	r.CheckRemoteItems(t, file1bdst)
  1995  
  1996  	// check old dest, new compare
  1997  	file3 := r.WriteObject(ctx, "dst/one", "one", t1)
  1998  	file2 := r.WriteObject(ctx, "CompareDest/one", "onet2", t2)
  1999  	file1c := r.WriteFile("one", "onet2", t2)
  2000  	r.CheckRemoteItems(t, file2, file3)
  2001  	r.CheckLocalItems(t, file1c)
  2002  
  2003  	accounting.GlobalStats().ResetCounters()
  2004  	// ctx = predictDstFromLogger(ctx)
  2005  	err = Sync(ctx, fdst, r.Flocal, false)
  2006  	// testLoggerVsLsf(ctx, fdst, operations.GetLoggerOpt(ctx).JSON, t)
  2007  	require.NoError(t, err)
  2008  
  2009  	r.CheckRemoteItems(t, file2, file3)
  2010  
  2011  	// check empty dest, new compare
  2012  	file4 := r.WriteObject(ctx, "CompareDest/two", "two", t2)
  2013  	file5 := r.WriteFile("two", "two", t2)
  2014  	r.CheckRemoteItems(t, file2, file3, file4)
  2015  	r.CheckLocalItems(t, file1c, file5)
  2016  
  2017  	accounting.GlobalStats().ResetCounters()
  2018  	// ctx = predictDstFromLogger(ctx)
  2019  	err = Sync(ctx, fdst, r.Flocal, false)
  2020  	// testLoggerVsLsf(ctx, fdst, operations.GetLoggerOpt(ctx).JSON, t)
  2021  	require.NoError(t, err)
  2022  
  2023  	r.CheckRemoteItems(t, file2, file3, file4)
  2024  
  2025  	// check new dest, new compare
  2026  	accounting.GlobalStats().ResetCounters()
  2027  	// ctx = predictDstFromLogger(ctx)
  2028  	err = Sync(ctx, fdst, r.Flocal, false)
  2029  	// testLoggerVsLsf(ctx, fdst, operations.GetLoggerOpt(ctx).JSON, t)
  2030  	require.NoError(t, err)
  2031  
  2032  	r.CheckRemoteItems(t, file2, file3, file4)
  2033  
  2034  	// Work out if we actually have hashes for uploaded files
  2035  	haveHash := false
  2036  	if ht := fdst.Hashes().GetOne(); ht != hash.None {
  2037  		file2obj, err := fdst.NewObject(ctx, "one")
  2038  		if err == nil {
  2039  			file2objHash, err := file2obj.Hash(ctx, ht)
  2040  			if err == nil {
  2041  				haveHash = file2objHash != ""
  2042  			}
  2043  		}
  2044  	}
  2045  
  2046  	// check new dest, new compare, src timestamp differs
  2047  	//
  2048  	// we only check this if we the file we uploaded previously
  2049  	// actually has a hash otherwise the differing timestamp is
  2050  	// always copied.
  2051  	if haveHash {
  2052  		file5b := r.WriteFile("two", "two", t3)
  2053  		r.CheckLocalItems(t, file1c, file5b)
  2054  
  2055  		accounting.GlobalStats().ResetCounters()
  2056  		// ctx = predictDstFromLogger(ctx)
  2057  		err = Sync(ctx, fdst, r.Flocal, false)
  2058  		// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2059  		require.NoError(t, err)
  2060  
  2061  		r.CheckRemoteItems(t, file2, file3, file4)
  2062  	} else {
  2063  		t.Log("No hash on uploaded file so skipping compare timestamp test")
  2064  	}
  2065  
  2066  	// check empty dest, old compare
  2067  	file5c := r.WriteFile("two", "twot3", t3)
  2068  	r.CheckRemoteItems(t, file2, file3, file4)
  2069  	r.CheckLocalItems(t, file1c, file5c)
  2070  
  2071  	accounting.GlobalStats().ResetCounters()
  2072  	// ctx = predictDstFromLogger(ctx)
  2073  	err = Sync(ctx, fdst, r.Flocal, false)
  2074  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2075  	require.NoError(t, err)
  2076  
  2077  	file5cdst := file5c
  2078  	file5cdst.Path = "dst/two"
  2079  
  2080  	r.CheckRemoteItems(t, file2, file3, file4, file5cdst)
  2081  }
  2082  
  2083  // Test with multiple CompareDest
  2084  func TestSyncMultipleCompareDest(t *testing.T) {
  2085  	ctx := context.Background()
  2086  	ctx, ci := fs.AddConfig(ctx)
  2087  	r := fstest.NewRun(t)
  2088  	precision := fs.GetModifyWindow(ctx, r.Fremote, r.Flocal)
  2089  
  2090  	ci.CompareDest = []string{r.FremoteName + "/pre-dest1", r.FremoteName + "/pre-dest2"}
  2091  
  2092  	// check empty dest, new compare
  2093  	fsrc1 := r.WriteFile("1", "1", t1)
  2094  	fsrc2 := r.WriteFile("2", "2", t1)
  2095  	fsrc3 := r.WriteFile("3", "3", t1)
  2096  	r.CheckLocalItems(t, fsrc1, fsrc2, fsrc3)
  2097  
  2098  	fdest1 := r.WriteObject(ctx, "pre-dest1/1", "1", t1)
  2099  	fdest2 := r.WriteObject(ctx, "pre-dest2/2", "2", t1)
  2100  	r.CheckRemoteItems(t, fdest1, fdest2)
  2101  
  2102  	accounting.GlobalStats().ResetCounters()
  2103  	fdst, err := fs.NewFs(ctx, r.FremoteName+"/dest")
  2104  	require.NoError(t, err)
  2105  	// ctx = predictDstFromLogger(ctx)
  2106  	require.NoError(t, Sync(ctx, fdst, r.Flocal, false))
  2107  	// testLoggerVsLsf(ctx, fdst, operations.GetLoggerOpt(ctx).JSON, t)
  2108  
  2109  	fdest3 := fsrc3
  2110  	fdest3.Path = "dest/3"
  2111  
  2112  	fstest.CheckItemsWithPrecision(t, fdst, precision, fsrc3)
  2113  	r.CheckRemoteItems(t, fdest1, fdest2, fdest3)
  2114  }
  2115  
  2116  // Test with CopyDest set
  2117  func TestSyncCopyDest(t *testing.T) {
  2118  	ctx := context.Background()
  2119  	ctx, ci := fs.AddConfig(ctx)
  2120  	r := fstest.NewRun(t)
  2121  
  2122  	if r.Fremote.Features().Copy == nil {
  2123  		t.Skip("Skipping test as remote does not support server-side copy")
  2124  	}
  2125  
  2126  	ci.CopyDest = []string{r.FremoteName + "/CopyDest"}
  2127  
  2128  	fdst, err := fs.NewFs(ctx, r.FremoteName+"/dst")
  2129  	require.NoError(t, err)
  2130  
  2131  	// check empty dest, empty copy
  2132  	file1 := r.WriteFile("one", "one", t1)
  2133  	r.CheckLocalItems(t, file1)
  2134  
  2135  	accounting.GlobalStats().ResetCounters()
  2136  	// ctx = predictDstFromLogger(ctx)
  2137  	err = Sync(ctx, fdst, r.Flocal, false)
  2138  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t) // not currently supported
  2139  	require.NoError(t, err)
  2140  
  2141  	file1dst := file1
  2142  	file1dst.Path = "dst/one"
  2143  
  2144  	r.CheckRemoteItems(t, file1dst)
  2145  
  2146  	// check old dest, empty copy
  2147  	file1b := r.WriteFile("one", "onet2", t2)
  2148  	r.CheckRemoteItems(t, file1dst)
  2149  	r.CheckLocalItems(t, file1b)
  2150  
  2151  	accounting.GlobalStats().ResetCounters()
  2152  	// ctx = predictDstFromLogger(ctx)
  2153  	err = Sync(ctx, fdst, r.Flocal, false)
  2154  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2155  	require.NoError(t, err)
  2156  
  2157  	file1bdst := file1b
  2158  	file1bdst.Path = "dst/one"
  2159  
  2160  	r.CheckRemoteItems(t, file1bdst)
  2161  
  2162  	// check old dest, new copy, backup-dir
  2163  
  2164  	ci.BackupDir = r.FremoteName + "/BackupDir"
  2165  
  2166  	file3 := r.WriteObject(ctx, "dst/one", "one", t1)
  2167  	file2 := r.WriteObject(ctx, "CopyDest/one", "onet2", t2)
  2168  	file1c := r.WriteFile("one", "onet2", t2)
  2169  	r.CheckRemoteItems(t, file2, file3)
  2170  	r.CheckLocalItems(t, file1c)
  2171  
  2172  	accounting.GlobalStats().ResetCounters()
  2173  	// ctx = predictDstFromLogger(ctx)
  2174  	err = Sync(ctx, fdst, r.Flocal, false)
  2175  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2176  	require.NoError(t, err)
  2177  
  2178  	file2dst := file2
  2179  	file2dst.Path = "dst/one"
  2180  	file3.Path = "BackupDir/one"
  2181  
  2182  	r.CheckRemoteItems(t, file2, file2dst, file3)
  2183  	ci.BackupDir = ""
  2184  
  2185  	// check empty dest, new copy
  2186  	file4 := r.WriteObject(ctx, "CopyDest/two", "two", t2)
  2187  	file5 := r.WriteFile("two", "two", t2)
  2188  	r.CheckRemoteItems(t, file2, file2dst, file3, file4)
  2189  	r.CheckLocalItems(t, file1c, file5)
  2190  
  2191  	accounting.GlobalStats().ResetCounters()
  2192  	// ctx = predictDstFromLogger(ctx)
  2193  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2194  	err = Sync(ctx, fdst, r.Flocal, false)
  2195  	require.NoError(t, err)
  2196  
  2197  	file4dst := file4
  2198  	file4dst.Path = "dst/two"
  2199  
  2200  	r.CheckRemoteItems(t, file2, file2dst, file3, file4, file4dst)
  2201  
  2202  	// check new dest, new copy
  2203  	accounting.GlobalStats().ResetCounters()
  2204  	// ctx = predictDstFromLogger(ctx)
  2205  	err = Sync(ctx, fdst, r.Flocal, false)
  2206  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2207  	require.NoError(t, err)
  2208  
  2209  	r.CheckRemoteItems(t, file2, file2dst, file3, file4, file4dst)
  2210  
  2211  	// check empty dest, old copy
  2212  	file6 := r.WriteObject(ctx, "CopyDest/three", "three", t2)
  2213  	file7 := r.WriteFile("three", "threet3", t3)
  2214  	r.CheckRemoteItems(t, file2, file2dst, file3, file4, file4dst, file6)
  2215  	r.CheckLocalItems(t, file1c, file5, file7)
  2216  
  2217  	accounting.GlobalStats().ResetCounters()
  2218  	// ctx = predictDstFromLogger(ctx)
  2219  	err = Sync(ctx, fdst, r.Flocal, false)
  2220  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2221  	require.NoError(t, err)
  2222  
  2223  	file7dst := file7
  2224  	file7dst.Path = "dst/three"
  2225  
  2226  	r.CheckRemoteItems(t, file2, file2dst, file3, file4, file4dst, file6, file7dst)
  2227  }
  2228  
  2229  // Test with BackupDir set
  2230  func testSyncBackupDir(t *testing.T, backupDir string, suffix string, suffixKeepExtension bool) {
  2231  	ctx := context.Background()
  2232  	ctx, ci := fs.AddConfig(ctx)
  2233  	r := fstest.NewRun(t)
  2234  
  2235  	if !operations.CanServerSideMove(r.Fremote) {
  2236  		t.Skip("Skipping test as remote does not support server-side move")
  2237  	}
  2238  	r.Mkdir(ctx, r.Fremote)
  2239  
  2240  	if backupDir != "" {
  2241  		ci.BackupDir = r.FremoteName + "/" + backupDir
  2242  		backupDir += "/"
  2243  	} else {
  2244  		ci.BackupDir = ""
  2245  		backupDir = "dst/"
  2246  		// Exclude the suffix from the sync otherwise the sync
  2247  		// deletes the old backup files
  2248  		flt, err := filter.NewFilter(nil)
  2249  		require.NoError(t, err)
  2250  		require.NoError(t, flt.AddRule("- *"+suffix))
  2251  		// Change the active filter
  2252  		ctx = filter.ReplaceConfig(ctx, flt)
  2253  	}
  2254  	ci.Suffix = suffix
  2255  	ci.SuffixKeepExtension = suffixKeepExtension
  2256  
  2257  	// Make the setup so we have one, two, three in the dest
  2258  	// and one (different), two (same) in the source
  2259  	file1 := r.WriteObject(ctx, "dst/one", "one", t1)
  2260  	file2 := r.WriteObject(ctx, "dst/two", "two", t1)
  2261  	file3 := r.WriteObject(ctx, "dst/three.txt", "three", t1)
  2262  	file2a := r.WriteFile("two", "two", t1)
  2263  	file1a := r.WriteFile("one", "oneA", t2)
  2264  
  2265  	r.CheckRemoteItems(t, file1, file2, file3)
  2266  	r.CheckLocalItems(t, file1a, file2a)
  2267  
  2268  	fdst, err := fs.NewFs(ctx, r.FremoteName+"/dst")
  2269  	require.NoError(t, err)
  2270  
  2271  	accounting.GlobalStats().ResetCounters()
  2272  	err = Sync(ctx, fdst, r.Flocal, false)
  2273  	require.NoError(t, err)
  2274  
  2275  	// one should be moved to the backup dir and the new one installed
  2276  	file1.Path = backupDir + "one" + suffix
  2277  	file1a.Path = "dst/one"
  2278  	// two should be unchanged
  2279  	// three should be moved to the backup dir
  2280  	if suffixKeepExtension {
  2281  		file3.Path = backupDir + "three" + suffix + ".txt"
  2282  	} else {
  2283  		file3.Path = backupDir + "three.txt" + suffix
  2284  	}
  2285  
  2286  	r.CheckRemoteItems(t, file1, file2, file3, file1a)
  2287  
  2288  	// Now check what happens if we do it again
  2289  	// Restore a different three and update one in the source
  2290  	file3a := r.WriteObject(ctx, "dst/three.txt", "threeA", t2)
  2291  	file1b := r.WriteFile("one", "oneBB", t3)
  2292  	r.CheckRemoteItems(t, file1, file2, file3, file1a, file3a)
  2293  
  2294  	// This should delete three and overwrite one again, checking
  2295  	// the files got overwritten correctly in backup-dir
  2296  	accounting.GlobalStats().ResetCounters()
  2297  	err = Sync(ctx, fdst, r.Flocal, false)
  2298  	require.NoError(t, err)
  2299  
  2300  	// one should be moved to the backup dir and the new one installed
  2301  	file1a.Path = backupDir + "one" + suffix
  2302  	file1b.Path = "dst/one"
  2303  	// two should be unchanged
  2304  	// three should be moved to the backup dir
  2305  	if suffixKeepExtension {
  2306  		file3a.Path = backupDir + "three" + suffix + ".txt"
  2307  	} else {
  2308  		file3a.Path = backupDir + "three.txt" + suffix
  2309  	}
  2310  
  2311  	r.CheckRemoteItems(t, file1b, file2, file3a, file1a)
  2312  }
  2313  func TestSyncBackupDir(t *testing.T) {
  2314  	testSyncBackupDir(t, "backup", "", false)
  2315  }
  2316  func TestSyncBackupDirWithSuffix(t *testing.T) {
  2317  	testSyncBackupDir(t, "backup", ".bak", false)
  2318  }
  2319  func TestSyncBackupDirWithSuffixKeepExtension(t *testing.T) {
  2320  	testSyncBackupDir(t, "backup", "-2019-01-01", true)
  2321  }
  2322  func TestSyncBackupDirSuffixOnly(t *testing.T) {
  2323  	testSyncBackupDir(t, "", ".bak", false)
  2324  }
  2325  
  2326  // Test with Suffix set
  2327  func testSyncSuffix(t *testing.T, suffix string, suffixKeepExtension bool) {
  2328  	ctx := context.Background()
  2329  	ctx, ci := fs.AddConfig(ctx)
  2330  	r := fstest.NewRun(t)
  2331  
  2332  	if !operations.CanServerSideMove(r.Fremote) {
  2333  		t.Skip("Skipping test as remote does not support server-side move")
  2334  	}
  2335  	r.Mkdir(ctx, r.Fremote)
  2336  
  2337  	ci.Suffix = suffix
  2338  	ci.SuffixKeepExtension = suffixKeepExtension
  2339  
  2340  	// Make the setup so we have one, two, three in the dest
  2341  	// and one (different), two (same) in the source
  2342  	file1 := r.WriteObject(ctx, "dst/one", "one", t1)
  2343  	file2 := r.WriteObject(ctx, "dst/two", "two", t1)
  2344  	file3 := r.WriteObject(ctx, "dst/three.txt", "three", t1)
  2345  	file2a := r.WriteFile("two", "two", t1)
  2346  	file1a := r.WriteFile("one", "oneA", t2)
  2347  	file3a := r.WriteFile("three.txt", "threeA", t1)
  2348  
  2349  	r.CheckRemoteItems(t, file1, file2, file3)
  2350  	r.CheckLocalItems(t, file1a, file2a, file3a)
  2351  
  2352  	fdst, err := fs.NewFs(ctx, r.FremoteName+"/dst")
  2353  	require.NoError(t, err)
  2354  
  2355  	accounting.GlobalStats().ResetCounters()
  2356  	err = operations.CopyFile(ctx, fdst, r.Flocal, "one", "one")
  2357  	require.NoError(t, err)
  2358  	err = operations.CopyFile(ctx, fdst, r.Flocal, "two", "two")
  2359  	require.NoError(t, err)
  2360  	err = operations.CopyFile(ctx, fdst, r.Flocal, "three.txt", "three.txt")
  2361  	require.NoError(t, err)
  2362  
  2363  	// one should be moved to the backup dir and the new one installed
  2364  	file1.Path = "dst/one" + suffix
  2365  	file1a.Path = "dst/one"
  2366  	// two should be unchanged
  2367  	// three should be moved to the backup dir
  2368  	if suffixKeepExtension {
  2369  		file3.Path = "dst/three" + suffix + ".txt"
  2370  	} else {
  2371  		file3.Path = "dst/three.txt" + suffix
  2372  	}
  2373  	file3a.Path = "dst/three.txt"
  2374  
  2375  	r.CheckRemoteItems(t, file1, file2, file3, file1a, file3a)
  2376  
  2377  	// Now check what happens if we do it again
  2378  	// Restore a different three and update one in the source
  2379  	file3b := r.WriteFile("three.txt", "threeBDifferentSize", t3)
  2380  	file1b := r.WriteFile("one", "oneBB", t3)
  2381  	r.CheckRemoteItems(t, file1, file2, file3, file1a, file3a)
  2382  
  2383  	// This should delete three and overwrite one again, checking
  2384  	// the files got overwritten correctly in backup-dir
  2385  	accounting.GlobalStats().ResetCounters()
  2386  	err = operations.CopyFile(ctx, fdst, r.Flocal, "one", "one")
  2387  	require.NoError(t, err)
  2388  	err = operations.CopyFile(ctx, fdst, r.Flocal, "two", "two")
  2389  	require.NoError(t, err)
  2390  	err = operations.CopyFile(ctx, fdst, r.Flocal, "three.txt", "three.txt")
  2391  	require.NoError(t, err)
  2392  
  2393  	// one should be moved to the backup dir and the new one installed
  2394  	file1a.Path = "dst/one" + suffix
  2395  	file1b.Path = "dst/one"
  2396  	// two should be unchanged
  2397  	// three should be moved to the backup dir
  2398  	if suffixKeepExtension {
  2399  		file3a.Path = "dst/three" + suffix + ".txt"
  2400  	} else {
  2401  		file3a.Path = "dst/three.txt" + suffix
  2402  	}
  2403  	file3b.Path = "dst/three.txt"
  2404  
  2405  	r.CheckRemoteItems(t, file1b, file3b, file2, file3a, file1a)
  2406  }
  2407  func TestSyncSuffix(t *testing.T)              { testSyncSuffix(t, ".bak", false) }
  2408  func TestSyncSuffixKeepExtension(t *testing.T) { testSyncSuffix(t, "-2019-01-01", true) }
  2409  
  2410  // Check we can sync two files with differing UTF-8 representations
  2411  func TestSyncUTFNorm(t *testing.T) {
  2412  	ctx := context.Background()
  2413  	if runtime.GOOS == "darwin" {
  2414  		t.Skip("Can't test UTF normalization on OS X")
  2415  	}
  2416  
  2417  	r := fstest.NewRun(t)
  2418  
  2419  	// Two strings with different unicode normalization (from OS X)
  2420  	Encoding1 := "Testêé"
  2421  	Encoding2 := "Testêé"
  2422  	assert.NotEqual(t, Encoding1, Encoding2)
  2423  	assert.Equal(t, norm.NFC.String(Encoding1), norm.NFC.String(Encoding2))
  2424  
  2425  	file1 := r.WriteFile(Encoding1, "This is a test", t1)
  2426  	r.CheckLocalItems(t, file1)
  2427  
  2428  	file2 := r.WriteObject(ctx, Encoding2, "This is a old test", t2)
  2429  	r.CheckRemoteItems(t, file2)
  2430  
  2431  	accounting.GlobalStats().ResetCounters()
  2432  	// ctx = predictDstFromLogger(ctx)
  2433  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  2434  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t) // can't test this on macOS
  2435  	require.NoError(t, err)
  2436  
  2437  	// We should have transferred exactly one file, but kept the
  2438  	// normalized state of the file.
  2439  	assert.Equal(t, toyFileTransfers(r), accounting.GlobalStats().GetTransfers())
  2440  	r.CheckLocalItems(t, file1)
  2441  	file1.Path = file2.Path
  2442  	r.CheckRemoteItems(t, file1)
  2443  }
  2444  
  2445  // Test --immutable
  2446  func TestSyncImmutable(t *testing.T) {
  2447  	ctx := context.Background()
  2448  	ctx, ci := fs.AddConfig(ctx)
  2449  	r := fstest.NewRun(t)
  2450  
  2451  	ci.Immutable = true
  2452  
  2453  	// Create file on source
  2454  	file1 := r.WriteFile("existing", "potato", t1)
  2455  	r.CheckLocalItems(t, file1)
  2456  	r.CheckRemoteItems(t)
  2457  
  2458  	// Should succeed
  2459  	accounting.GlobalStats().ResetCounters()
  2460  	ctx = predictDstFromLogger(ctx)
  2461  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  2462  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2463  	require.NoError(t, err)
  2464  	r.CheckLocalItems(t, file1)
  2465  	r.CheckRemoteItems(t, file1)
  2466  
  2467  	// Modify file data and timestamp on source
  2468  	file2 := r.WriteFile("existing", "tomatoes", t2)
  2469  	r.CheckLocalItems(t, file2)
  2470  	r.CheckRemoteItems(t, file1)
  2471  
  2472  	// Should fail with ErrorImmutableModified and not modify local or remote files
  2473  	accounting.GlobalStats().ResetCounters()
  2474  	ctx = predictDstFromLogger(ctx)
  2475  	err = Sync(ctx, r.Fremote, r.Flocal, false)
  2476  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2477  	assert.EqualError(t, err, fs.ErrorImmutableModified.Error())
  2478  	r.CheckLocalItems(t, file2)
  2479  	r.CheckRemoteItems(t, file1)
  2480  }
  2481  
  2482  // Test --ignore-case-sync
  2483  func TestSyncIgnoreCase(t *testing.T) {
  2484  	ctx := context.Background()
  2485  	ctx, ci := fs.AddConfig(ctx)
  2486  	r := fstest.NewRun(t)
  2487  
  2488  	// Only test if filesystems are case sensitive
  2489  	if r.Fremote.Features().CaseInsensitive || r.Flocal.Features().CaseInsensitive {
  2490  		t.Skip("Skipping test as local or remote are case-insensitive")
  2491  	}
  2492  
  2493  	ci.IgnoreCaseSync = true
  2494  
  2495  	// Create files with different filename casing
  2496  	file1 := r.WriteFile("existing", "potato", t1)
  2497  	r.CheckLocalItems(t, file1)
  2498  	file2 := r.WriteObject(ctx, "EXISTING", "potato", t1)
  2499  	r.CheckRemoteItems(t, file2)
  2500  
  2501  	// Should not copy files that are differently-cased but otherwise identical
  2502  	accounting.GlobalStats().ResetCounters()
  2503  	// ctx = predictDstFromLogger(ctx)
  2504  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  2505  	// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t) // can't test this on macOS
  2506  	require.NoError(t, err)
  2507  	r.CheckLocalItems(t, file1)
  2508  	r.CheckRemoteItems(t, file2)
  2509  }
  2510  
  2511  // Test --fix-case
  2512  func TestFixCase(t *testing.T) {
  2513  	ctx := context.Background()
  2514  	ctx, ci := fs.AddConfig(ctx)
  2515  	r := fstest.NewRun(t)
  2516  
  2517  	// Only test if remote is case insensitive
  2518  	if !r.Fremote.Features().CaseInsensitive {
  2519  		t.Skip("Skipping test as local or remote are case-sensitive")
  2520  	}
  2521  
  2522  	ci.FixCase = true
  2523  
  2524  	// Create files with different filename casing
  2525  	file1a := r.WriteFile("existing", "potato", t1)
  2526  	file1b := r.WriteFile("existingbutdifferent", "donut", t1)
  2527  	file1c := r.WriteFile("subdira/subdirb/subdirc/hello", "donut", t1)
  2528  	file1d := r.WriteFile("subdira/subdirb/subdirc/subdird/filewithoutcasedifferences", "donut", t1)
  2529  	r.CheckLocalItems(t, file1a, file1b, file1c, file1d)
  2530  	file2a := r.WriteObject(ctx, "EXISTING", "potato", t1)
  2531  	file2b := r.WriteObject(ctx, "EXISTINGBUTDIFFERENT", "lemonade", t1)
  2532  	file2c := r.WriteObject(ctx, "SUBDIRA/subdirb/SUBDIRC/HELLO", "lemonade", t1)
  2533  	file2d := r.WriteObject(ctx, "SUBDIRA/subdirb/SUBDIRC/subdird/filewithoutcasedifferences", "lemonade", t1)
  2534  	r.CheckRemoteItems(t, file2a, file2b, file2c, file2d)
  2535  
  2536  	// Should force rename of dest file that is differently-cased
  2537  	accounting.GlobalStats().ResetCounters()
  2538  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  2539  	require.NoError(t, err)
  2540  	r.CheckLocalItems(t, file1a, file1b, file1c, file1d)
  2541  	r.CheckRemoteItems(t, file1a, file1b, file1c, file1d)
  2542  }
  2543  
  2544  // Test that aborting on --max-transfer works
  2545  func TestMaxTransfer(t *testing.T) {
  2546  	ctx := context.Background()
  2547  	ctx, ci := fs.AddConfig(ctx)
  2548  	ci.MaxTransfer = 3 * 1024
  2549  	ci.Transfers = 1
  2550  	ci.Checkers = 1
  2551  	ci.CutoffMode = fs.CutoffModeHard
  2552  
  2553  	test := func(t *testing.T, cutoff fs.CutoffMode) {
  2554  		r := fstest.NewRun(t)
  2555  		ci.CutoffMode = cutoff
  2556  
  2557  		if r.Fremote.Name() != "local" {
  2558  			t.Skip("This test only runs on local")
  2559  		}
  2560  
  2561  		// Create file on source
  2562  		file1 := r.WriteFile("file1", string(make([]byte, 5*1024)), t1)
  2563  		file2 := r.WriteFile("file2", string(make([]byte, 2*1024)), t1)
  2564  		file3 := r.WriteFile("file3", string(make([]byte, 3*1024)), t1)
  2565  		r.CheckLocalItems(t, file1, file2, file3)
  2566  		r.CheckRemoteItems(t)
  2567  
  2568  		accounting.GlobalStats().ResetCounters()
  2569  
  2570  		// ctx = predictDstFromLogger(ctx) // not currently supported
  2571  		err := Sync(ctx, r.Fremote, r.Flocal, false)
  2572  		// testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2573  		expectedErr := fserrors.FsError(accounting.ErrorMaxTransferLimitReachedFatal)
  2574  		if cutoff != fs.CutoffModeHard {
  2575  			expectedErr = accounting.ErrorMaxTransferLimitReachedGraceful
  2576  		}
  2577  		fserrors.Count(expectedErr)
  2578  		assert.Equal(t, expectedErr, err)
  2579  	}
  2580  
  2581  	t.Run("Hard", func(t *testing.T) { test(t, fs.CutoffModeHard) })
  2582  	t.Run("Soft", func(t *testing.T) { test(t, fs.CutoffModeSoft) })
  2583  	t.Run("Cautious", func(t *testing.T) { test(t, fs.CutoffModeCautious) })
  2584  }
  2585  
  2586  func testSyncConcurrent(t *testing.T, subtest string) {
  2587  	const (
  2588  		NFILES     = 20
  2589  		NCHECKERS  = 4
  2590  		NTRANSFERS = 4
  2591  	)
  2592  
  2593  	ctx, ci := fs.AddConfig(context.Background())
  2594  	ci.Checkers = NCHECKERS
  2595  	ci.Transfers = NTRANSFERS
  2596  
  2597  	r := fstest.NewRun(t)
  2598  	stats := accounting.GlobalStats()
  2599  
  2600  	itemsBefore := []fstest.Item{}
  2601  	itemsAfter := []fstest.Item{}
  2602  	for i := 0; i < NFILES; i++ {
  2603  		nameBoth := fmt.Sprintf("both%d", i)
  2604  		nameOnly := fmt.Sprintf("only%d", i)
  2605  		switch subtest {
  2606  		case "delete":
  2607  			fileBoth := r.WriteBoth(ctx, nameBoth, "potato", t1)
  2608  			fileOnly := r.WriteObject(ctx, nameOnly, "potato", t1)
  2609  			itemsBefore = append(itemsBefore, fileBoth, fileOnly)
  2610  			itemsAfter = append(itemsAfter, fileBoth)
  2611  		case "truncate":
  2612  			fileBoth := r.WriteBoth(ctx, nameBoth, "potato", t1)
  2613  			fileFull := r.WriteObject(ctx, nameOnly, "potato", t1)
  2614  			fileEmpty := r.WriteFile(nameOnly, "", t1)
  2615  			itemsBefore = append(itemsBefore, fileBoth, fileFull)
  2616  			itemsAfter = append(itemsAfter, fileBoth, fileEmpty)
  2617  		}
  2618  	}
  2619  
  2620  	r.CheckRemoteItems(t, itemsBefore...)
  2621  	stats.ResetErrors()
  2622  	ctx = predictDstFromLogger(ctx)
  2623  	err := Sync(ctx, r.Fremote, r.Flocal, false)
  2624  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2625  	if errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
  2626  		t.Skipf("Skip test because remote cannot upload empty files")
  2627  	}
  2628  	assert.NoError(t, err, "Sync must not return a error")
  2629  	assert.False(t, stats.Errored(), "Low level errors must not have happened")
  2630  	r.CheckRemoteItems(t, itemsAfter...)
  2631  }
  2632  
  2633  func TestSyncConcurrentDelete(t *testing.T) {
  2634  	testSyncConcurrent(t, "delete")
  2635  }
  2636  
  2637  func TestSyncConcurrentTruncate(t *testing.T) {
  2638  	testSyncConcurrent(t, "truncate")
  2639  }
  2640  
  2641  // Tests that nothing is transferred when src and dst already match
  2642  // Run the same sync twice, ensure no action is taken the second time
  2643  func testNothingToTransfer(t *testing.T, copyEmptySrcDirs bool) {
  2644  	accounting.GlobalStats().ResetCounters()
  2645  	ctx, _ := fs.AddConfig(context.Background())
  2646  	r := fstest.NewRun(t)
  2647  	file1 := r.WriteFile("sub dir/hello world", "hello world", t1)
  2648  	file2 := r.WriteFile("sub dir2/very/very/very/very/very/nested/subdir/hello world", "hello world", t1)
  2649  	r.CheckLocalItems(t, file1, file2)
  2650  	_, err := operations.SetDirModTime(ctx, r.Flocal, nil, "sub dir", t2)
  2651  	if err != nil && !errors.Is(err, fs.ErrorNotImplemented) {
  2652  		require.NoError(t, err)
  2653  	}
  2654  	r.Mkdir(ctx, r.Fremote)
  2655  	_, err = operations.MkdirModTime(ctx, r.Fremote, "sub dir", t3)
  2656  	require.NoError(t, err)
  2657  
  2658  	// set logging
  2659  	// (this checks log output as DirModtime operations do not yet have stats, and r.CheckDirectoryModTimes also does not tell us what actions were taken)
  2660  	oldLogLevel := fs.GetConfig(context.Background()).LogLevel
  2661  	defer func() { fs.GetConfig(context.Background()).LogLevel = oldLogLevel }() // reset to old val after test
  2662  	// need to do this as fs.Infof only respects the globalConfig
  2663  	fs.GetConfig(context.Background()).LogLevel = fs.LogLevelInfo
  2664  
  2665  	accounting.GlobalStats().ResetCounters()
  2666  	ctx = predictDstFromLogger(ctx)
  2667  	output := bilib.CaptureOutput(func() {
  2668  		err = CopyDir(ctx, r.Fremote, r.Flocal, copyEmptySrcDirs)
  2669  		require.NoError(t, err)
  2670  	})
  2671  	require.NotNil(t, output)
  2672  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2673  	r.CheckLocalItems(t, file1, file2)
  2674  	r.CheckRemoteItems(t, file1, file2)
  2675  	// Check that the modtimes of the directories are as expected
  2676  	r.CheckDirectoryModTimes(t, "sub dir", "sub dir2", "sub dir2/very", "sub dir2/very/very", "sub dir2/very/very/very/very/very/nested/subdir")
  2677  
  2678  	// check that actions were taken
  2679  	assert.True(t, strings.Contains(string(output), "Copied"), `expected to find at least one "Copied" log: `+string(output))
  2680  	if r.Fremote.Features().DirSetModTime != nil || r.Fremote.Features().MkdirMetadata != nil {
  2681  		assert.True(t, strings.Contains(string(output), "Set directory modification time"), `expected to find at least one "Set directory modification time" log: `+string(output))
  2682  	}
  2683  	assert.False(t, strings.Contains(string(output), "There was nothing to transfer"), `expected to find no "There was nothing to transfer" logs, but found one: `+string(output))
  2684  	assert.True(t, accounting.GlobalStats().GetTransfers() >= 2)
  2685  
  2686  	// run it again and make sure no actions were taken
  2687  	accounting.GlobalStats().ResetCounters()
  2688  	ctx = predictDstFromLogger(ctx)
  2689  	output = bilib.CaptureOutput(func() {
  2690  		err = CopyDir(ctx, r.Fremote, r.Flocal, copyEmptySrcDirs)
  2691  		require.NoError(t, err)
  2692  	})
  2693  	require.NotNil(t, output)
  2694  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2695  	r.CheckLocalItems(t, file1, file2)
  2696  	r.CheckRemoteItems(t, file1, file2)
  2697  	// Check that the modtimes of the directories are as expected
  2698  	r.CheckDirectoryModTimes(t, "sub dir", "sub dir2", "sub dir2/very", "sub dir2/very/very", "sub dir2/very/very/very/very/very/nested/subdir")
  2699  
  2700  	// check that actions were NOT taken
  2701  	assert.False(t, strings.Contains(string(output), "Copied"), `expected to find no "Copied" logs, but found one: `+string(output))
  2702  	if r.Fremote.Features().DirSetModTime != nil || r.Fremote.Features().MkdirMetadata != nil {
  2703  		assert.False(t, strings.Contains(string(output), "Set directory modification time"), `expected to find no "Set directory modification time" logs, but found one: `+string(output))
  2704  		assert.False(t, strings.Contains(string(output), "Updated directory metadata"), `expected to find no "Updated directory metadata" logs, but found one: `+string(output))
  2705  		assert.False(t, strings.Contains(string(output), "directory"), `expected to find no "directory"-related logs, but found one: `+string(output)) // catch-all
  2706  	}
  2707  	assert.True(t, strings.Contains(string(output), "There was nothing to transfer"), `expected to find a "There was nothing to transfer" log: `+string(output))
  2708  	assert.Equal(t, int64(0), accounting.GlobalStats().GetTransfers())
  2709  
  2710  	// check nested empty dir behavior (FIXME: probably belongs in a separate test)
  2711  	if r.Fremote.Features().DirSetModTime == nil && r.Fremote.Features().MkdirMetadata == nil {
  2712  		return
  2713  	}
  2714  	file3 := r.WriteFile("sub dir2/sub dir3/hello world", "hello again, world", t1)
  2715  	_, err = operations.SetDirModTime(ctx, r.Flocal, nil, "sub dir2", t1)
  2716  	assert.NoError(t, err)
  2717  	_, err = operations.SetDirModTime(ctx, r.Fremote, nil, "sub dir2", t1)
  2718  	assert.NoError(t, err)
  2719  	_, err = operations.MkdirModTime(ctx, r.Flocal, "sub dirEmpty/sub dirEmpty2", t2)
  2720  	assert.NoError(t, err)
  2721  	_, err = operations.SetDirModTime(ctx, r.Flocal, nil, "sub dirEmpty", t2)
  2722  	assert.NoError(t, err)
  2723  
  2724  	accounting.GlobalStats().ResetCounters()
  2725  	ctx = predictDstFromLogger(ctx)
  2726  	output = bilib.CaptureOutput(func() {
  2727  		err = CopyDir(ctx, r.Fremote, r.Flocal, copyEmptySrcDirs)
  2728  		require.NoError(t, err)
  2729  	})
  2730  	require.NotNil(t, output)
  2731  	testLoggerVsLsf(ctx, r.Fremote, operations.GetLoggerOpt(ctx).JSON, t)
  2732  	r.CheckLocalItems(t, file1, file2, file3)
  2733  	r.CheckRemoteItems(t, file1, file2, file3)
  2734  	// Check that the modtimes of the directories are as expected
  2735  	r.CheckDirectoryModTimes(t, "sub dir", "sub dir2", "sub dir2/very", "sub dir2/very/very", "sub dir2/very/very/very/very/very/nested/subdir", "sub dir2/sub dir3")
  2736  	if copyEmptySrcDirs {
  2737  		r.CheckDirectoryModTimes(t, "sub dirEmpty", "sub dirEmpty/sub dirEmpty2")
  2738  		assert.True(t, strings.Contains(string(output), "sub dirEmpty:"), `expected to find at least one "sub dirEmpty:" log: `+string(output))
  2739  	} else {
  2740  		assert.False(t, strings.Contains(string(output), "sub dirEmpty:"), `expected to find no "sub dirEmpty:" logs, but found one (empty dir was synced and shouldn't have been): `+string(output))
  2741  	}
  2742  	assert.True(t, strings.Contains(string(output), "sub dir3:"), `expected to find at least one "sub dir3:" log: `+string(output))
  2743  	assert.False(t, strings.Contains(string(output), "sub dir2/very:"), `expected to find no "sub dir2/very:" logs, but found one (unmodified dir was marked modified): `+string(output))
  2744  }
  2745  
  2746  func TestNothingToTransferWithEmptyDirs(t *testing.T) {
  2747  	testNothingToTransfer(t, true)
  2748  }
  2749  
  2750  func TestNothingToTransferWithoutEmptyDirs(t *testing.T) {
  2751  	testNothingToTransfer(t, false)
  2752  }
  2753  
  2754  // for testing logger:
  2755  func predictDstFromLogger(ctx context.Context) context.Context {
  2756  	opt := operations.NewLoggerOpt()
  2757  	var lock mutex.Mutex
  2758  
  2759  	opt.LoggerFn = func(ctx context.Context, sigil operations.Sigil, src, dst fs.DirEntry, err error) {
  2760  		lock.Lock()
  2761  		defer lock.Unlock()
  2762  
  2763  		// ignore dirs for our purposes here
  2764  		if err == fs.ErrorIsDir {
  2765  			return
  2766  		}
  2767  		winner := operations.WinningSide(ctx, sigil, src, dst, err)
  2768  		if winner.Obj != nil {
  2769  			file := winner.Obj
  2770  			obj, ok := file.(fs.ObjectInfo)
  2771  			checksum := ""
  2772  			timeFormat := "2006-01-02 15:04:05"
  2773  			if ok {
  2774  				if obj.Fs().Hashes().GetOne() == hash.MD5 {
  2775  					// skip if no MD5
  2776  					checksum, _ = obj.Hash(ctx, hash.MD5)
  2777  				}
  2778  				timeFormat = operations.FormatForLSFPrecision(obj.Fs().Precision())
  2779  			}
  2780  			errMsg := ""
  2781  			if winner.Err != nil {
  2782  				errMsg = ";" + winner.Err.Error()
  2783  			}
  2784  			operations.SyncFprintf(opt.JSON, "%s;%s;%v;%s%s\n", file.ModTime(ctx).Local().Format(timeFormat), checksum, file.Size(), file.Remote(), errMsg)
  2785  		}
  2786  	}
  2787  	return operations.WithSyncLogger(ctx, opt)
  2788  }
  2789  
  2790  func DstLsf(ctx context.Context, Fremote fs.Fs) *bytes.Buffer {
  2791  	var opt = operations.ListJSONOpt{
  2792  		NoModTime:  false,
  2793  		NoMimeType: true,
  2794  		DirsOnly:   false,
  2795  		FilesOnly:  true,
  2796  		Recurse:    true,
  2797  		ShowHash:   true,
  2798  		HashTypes:  []string{"MD5"},
  2799  	}
  2800  
  2801  	var list operations.ListFormat
  2802  
  2803  	list.SetSeparator(";")
  2804  	timeFormat := operations.FormatForLSFPrecision(Fremote.Precision())
  2805  	list.AddModTime(timeFormat)
  2806  	list.AddHash(hash.MD5)
  2807  	list.AddSize()
  2808  	list.AddPath()
  2809  
  2810  	out := new(bytes.Buffer)
  2811  
  2812  	err := operations.ListJSON(ctx, Fremote, "", &opt, func(item *operations.ListJSONItem) error {
  2813  		_, _ = fmt.Fprintln(out, list.Format(item))
  2814  		return nil
  2815  	})
  2816  	if err != nil {
  2817  		fs.Errorf(Fremote, "ListJSON error: %v", err)
  2818  	}
  2819  
  2820  	return out
  2821  }
  2822  
  2823  func LoggerMatchesLsf(logger, lsf *bytes.Buffer) error {
  2824  	loggerSplit := bytes.Split(logger.Bytes(), []byte("\n"))
  2825  	sort.SliceStable(loggerSplit, func(i int, j int) bool { return string(loggerSplit[i]) < string(loggerSplit[j]) })
  2826  	lsfSplit := bytes.Split(lsf.Bytes(), []byte("\n"))
  2827  	sort.SliceStable(lsfSplit, func(i int, j int) bool { return string(lsfSplit[i]) < string(lsfSplit[j]) })
  2828  
  2829  	loggerJoined := bytes.Join(loggerSplit, []byte("\n"))
  2830  	lsfJoined := bytes.Join(lsfSplit, []byte("\n"))
  2831  
  2832  	if bytes.Equal(loggerJoined, lsfJoined) {
  2833  		return nil
  2834  	}
  2835  	Diff(string(loggerJoined), string(lsfJoined))
  2836  	return fmt.Errorf("logger does not match lsf! \nlogger: \n%s \nlsf: \n%s", loggerJoined, lsfJoined)
  2837  }
  2838  
  2839  func Diff(rev1, rev2 string) {
  2840  	fmt.Printf("Diff of %q and %q\n", "logger", "lsf")
  2841  	cmd := exec.Command("bash", "-c", fmt.Sprintf(`diff <(echo "%s")  <(echo "%s")`, rev1, rev2))
  2842  	out, _ := cmd.Output()
  2843  	_, _ = os.Stdout.Write(out)
  2844  }
  2845  
  2846  func testLoggerVsLsf(ctx context.Context, Fremote fs.Fs, logger *bytes.Buffer, t *testing.T) {
  2847  	var newlogger bytes.Buffer
  2848  	canTestModtime := fs.GetModifyWindow(ctx, Fremote) != fs.ModTimeNotSupported
  2849  	canTestHash := Fremote.Hashes().Contains(hash.MD5)
  2850  	if !canTestHash || !canTestModtime {
  2851  		loggerSplit := bytes.Split(logger.Bytes(), []byte("\n"))
  2852  		for i, line := range loggerSplit {
  2853  			elements := bytes.Split(line, []byte(";"))
  2854  			if len(elements) >= 2 {
  2855  				if !canTestModtime {
  2856  					elements[0] = []byte("")
  2857  				}
  2858  				if !canTestHash {
  2859  					elements[1] = []byte("")
  2860  				}
  2861  			}
  2862  			loggerSplit[i] = bytes.Join(elements, []byte(";"))
  2863  		}
  2864  		newlogger.Write(bytes.Join(loggerSplit, []byte("\n")))
  2865  	} else {
  2866  		newlogger.Write(logger.Bytes())
  2867  	}
  2868  
  2869  	r := fstest.NewRun(t)
  2870  	if r.Flocal.Precision() == Fremote.Precision() && r.Flocal.Hashes().Contains(hash.MD5) && canTestHash {
  2871  		lsf := DstLsf(ctx, Fremote)
  2872  		err := LoggerMatchesLsf(&newlogger, lsf)
  2873  		require.NoError(t, err)
  2874  	}
  2875  }