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 }