github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fstest/fstests/fstests.go (about) 1 // Package fstests provides generic integration tests for the Fs and 2 // Object interfaces. 3 // 4 // These tests are concerned with the basic functionality of a 5 // backend. The tests in fs/sync and fs/operations tests more 6 // cornercases that these tests don't. 7 package fstests 8 9 import ( 10 "bytes" 11 "context" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "math/bits" 16 "os" 17 "path" 18 "path/filepath" 19 "reflect" 20 "sort" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/ncw/rclone/fs" 26 "github.com/ncw/rclone/fs/config" 27 "github.com/ncw/rclone/fs/fserrors" 28 "github.com/ncw/rclone/fs/hash" 29 "github.com/ncw/rclone/fs/object" 30 "github.com/ncw/rclone/fs/operations" 31 "github.com/ncw/rclone/fs/walk" 32 "github.com/ncw/rclone/fstest" 33 "github.com/ncw/rclone/lib/readers" 34 "github.com/pkg/errors" 35 "github.com/stretchr/testify/assert" 36 "github.com/stretchr/testify/require" 37 ) 38 39 // InternalTester is an optional interface for Fs which allows to execute internal tests 40 // 41 // This interface should be implemented in 'backend'_internal_test.go and not in 'backend'.go 42 type InternalTester interface { 43 InternalTest(*testing.T) 44 } 45 46 // ChunkedUploadConfig contains the values used by TestFsPutChunked 47 // to determine the limits of chunked uploading 48 type ChunkedUploadConfig struct { 49 // Minimum allowed chunk size 50 MinChunkSize fs.SizeSuffix 51 // Maximum allowed chunk size, 0 is no limit 52 MaxChunkSize fs.SizeSuffix 53 // Rounds the given chunk size up to the next valid value 54 // nil will disable rounding 55 // e.g. the next power of 2 56 CeilChunkSize func(fs.SizeSuffix) fs.SizeSuffix 57 // More than one chunk is required on upload 58 NeedMultipleChunks bool 59 } 60 61 // SetUploadChunkSizer is a test only interface to change the upload chunk size at runtime 62 type SetUploadChunkSizer interface { 63 // Change the configured UploadChunkSize. 64 // Will only be called while no transfer is in progress. 65 SetUploadChunkSize(fs.SizeSuffix) (fs.SizeSuffix, error) 66 } 67 68 // SetUploadCutoffer is a test only interface to change the upload cutoff size at runtime 69 type SetUploadCutoffer interface { 70 // Change the configured UploadCutoff. 71 // Will only be called while no transfer is in progress. 72 SetUploadCutoff(fs.SizeSuffix) (fs.SizeSuffix, error) 73 } 74 75 // NextPowerOfTwo returns the current or next bigger power of two. 76 // All values less or equal 0 will return 0 77 func NextPowerOfTwo(i fs.SizeSuffix) fs.SizeSuffix { 78 return 1 << uint(64-bits.LeadingZeros64(uint64(i)-1)) 79 } 80 81 // NextMultipleOf returns a function that can be used as a CeilChunkSize function. 82 // This function will return the next multiple of m that is equal or bigger than i. 83 // All values less or equal 0 will return 0. 84 func NextMultipleOf(m fs.SizeSuffix) func(fs.SizeSuffix) fs.SizeSuffix { 85 if m <= 0 { 86 panic(fmt.Sprintf("invalid multiplier %s", m)) 87 } 88 return func(i fs.SizeSuffix) fs.SizeSuffix { 89 if i <= 0 { 90 return 0 91 } 92 93 return (((i - 1) / m) + 1) * m 94 } 95 } 96 97 // dirsToNames returns a sorted list of names 98 func dirsToNames(dirs []fs.Directory) []string { 99 names := []string{} 100 for _, dir := range dirs { 101 names = append(names, fstest.WinPath(fstest.Normalize(dir.Remote()))) 102 } 103 sort.Strings(names) 104 return names 105 } 106 107 // objsToNames returns a sorted list of object names 108 func objsToNames(objs []fs.Object) []string { 109 names := []string{} 110 for _, obj := range objs { 111 names = append(names, fstest.WinPath(fstest.Normalize(obj.Remote()))) 112 } 113 sort.Strings(names) 114 return names 115 } 116 117 // findObject finds the object on the remote 118 func findObject(t *testing.T, f fs.Fs, Name string) fs.Object { 119 var obj fs.Object 120 var err error 121 sleepTime := 1 * time.Second 122 for i := 1; i <= *fstest.ListRetries; i++ { 123 obj, err = f.NewObject(context.Background(), Name) 124 if err == nil { 125 break 126 } 127 t.Logf("Sleeping for %v for findObject eventual consistency: %d/%d (%v)", sleepTime, i, *fstest.ListRetries, err) 128 time.Sleep(sleepTime) 129 sleepTime = (sleepTime * 3) / 2 130 } 131 require.NoError(t, err) 132 return obj 133 } 134 135 // retry f() until no retriable error 136 func retry(t *testing.T, what string, f func() error) { 137 const maxTries = 10 138 var err error 139 for tries := 1; tries <= maxTries; tries++ { 140 err = f() 141 // exit if no error, or error is not retriable 142 if err == nil || !fserrors.IsRetryError(err) { 143 break 144 } 145 t.Logf("%s error: %v - low level retry %d/%d", what, err, tries, maxTries) 146 time.Sleep(2 * time.Second) 147 } 148 require.NoError(t, err, what) 149 } 150 151 // testPut puts file to the remote 152 func testPut(t *testing.T, f fs.Fs, file *fstest.Item) (string, fs.Object) { 153 var ( 154 err error 155 obj fs.Object 156 uploadHash *hash.MultiHasher 157 contents string 158 ) 159 retry(t, "Put", func() error { 160 contents = fstest.RandomString(100) 161 buf := bytes.NewBufferString(contents) 162 uploadHash = hash.NewMultiHasher() 163 in := io.TeeReader(buf, uploadHash) 164 165 file.Size = int64(buf.Len()) 166 obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil) 167 obj, err = f.Put(context.Background(), in, obji) 168 return err 169 }) 170 file.Hashes = uploadHash.Sums() 171 file.Check(t, obj, f.Precision()) 172 // Re-read the object and check again 173 obj = findObject(t, f, file.Path) 174 file.Check(t, obj, f.Precision()) 175 return contents, obj 176 } 177 178 // testPutLarge puts file to the remote, checks it and removes it on success. 179 func testPutLarge(t *testing.T, f fs.Fs, file *fstest.Item) { 180 var ( 181 err error 182 obj fs.Object 183 uploadHash *hash.MultiHasher 184 ) 185 retry(t, "PutLarge", func() error { 186 r := readers.NewPatternReader(file.Size) 187 uploadHash = hash.NewMultiHasher() 188 in := io.TeeReader(r, uploadHash) 189 190 obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil) 191 obj, err = f.Put(context.Background(), in, obji) 192 if file.Size == 0 && err == fs.ErrorCantUploadEmptyFiles { 193 t.Skip("Can't upload zero length files") 194 } 195 return err 196 }) 197 file.Hashes = uploadHash.Sums() 198 file.Check(t, obj, f.Precision()) 199 200 // Re-read the object and check again 201 obj = findObject(t, f, file.Path) 202 file.Check(t, obj, f.Precision()) 203 204 // Download the object and check it is OK 205 downloadHash := hash.NewMultiHasher() 206 download, err := obj.Open(context.Background()) 207 require.NoError(t, err) 208 n, err := io.Copy(downloadHash, download) 209 require.NoError(t, err) 210 assert.Equal(t, file.Size, n) 211 require.NoError(t, download.Close()) 212 assert.Equal(t, file.Hashes, downloadHash.Sums()) 213 214 // Remove the object 215 require.NoError(t, obj.Remove(context.Background())) 216 } 217 218 // errorReader just returns an error on Read 219 type errorReader struct { 220 err error 221 } 222 223 // Read returns an error immediately 224 func (er errorReader) Read(p []byte) (n int, err error) { 225 return 0, er.err 226 } 227 228 // read the contents of an object as a string 229 func readObject(t *testing.T, obj fs.Object, limit int64, options ...fs.OpenOption) string { 230 what := fmt.Sprintf("readObject(%q) limit=%d, options=%+v", obj, limit, options) 231 in, err := obj.Open(context.Background(), options...) 232 require.NoError(t, err, what) 233 var r io.Reader = in 234 if limit >= 0 { 235 r = &io.LimitedReader{R: r, N: limit} 236 } 237 contents, err := ioutil.ReadAll(r) 238 require.NoError(t, err, what) 239 err = in.Close() 240 require.NoError(t, err, what) 241 return string(contents) 242 } 243 244 // ExtraConfigItem describes a config item for the tests 245 type ExtraConfigItem struct{ Name, Key, Value string } 246 247 // Opt is options for Run 248 type Opt struct { 249 RemoteName string 250 NilObject fs.Object 251 ExtraConfig []ExtraConfigItem 252 SkipBadWindowsCharacters bool // skips unusable characters for windows if set 253 SkipFsMatch bool // if set skip exact matching of Fs value 254 TiersToTest []string // List of tiers which can be tested in setTier test 255 ChunkedUpload ChunkedUploadConfig 256 UnimplementableFsMethods []string // List of methods which can't be implemented in this wrapping Fs 257 UnimplementableObjectMethods []string // List of methods which can't be implemented in this wrapping Fs 258 SkipFsCheckWrap bool // if set skip FsCheckWrap 259 SkipObjectCheckWrap bool // if set skip ObjectCheckWrap 260 } 261 262 // returns true if x is found in ss 263 func stringsContains(x string, ss []string) bool { 264 for _, s := range ss { 265 if x == s { 266 return true 267 } 268 } 269 return false 270 } 271 272 // Run runs the basic integration tests for a remote using the options passed in. 273 // 274 // They are structured in a hierarchical way so that dependencies for the tests can be created. 275 // 276 // For example some tests require the directory to be created - these 277 // are inside the "FsMkdir" test. Some tests require some tests files 278 // - these are inside the "FsPutFiles" test. 279 func Run(t *testing.T, opt *Opt) { 280 var ( 281 remote fs.Fs 282 remoteName = opt.RemoteName 283 subRemoteName string 284 subRemoteLeaf string 285 file1 = fstest.Item{ 286 ModTime: fstest.Time("2001-02-03T04:05:06.499999999Z"), 287 Path: "file name.txt", 288 } 289 file1Contents string 290 file2 = fstest.Item{ 291 ModTime: fstest.Time("2001-02-03T04:05:10.123123123Z"), 292 Path: `hello? sausage/êé/Hello, 世界/ " ' @ < > & ? + ≠/z.txt`, 293 WinPath: `hello_ sausage/êé/Hello, 世界/ _ ' @ _ _ & _ + ≠/z.txt`, 294 } 295 isLocalRemote bool 296 purged bool // whether the dir has been purged or not 297 ) 298 299 // Skip the test if the remote isn't configured 300 skipIfNotOk := func(t *testing.T) { 301 if remote == nil { 302 t.Skipf("WARN: %q not configured", remoteName) 303 } 304 } 305 306 // Skip if remote is not ListR capable, otherwise set the useListR 307 // flag, returning a function to restore its value 308 skipIfNotListR := func(t *testing.T) func() { 309 skipIfNotOk(t) 310 if remote.Features().ListR == nil { 311 t.Skip("FS has no ListR interface") 312 } 313 previous := fs.Config.UseListR 314 fs.Config.UseListR = true 315 return func() { 316 fs.Config.UseListR = previous 317 } 318 } 319 320 // Skip if remote is not SetTier and GetTier capable 321 skipIfNotSetTier := func(t *testing.T) { 322 skipIfNotOk(t) 323 if remote.Features().SetTier == false || 324 remote.Features().GetTier == false { 325 t.Skip("FS has no SetTier & GetTier interfaces") 326 } 327 } 328 329 // Return true if f (or any of the things it wraps) is bucket 330 // based but not at the root. 331 isBucketBasedButNotRoot := func(f fs.Fs) bool { 332 for { 333 doUnWrap := f.Features().UnWrap 334 if doUnWrap == nil { 335 break 336 } 337 f = doUnWrap() 338 } 339 return f.Features().BucketBased && strings.Contains(strings.Trim(f.Root(), "/"), "/") 340 } 341 342 // Remove bad characters from Windows file name if set 343 if opt.SkipBadWindowsCharacters { 344 t.Logf("Removing bad windows characters from test file") 345 file2.Path = fstest.WinPath(file2.Path) 346 } 347 348 // Initialise the remote 349 fstest.Initialise() 350 351 // Set extra config if supplied 352 for _, item := range opt.ExtraConfig { 353 config.FileSet(item.Name, item.Key, item.Value) 354 } 355 if *fstest.RemoteName != "" { 356 remoteName = *fstest.RemoteName 357 } 358 t.Logf("Using remote %q", remoteName) 359 var err error 360 if remoteName == "" { 361 remoteName, err = fstest.LocalRemote() 362 require.NoError(t, err) 363 isLocalRemote = true 364 } 365 366 // Make the Fs we are testing with, initialising the local variables 367 // subRemoteName - name of the remote after the TestRemote: 368 // subRemoteLeaf - a subdirectory to use under that 369 // remote - the result of fs.NewFs(TestRemote:subRemoteName) 370 subRemoteName, subRemoteLeaf, err = fstest.RandomRemoteName(remoteName) 371 require.NoError(t, err) 372 remote, err = fs.NewFs(subRemoteName) 373 if err == fs.ErrorNotFoundInConfigFile { 374 t.Logf("Didn't find %q in config file - skipping tests", remoteName) 375 return 376 } 377 require.NoError(t, err, fmt.Sprintf("unexpected error: %v", err)) 378 379 // Skip the rest if it failed 380 skipIfNotOk(t) 381 382 // Check to see if Fs that wrap other Fs implement all the optional methods 383 t.Run("FsCheckWrap", func(t *testing.T) { 384 skipIfNotOk(t) 385 if opt.SkipFsCheckWrap { 386 t.Skip("Skipping FsCheckWrap on this Fs") 387 } 388 ft := new(fs.Features).Fill(remote) 389 if ft.UnWrap == nil { 390 t.Skip("Not a wrapping Fs") 391 } 392 v := reflect.ValueOf(ft).Elem() 393 vType := v.Type() 394 for i := 0; i < v.NumField(); i++ { 395 vName := vType.Field(i).Name 396 if stringsContains(vName, opt.UnimplementableFsMethods) { 397 continue 398 } 399 field := v.Field(i) 400 // skip the bools 401 if field.Type().Kind() == reflect.Bool { 402 continue 403 } 404 if field.IsNil() { 405 t.Errorf("Missing Fs wrapper for %s", vName) 406 } 407 } 408 }) 409 410 // TestFsRmdirNotFound tests deleting a non existent directory 411 t.Run("FsRmdirNotFound", func(t *testing.T) { 412 skipIfNotOk(t) 413 if isBucketBasedButNotRoot(remote) { 414 t.Skip("Skipping test as non root bucket based remote") 415 } 416 err := remote.Rmdir(context.Background(), "") 417 assert.Error(t, err, "Expecting error on Rmdir non existent") 418 }) 419 420 // Make the directory 421 err = remote.Mkdir(context.Background(), "") 422 require.NoError(t, err) 423 fstest.CheckListing(t, remote, []fstest.Item{}) 424 425 // TestFsString tests the String method 426 t.Run("FsString", func(t *testing.T) { 427 skipIfNotOk(t) 428 str := remote.String() 429 require.NotEqual(t, "", str) 430 }) 431 432 // TestFsName tests the Name method 433 t.Run("FsName", func(t *testing.T) { 434 skipIfNotOk(t) 435 got := remote.Name() 436 want := remoteName[:strings.IndexRune(remoteName, ':')+1] 437 if isLocalRemote { 438 want = "local:" 439 } 440 require.Equal(t, want, got+":") 441 }) 442 443 // TestFsRoot tests the Root method 444 t.Run("FsRoot", func(t *testing.T) { 445 skipIfNotOk(t) 446 name := remote.Name() + ":" 447 root := remote.Root() 448 if isLocalRemote { 449 // only check last path element on local 450 require.Equal(t, filepath.Base(subRemoteName), filepath.Base(root)) 451 } else { 452 require.Equal(t, subRemoteName, name+root) 453 } 454 }) 455 456 // TestFsRmdirEmpty tests deleting an empty directory 457 t.Run("FsRmdirEmpty", func(t *testing.T) { 458 skipIfNotOk(t) 459 err := remote.Rmdir(context.Background(), "") 460 require.NoError(t, err) 461 }) 462 463 // TestFsMkdir tests making a directory 464 // 465 // Tests that require the directory to be made are within this 466 t.Run("FsMkdir", func(t *testing.T) { 467 skipIfNotOk(t) 468 469 err := remote.Mkdir(context.Background(), "") 470 require.NoError(t, err) 471 fstest.CheckListing(t, remote, []fstest.Item{}) 472 473 err = remote.Mkdir(context.Background(), "") 474 require.NoError(t, err) 475 476 // TestFsMkdirRmdirSubdir tests making and removing a sub directory 477 t.Run("FsMkdirRmdirSubdir", func(t *testing.T) { 478 skipIfNotOk(t) 479 dir := "dir/subdir" 480 err := operations.Mkdir(context.Background(), remote, dir) 481 require.NoError(t, err) 482 fstest.CheckListingWithPrecision(t, remote, []fstest.Item{}, []string{"dir", "dir/subdir"}, fs.GetModifyWindow(remote)) 483 484 err = operations.Rmdir(context.Background(), remote, dir) 485 require.NoError(t, err) 486 fstest.CheckListingWithPrecision(t, remote, []fstest.Item{}, []string{"dir"}, fs.GetModifyWindow(remote)) 487 488 err = operations.Rmdir(context.Background(), remote, "dir") 489 require.NoError(t, err) 490 fstest.CheckListingWithPrecision(t, remote, []fstest.Item{}, []string{}, fs.GetModifyWindow(remote)) 491 }) 492 493 // TestFsListEmpty tests listing an empty directory 494 t.Run("FsListEmpty", func(t *testing.T) { 495 skipIfNotOk(t) 496 fstest.CheckListing(t, remote, []fstest.Item{}) 497 }) 498 499 // TestFsListDirEmpty tests listing the directories from an empty directory 500 TestFsListDirEmpty := func(t *testing.T) { 501 skipIfNotOk(t) 502 objs, dirs, err := walk.GetAll(context.Background(), remote, "", true, 1) 503 require.NoError(t, err) 504 assert.Equal(t, []string{}, objsToNames(objs)) 505 assert.Equal(t, []string{}, dirsToNames(dirs)) 506 } 507 t.Run("FsListDirEmpty", TestFsListDirEmpty) 508 509 // TestFsListRDirEmpty tests listing the directories from an empty directory using ListR 510 t.Run("FsListRDirEmpty", func(t *testing.T) { 511 defer skipIfNotListR(t)() 512 TestFsListDirEmpty(t) 513 }) 514 515 // TestFsListDirNotFound tests listing the directories from an empty directory 516 TestFsListDirNotFound := func(t *testing.T) { 517 skipIfNotOk(t) 518 objs, dirs, err := walk.GetAll(context.Background(), remote, "does not exist", true, 1) 519 if !remote.Features().CanHaveEmptyDirectories { 520 if err != fs.ErrorDirNotFound { 521 assert.NoError(t, err) 522 assert.Equal(t, 0, len(objs)+len(dirs)) 523 } 524 } else { 525 assert.Equal(t, fs.ErrorDirNotFound, err) 526 } 527 } 528 t.Run("FsListDirNotFound", TestFsListDirNotFound) 529 530 // TestFsListRDirNotFound tests listing the directories from an empty directory using ListR 531 t.Run("FsListRDirNotFound", func(t *testing.T) { 532 defer skipIfNotListR(t)() 533 TestFsListDirNotFound(t) 534 }) 535 536 // TestFsNewObjectNotFound tests not finding a object 537 t.Run("FsNewObjectNotFound", func(t *testing.T) { 538 skipIfNotOk(t) 539 // Object in an existing directory 540 o, err := remote.NewObject(context.Background(), "potato") 541 assert.Nil(t, o) 542 assert.Equal(t, fs.ErrorObjectNotFound, err) 543 // Now try an object in a non existing directory 544 o, err = remote.NewObject(context.Background(), "directory/not/found/potato") 545 assert.Nil(t, o) 546 assert.Equal(t, fs.ErrorObjectNotFound, err) 547 }) 548 549 // TestFsPutError tests uploading a file where there is an error 550 // 551 // It makes sure that aborting a file half way through does not create 552 // a file on the remote. 553 // 554 // go test -v -run 'TestIntegration/Test(Setup|Init|FsMkdir|FsPutError)$' 555 t.Run("FsPutError", func(t *testing.T) { 556 skipIfNotOk(t) 557 558 const N = 5 * 1024 559 // Read N bytes then produce an error 560 contents := fstest.RandomString(N) 561 buf := bytes.NewBufferString(contents) 562 er := &errorReader{errors.New("potato")} 563 in := io.MultiReader(buf, er) 564 565 obji := object.NewStaticObjectInfo(file2.Path, file2.ModTime, 2*N, true, nil, nil) 566 _, err := remote.Put(context.Background(), in, obji) 567 // assert.Nil(t, obj) - FIXME some remotes return the object even on nil 568 assert.NotNil(t, err) 569 570 obj, err := remote.NewObject(context.Background(), file2.Path) 571 assert.Nil(t, obj) 572 assert.Equal(t, fs.ErrorObjectNotFound, err) 573 }) 574 575 t.Run("FsPutChunked", func(t *testing.T) { 576 skipIfNotOk(t) 577 if testing.Short() { 578 t.Skip("not running with -short") 579 } 580 581 setUploadChunkSizer, _ := remote.(SetUploadChunkSizer) 582 if setUploadChunkSizer == nil { 583 t.Skipf("%T does not implement SetUploadChunkSizer", remote) 584 } 585 586 setUploadCutoffer, _ := remote.(SetUploadCutoffer) 587 588 minChunkSize := opt.ChunkedUpload.MinChunkSize 589 if minChunkSize < 100 { 590 minChunkSize = 100 591 } 592 if opt.ChunkedUpload.CeilChunkSize != nil { 593 minChunkSize = opt.ChunkedUpload.CeilChunkSize(minChunkSize) 594 } 595 596 maxChunkSize := 2 * fs.MebiByte 597 if maxChunkSize < 2*minChunkSize { 598 maxChunkSize = 2 * minChunkSize 599 } 600 if opt.ChunkedUpload.MaxChunkSize > 0 && maxChunkSize > opt.ChunkedUpload.MaxChunkSize { 601 maxChunkSize = opt.ChunkedUpload.MaxChunkSize 602 } 603 if opt.ChunkedUpload.CeilChunkSize != nil { 604 maxChunkSize = opt.ChunkedUpload.CeilChunkSize(maxChunkSize) 605 } 606 607 next := func(f func(fs.SizeSuffix) fs.SizeSuffix) fs.SizeSuffix { 608 s := f(minChunkSize) 609 if s > maxChunkSize { 610 s = minChunkSize 611 } 612 return s 613 } 614 615 chunkSizes := fs.SizeSuffixList{ 616 minChunkSize, 617 minChunkSize + (maxChunkSize-minChunkSize)/3, 618 next(NextPowerOfTwo), 619 next(NextMultipleOf(100000)), 620 next(NextMultipleOf(100001)), 621 maxChunkSize, 622 } 623 chunkSizes.Sort() 624 625 // Set the minimum chunk size, upload cutoff and reset it at the end 626 oldChunkSize, err := setUploadChunkSizer.SetUploadChunkSize(minChunkSize) 627 require.NoError(t, err) 628 var oldUploadCutoff fs.SizeSuffix 629 if setUploadCutoffer != nil { 630 oldUploadCutoff, err = setUploadCutoffer.SetUploadCutoff(minChunkSize) 631 require.NoError(t, err) 632 } 633 defer func() { 634 _, err := setUploadChunkSizer.SetUploadChunkSize(oldChunkSize) 635 assert.NoError(t, err) 636 if setUploadCutoffer != nil { 637 _, err := setUploadCutoffer.SetUploadCutoff(oldUploadCutoff) 638 assert.NoError(t, err) 639 } 640 }() 641 642 var lastCs fs.SizeSuffix 643 for _, cs := range chunkSizes { 644 if cs <= lastCs { 645 continue 646 } 647 if opt.ChunkedUpload.CeilChunkSize != nil { 648 cs = opt.ChunkedUpload.CeilChunkSize(cs) 649 } 650 lastCs = cs 651 652 t.Run(cs.String(), func(t *testing.T) { 653 _, err := setUploadChunkSizer.SetUploadChunkSize(cs) 654 require.NoError(t, err) 655 if setUploadCutoffer != nil { 656 _, err = setUploadCutoffer.SetUploadCutoff(cs) 657 require.NoError(t, err) 658 } 659 660 var testChunks []fs.SizeSuffix 661 if opt.ChunkedUpload.NeedMultipleChunks { 662 // If NeedMultipleChunks is set then test with > cs 663 testChunks = []fs.SizeSuffix{cs + 1, 2 * cs, 2*cs + 1} 664 } else { 665 testChunks = []fs.SizeSuffix{cs - 1, cs, 2*cs + 1} 666 } 667 668 for _, fileSize := range testChunks { 669 t.Run(fmt.Sprintf("%d", fileSize), func(t *testing.T) { 670 testPutLarge(t, remote, &fstest.Item{ 671 ModTime: fstest.Time("2001-02-03T04:05:06.499999999Z"), 672 Path: fmt.Sprintf("chunked-%s-%s.bin", cs.String(), fileSize.String()), 673 Size: int64(fileSize), 674 }) 675 }) 676 } 677 }) 678 } 679 }) 680 681 t.Run("FsPutZeroLength", func(t *testing.T) { 682 skipIfNotOk(t) 683 684 testPutLarge(t, remote, &fstest.Item{ 685 ModTime: fstest.Time("2001-02-03T04:05:06.499999999Z"), 686 Path: fmt.Sprintf("zero-length-file"), 687 Size: int64(0), 688 }) 689 }) 690 691 t.Run("FsOpenWriterAt", func(t *testing.T) { 692 skipIfNotOk(t) 693 openWriterAt := remote.Features().OpenWriterAt 694 if openWriterAt == nil { 695 t.Skip("FS has no OpenWriterAt interface") 696 } 697 path := "writer-at-subdir/writer-at-file" 698 out, err := openWriterAt(context.Background(), path, -1) 699 require.NoError(t, err) 700 701 var n int 702 n, err = out.WriteAt([]byte("def"), 3) 703 assert.NoError(t, err) 704 assert.Equal(t, 3, n) 705 n, err = out.WriteAt([]byte("ghi"), 6) 706 assert.NoError(t, err) 707 assert.Equal(t, 3, n) 708 n, err = out.WriteAt([]byte("abc"), 0) 709 assert.NoError(t, err) 710 assert.Equal(t, 3, n) 711 712 assert.NoError(t, out.Close()) 713 714 obj := findObject(t, remote, path) 715 assert.Equal(t, "abcdefghi", readObject(t, obj, -1), "contents of file differ") 716 717 assert.NoError(t, obj.Remove(context.Background())) 718 assert.NoError(t, remote.Rmdir(context.Background(), "writer-at-subdir")) 719 }) 720 721 // TestFsChangeNotify tests that changes are properly 722 // propagated 723 // 724 // go test -v -remote TestDrive: -run '^Test(Setup|Init|FsChangeNotify)$' -verbose 725 t.Run("FsChangeNotify", func(t *testing.T) { 726 skipIfNotOk(t) 727 728 // Check have ChangeNotify 729 doChangeNotify := remote.Features().ChangeNotify 730 if doChangeNotify == nil { 731 t.Skip("FS has no ChangeNotify interface") 732 } 733 734 err := operations.Mkdir(context.Background(), remote, "dir") 735 require.NoError(t, err) 736 737 pollInterval := make(chan time.Duration) 738 dirChanges := map[string]struct{}{} 739 objChanges := map[string]struct{}{} 740 doChangeNotify(context.Background(), func(x string, e fs.EntryType) { 741 fs.Debugf(nil, "doChangeNotify(%q, %+v)", x, e) 742 if strings.HasPrefix(x, file1.Path[:5]) || strings.HasPrefix(x, file2.Path[:5]) { 743 fs.Debugf(nil, "Ignoring notify for file1 or file2: %q, %v", x, e) 744 return 745 } 746 if e == fs.EntryDirectory { 747 dirChanges[x] = struct{}{} 748 } else if e == fs.EntryObject { 749 objChanges[x] = struct{}{} 750 } 751 }, pollInterval) 752 defer func() { close(pollInterval) }() 753 pollInterval <- time.Second 754 755 var dirs []string 756 for _, idx := range []int{1, 3, 2} { 757 dir := fmt.Sprintf("dir/subdir%d", idx) 758 err = operations.Mkdir(context.Background(), remote, dir) 759 require.NoError(t, err) 760 dirs = append(dirs, dir) 761 } 762 763 var objs []fs.Object 764 for _, idx := range []int{2, 4, 3} { 765 file := fstest.Item{ 766 ModTime: time.Now(), 767 Path: fmt.Sprintf("dir/file%d", idx), 768 } 769 _, o := testPut(t, remote, &file) 770 objs = append(objs, o) 771 } 772 773 // Looks for each item in wants in changes - 774 // if they are all found it returns true 775 contains := func(changes map[string]struct{}, wants []string) bool { 776 for _, want := range wants { 777 _, ok := changes[want] 778 if !ok { 779 return false 780 } 781 } 782 return true 783 } 784 785 // Wait a little while for the changes to come in 786 wantDirChanges := []string{"dir/subdir1", "dir/subdir3", "dir/subdir2"} 787 wantObjChanges := []string{"dir/file2", "dir/file4", "dir/file3"} 788 ok := false 789 for tries := 1; tries < 10; tries++ { 790 ok = contains(dirChanges, wantDirChanges) && contains(objChanges, wantObjChanges) 791 if ok { 792 break 793 } 794 t.Logf("Try %d/10 waiting for dirChanges and objChanges", tries) 795 time.Sleep(3 * time.Second) 796 } 797 if !ok { 798 t.Errorf("%+v does not contain %+v or \n%+v does not contain %+v", dirChanges, wantDirChanges, objChanges, wantObjChanges) 799 } 800 801 // tidy up afterwards 802 for _, o := range objs { 803 assert.NoError(t, o.Remove(context.Background())) 804 } 805 dirs = append(dirs, "dir") 806 for _, dir := range dirs { 807 assert.NoError(t, remote.Rmdir(context.Background(), dir)) 808 } 809 }) 810 811 // TestFsPut files writes file1, file2 and tests an update 812 // 813 // Tests that require file1, file2 are within this 814 t.Run("FsPutFiles", func(t *testing.T) { 815 skipIfNotOk(t) 816 file1Contents, _ = testPut(t, remote, &file1) 817 /* file2Contents = */ testPut(t, remote, &file2) 818 file1Contents, _ = testPut(t, remote, &file1) 819 // Note that the next test will check there are no duplicated file names 820 821 // TestFsListDirFile2 tests the files are correctly uploaded by doing 822 // Depth 1 directory listings 823 TestFsListDirFile2 := func(t *testing.T) { 824 skipIfNotOk(t) 825 list := func(dir string, expectedDirNames, expectedObjNames []string) { 826 var objNames, dirNames []string 827 for i := 1; i <= *fstest.ListRetries; i++ { 828 objs, dirs, err := walk.GetAll(context.Background(), remote, dir, true, 1) 829 if errors.Cause(err) == fs.ErrorDirNotFound { 830 objs, dirs, err = walk.GetAll(context.Background(), remote, fstest.WinPath(dir), true, 1) 831 } 832 require.NoError(t, err) 833 objNames = objsToNames(objs) 834 dirNames = dirsToNames(dirs) 835 if len(objNames) >= len(expectedObjNames) && len(dirNames) >= len(expectedDirNames) { 836 break 837 } 838 t.Logf("Sleeping for 1 second for TestFsListDirFile2 eventual consistency: %d/%d", i, *fstest.ListRetries) 839 time.Sleep(1 * time.Second) 840 } 841 assert.Equal(t, expectedDirNames, dirNames) 842 assert.Equal(t, expectedObjNames, objNames) 843 } 844 dir := file2.Path 845 deepest := true 846 for dir != "" { 847 expectedObjNames := []string{} 848 expectedDirNames := []string{} 849 child := dir 850 dir = path.Dir(dir) 851 if dir == "." { 852 dir = "" 853 expectedObjNames = append(expectedObjNames, fstest.WinPath(file1.Path)) 854 } 855 if deepest { 856 expectedObjNames = append(expectedObjNames, fstest.WinPath(file2.Path)) 857 deepest = false 858 } else { 859 expectedDirNames = append(expectedDirNames, fstest.WinPath(child)) 860 } 861 list(dir, expectedDirNames, expectedObjNames) 862 } 863 } 864 t.Run("FsListDirFile2", TestFsListDirFile2) 865 866 // TestFsListRDirFile2 tests the files are correctly uploaded by doing 867 // Depth 1 directory listings using ListR 868 t.Run("FsListRDirFile2", func(t *testing.T) { 869 defer skipIfNotListR(t)() 870 TestFsListDirFile2(t) 871 }) 872 873 // Test the files are all there with walk.ListR recursive listings 874 t.Run("FsListR", func(t *testing.T) { 875 skipIfNotOk(t) 876 objs, dirs, err := walk.GetAll(context.Background(), remote, "", true, -1) 877 require.NoError(t, err) 878 assert.Equal(t, []string{ 879 "hello_ sausage", 880 "hello_ sausage/êé", 881 "hello_ sausage/êé/Hello, 世界", 882 "hello_ sausage/êé/Hello, 世界/ _ ' @ _ _ & _ + ≠", 883 }, dirsToNames(dirs)) 884 assert.Equal(t, []string{ 885 "file name.txt", 886 "hello_ sausage/êé/Hello, 世界/ _ ' @ _ _ & _ + ≠/z.txt", 887 }, objsToNames(objs)) 888 }) 889 890 // Test the files are all there with 891 // walk.ListR recursive listings on a sub dir 892 t.Run("FsListRSubdir", func(t *testing.T) { 893 skipIfNotOk(t) 894 objs, dirs, err := walk.GetAll(context.Background(), remote, path.Dir(path.Dir(path.Dir(path.Dir(file2.Path)))), true, -1) 895 require.NoError(t, err) 896 assert.Equal(t, []string{ 897 "hello_ sausage/êé", 898 "hello_ sausage/êé/Hello, 世界", 899 "hello_ sausage/êé/Hello, 世界/ _ ' @ _ _ & _ + ≠", 900 }, dirsToNames(dirs)) 901 assert.Equal(t, []string{ 902 "hello_ sausage/êé/Hello, 世界/ _ ' @ _ _ & _ + ≠/z.txt", 903 }, objsToNames(objs)) 904 }) 905 906 // TestFsListDirRoot tests that DirList works in the root 907 TestFsListDirRoot := func(t *testing.T) { 908 skipIfNotOk(t) 909 rootRemote, err := fs.NewFs(remoteName) 910 require.NoError(t, err) 911 _, dirs, err := walk.GetAll(context.Background(), rootRemote, "", true, 1) 912 require.NoError(t, err) 913 assert.Contains(t, dirsToNames(dirs), subRemoteLeaf, "Remote leaf not found") 914 } 915 t.Run("FsListDirRoot", TestFsListDirRoot) 916 917 // TestFsListRDirRoot tests that DirList works in the root using ListR 918 t.Run("FsListRDirRoot", func(t *testing.T) { 919 defer skipIfNotListR(t)() 920 TestFsListDirRoot(t) 921 }) 922 923 // TestFsListSubdir tests List works for a subdirectory 924 TestFsListSubdir := func(t *testing.T) { 925 skipIfNotOk(t) 926 fileName := file2.Path 927 var err error 928 var objs []fs.Object 929 var dirs []fs.Directory 930 for i := 0; i < 2; i++ { 931 dir, _ := path.Split(fileName) 932 dir = dir[:len(dir)-1] 933 objs, dirs, err = walk.GetAll(context.Background(), remote, dir, true, -1) 934 if err != fs.ErrorDirNotFound { 935 break 936 } 937 fileName = file2.WinPath 938 } 939 require.NoError(t, err) 940 require.Len(t, objs, 1) 941 assert.Equal(t, fileName, objs[0].Remote()) 942 require.Len(t, dirs, 0) 943 } 944 t.Run("FsListSubdir", TestFsListSubdir) 945 946 // TestFsListRSubdir tests List works for a subdirectory using ListR 947 t.Run("FsListRSubdir", func(t *testing.T) { 948 defer skipIfNotListR(t)() 949 TestFsListSubdir(t) 950 }) 951 952 // TestFsListLevel2 tests List works for 2 levels 953 TestFsListLevel2 := func(t *testing.T) { 954 skipIfNotOk(t) 955 objs, dirs, err := walk.GetAll(context.Background(), remote, "", true, 2) 956 if err == fs.ErrorLevelNotSupported { 957 return 958 } 959 require.NoError(t, err) 960 assert.Equal(t, []string{file1.Path}, objsToNames(objs)) 961 assert.Equal(t, []string{`hello_ sausage`, `hello_ sausage/êé`}, dirsToNames(dirs)) 962 } 963 t.Run("FsListLevel2", TestFsListLevel2) 964 965 // TestFsListRLevel2 tests List works for 2 levels using ListR 966 t.Run("FsListRLevel2", func(t *testing.T) { 967 defer skipIfNotListR(t)() 968 TestFsListLevel2(t) 969 }) 970 971 // TestFsListFile1 tests file present 972 t.Run("FsListFile1", func(t *testing.T) { 973 skipIfNotOk(t) 974 fstest.CheckListing(t, remote, []fstest.Item{file1, file2}) 975 }) 976 977 // TestFsNewObject tests NewObject 978 t.Run("FsNewObject", func(t *testing.T) { 979 skipIfNotOk(t) 980 obj := findObject(t, remote, file1.Path) 981 file1.Check(t, obj, remote.Precision()) 982 }) 983 984 // TestFsListFile1and2 tests two files present 985 t.Run("FsListFile1and2", func(t *testing.T) { 986 skipIfNotOk(t) 987 fstest.CheckListing(t, remote, []fstest.Item{file1, file2}) 988 }) 989 990 // TestFsNewObjectDir tests NewObject on a directory which should produce an error 991 t.Run("FsNewObjectDir", func(t *testing.T) { 992 skipIfNotOk(t) 993 dir := path.Dir(file2.Path) 994 obj, err := remote.NewObject(context.Background(), dir) 995 assert.Nil(t, obj) 996 assert.NotNil(t, err) 997 }) 998 999 // TestFsCopy tests Copy 1000 t.Run("FsCopy", func(t *testing.T) { 1001 skipIfNotOk(t) 1002 1003 // Check have Copy 1004 doCopy := remote.Features().Copy 1005 if doCopy == nil { 1006 t.Skip("FS has no Copier interface") 1007 } 1008 1009 // Test with file2 so have + and ' ' in file name 1010 var file2Copy = file2 1011 file2Copy.Path += "-copy" 1012 1013 // do the copy 1014 src := findObject(t, remote, file2.Path) 1015 dst, err := doCopy(context.Background(), src, file2Copy.Path) 1016 if err == fs.ErrorCantCopy { 1017 t.Skip("FS can't copy") 1018 } 1019 require.NoError(t, err, fmt.Sprintf("Error: %#v", err)) 1020 1021 // check file exists in new listing 1022 fstest.CheckListing(t, remote, []fstest.Item{file1, file2, file2Copy}) 1023 1024 // Check dst lightly - list above has checked ModTime/Hashes 1025 assert.Equal(t, file2Copy.Path, dst.Remote()) 1026 1027 // Delete copy 1028 err = dst.Remove(context.Background()) 1029 require.NoError(t, err) 1030 1031 }) 1032 1033 // TestFsMove tests Move 1034 t.Run("FsMove", func(t *testing.T) { 1035 skipIfNotOk(t) 1036 1037 // Check have Move 1038 doMove := remote.Features().Move 1039 if doMove == nil { 1040 t.Skip("FS has no Mover interface") 1041 } 1042 1043 // state of files now: 1044 // 1: file name.txt 1045 // 2: hello sausage?/../z.txt 1046 1047 var file1Move = file1 1048 var file2Move = file2 1049 1050 // check happy path, i.e. no naming conflicts when rename and move are two 1051 // separate operations 1052 file2Move.Path = "other.txt" 1053 file2Move.WinPath = "" 1054 src := findObject(t, remote, file2.Path) 1055 dst, err := doMove(context.Background(), src, file2Move.Path) 1056 if err == fs.ErrorCantMove { 1057 t.Skip("FS can't move") 1058 } 1059 require.NoError(t, err) 1060 // check file exists in new listing 1061 fstest.CheckListing(t, remote, []fstest.Item{file1, file2Move}) 1062 // Check dst lightly - list above has checked ModTime/Hashes 1063 assert.Equal(t, file2Move.Path, dst.Remote()) 1064 // 1: file name.txt 1065 // 2: other.txt 1066 1067 // Check conflict on "rename, then move" 1068 file1Move.Path = "moveTest/other.txt" 1069 src = findObject(t, remote, file1.Path) 1070 _, err = doMove(context.Background(), src, file1Move.Path) 1071 require.NoError(t, err) 1072 fstest.CheckListing(t, remote, []fstest.Item{file1Move, file2Move}) 1073 // 1: moveTest/other.txt 1074 // 2: other.txt 1075 1076 // Check conflict on "move, then rename" 1077 src = findObject(t, remote, file1Move.Path) 1078 _, err = doMove(context.Background(), src, file1.Path) 1079 require.NoError(t, err) 1080 fstest.CheckListing(t, remote, []fstest.Item{file1, file2Move}) 1081 // 1: file name.txt 1082 // 2: other.txt 1083 1084 src = findObject(t, remote, file2Move.Path) 1085 _, err = doMove(context.Background(), src, file2.Path) 1086 require.NoError(t, err) 1087 fstest.CheckListing(t, remote, []fstest.Item{file1, file2}) 1088 // 1: file name.txt 1089 // 2: hello sausage?/../z.txt 1090 1091 // Tidy up moveTest directory 1092 require.NoError(t, remote.Rmdir(context.Background(), "moveTest")) 1093 }) 1094 1095 // Move src to this remote using server side move operations. 1096 // 1097 // Will only be called if src.Fs().Name() == f.Name() 1098 // 1099 // If it isn't possible then return fs.ErrorCantDirMove 1100 // 1101 // If destination exists then return fs.ErrorDirExists 1102 1103 // TestFsDirMove tests DirMove 1104 // 1105 // go test -v -run 'TestIntegration/Test(Setup|Init|FsMkdir|FsPutFile1|FsPutFile2|FsUpdateFile1|FsDirMove)$ 1106 t.Run("FsDirMove", func(t *testing.T) { 1107 skipIfNotOk(t) 1108 1109 // Check have DirMove 1110 doDirMove := remote.Features().DirMove 1111 if doDirMove == nil { 1112 t.Skip("FS has no DirMover interface") 1113 } 1114 1115 // Check it can't move onto itself 1116 err := doDirMove(context.Background(), remote, "", "") 1117 require.Equal(t, fs.ErrorDirExists, err) 1118 1119 // new remote 1120 newRemote, _, removeNewRemote, err := fstest.RandomRemote(remoteName, false) 1121 require.NoError(t, err) 1122 defer removeNewRemote() 1123 1124 const newName = "new_name/sub_new_name" 1125 // try the move 1126 err = newRemote.Features().DirMove(context.Background(), remote, "", newName) 1127 require.NoError(t, err) 1128 1129 // check remotes 1130 // remote should not exist here 1131 _, err = remote.List(context.Background(), "") 1132 assert.Equal(t, fs.ErrorDirNotFound, errors.Cause(err)) 1133 //fstest.CheckListingWithPrecision(t, remote, []fstest.Item{}, []string{}, remote.Precision()) 1134 file1Copy := file1 1135 file1Copy.Path = path.Join(newName, file1.Path) 1136 file2Copy := file2 1137 file2Copy.Path = path.Join(newName, file2.Path) 1138 file2Copy.WinPath = path.Join(newName, file2.WinPath) 1139 fstest.CheckListingWithPrecision(t, newRemote, []fstest.Item{file2Copy, file1Copy}, []string{ 1140 "new_name", 1141 "new_name/sub_new_name", 1142 "new_name/sub_new_name/hello? sausage", 1143 "new_name/sub_new_name/hello? sausage/êé", 1144 "new_name/sub_new_name/hello? sausage/êé/Hello, 世界", 1145 "new_name/sub_new_name/hello? sausage/êé/Hello, 世界/ \" ' @ < > & ? + ≠", 1146 }, newRemote.Precision()) 1147 1148 // move it back 1149 err = doDirMove(context.Background(), newRemote, newName, "") 1150 require.NoError(t, err) 1151 1152 // check remotes 1153 fstest.CheckListingWithPrecision(t, remote, []fstest.Item{file2, file1}, []string{ 1154 "hello? sausage", 1155 "hello? sausage/êé", 1156 "hello? sausage/êé/Hello, 世界", 1157 "hello? sausage/êé/Hello, 世界/ \" ' @ < > & ? + ≠", 1158 }, remote.Precision()) 1159 fstest.CheckListingWithPrecision(t, newRemote, []fstest.Item{}, []string{ 1160 "new_name", 1161 }, newRemote.Precision()) 1162 }) 1163 1164 // TestFsRmdirFull tests removing a non empty directory 1165 t.Run("FsRmdirFull", func(t *testing.T) { 1166 skipIfNotOk(t) 1167 if isBucketBasedButNotRoot(remote) { 1168 t.Skip("Skipping test as non root bucket based remote") 1169 } 1170 err := remote.Rmdir(context.Background(), "") 1171 require.Error(t, err, "Expecting error on RMdir on non empty remote") 1172 }) 1173 1174 // TestFsPrecision tests the Precision of the Fs 1175 t.Run("FsPrecision", func(t *testing.T) { 1176 skipIfNotOk(t) 1177 precision := remote.Precision() 1178 if precision == fs.ModTimeNotSupported { 1179 return 1180 } 1181 if precision > time.Second || precision < 0 { 1182 t.Fatalf("Precision out of range %v", precision) 1183 } 1184 // FIXME check expected precision 1185 }) 1186 1187 // TestObjectString tests the Object String method 1188 t.Run("ObjectString", func(t *testing.T) { 1189 skipIfNotOk(t) 1190 obj := findObject(t, remote, file1.Path) 1191 assert.Equal(t, file1.Path, obj.String()) 1192 if opt.NilObject != nil { 1193 assert.Equal(t, "<nil>", opt.NilObject.String()) 1194 } 1195 }) 1196 1197 // TestObjectFs tests the object can be found 1198 t.Run("ObjectFs", func(t *testing.T) { 1199 skipIfNotOk(t) 1200 obj := findObject(t, remote, file1.Path) 1201 // If this is set we don't do the direct comparison of 1202 // the Fs from the object as it may be different 1203 if opt.SkipFsMatch { 1204 return 1205 } 1206 testRemote := remote 1207 if obj.Fs() != testRemote { 1208 // Check to see if this wraps something else 1209 if doUnWrap := testRemote.Features().UnWrap; doUnWrap != nil { 1210 testRemote = doUnWrap() 1211 } 1212 } 1213 assert.Equal(t, obj.Fs(), testRemote) 1214 }) 1215 1216 // TestObjectRemote tests the Remote is correct 1217 t.Run("ObjectRemote", func(t *testing.T) { 1218 skipIfNotOk(t) 1219 obj := findObject(t, remote, file1.Path) 1220 assert.Equal(t, file1.Path, obj.Remote()) 1221 }) 1222 1223 // TestObjectHashes checks all the hashes the object supports 1224 t.Run("ObjectHashes", func(t *testing.T) { 1225 skipIfNotOk(t) 1226 obj := findObject(t, remote, file1.Path) 1227 file1.CheckHashes(t, obj) 1228 }) 1229 1230 // TestObjectModTime tests the ModTime of the object is correct 1231 TestObjectModTime := func(t *testing.T) { 1232 skipIfNotOk(t) 1233 obj := findObject(t, remote, file1.Path) 1234 file1.CheckModTime(t, obj, obj.ModTime(context.Background()), remote.Precision()) 1235 } 1236 t.Run("ObjectModTime", TestObjectModTime) 1237 1238 // TestObjectMimeType tests the MimeType of the object is correct 1239 t.Run("ObjectMimeType", func(t *testing.T) { 1240 skipIfNotOk(t) 1241 obj := findObject(t, remote, file1.Path) 1242 do, ok := obj.(fs.MimeTyper) 1243 if !ok { 1244 t.Skip("MimeType method not supported") 1245 } 1246 mimeType := do.MimeType(context.Background()) 1247 if strings.ContainsRune(mimeType, ';') { 1248 assert.Equal(t, "text/plain; charset=utf-8", mimeType) 1249 } else { 1250 assert.Equal(t, "text/plain", mimeType) 1251 } 1252 }) 1253 1254 // TestObjectSetModTime tests that SetModTime works 1255 t.Run("ObjectSetModTime", func(t *testing.T) { 1256 skipIfNotOk(t) 1257 newModTime := fstest.Time("2011-12-13T14:15:16.999999999Z") 1258 obj := findObject(t, remote, file1.Path) 1259 err := obj.SetModTime(context.Background(), newModTime) 1260 if err == fs.ErrorCantSetModTime || err == fs.ErrorCantSetModTimeWithoutDelete { 1261 t.Log(err) 1262 return 1263 } 1264 require.NoError(t, err) 1265 file1.ModTime = newModTime 1266 file1.CheckModTime(t, obj, obj.ModTime(context.Background()), remote.Precision()) 1267 // And make a new object and read it from there too 1268 TestObjectModTime(t) 1269 }) 1270 1271 // TestObjectSize tests that Size works 1272 t.Run("ObjectSize", func(t *testing.T) { 1273 skipIfNotOk(t) 1274 obj := findObject(t, remote, file1.Path) 1275 assert.Equal(t, file1.Size, obj.Size()) 1276 }) 1277 1278 // TestObjectOpen tests that Open works 1279 t.Run("ObjectOpen", func(t *testing.T) { 1280 skipIfNotOk(t) 1281 obj := findObject(t, remote, file1.Path) 1282 assert.Equal(t, file1Contents, readObject(t, obj, -1), "contents of file1 differ") 1283 }) 1284 1285 // TestObjectOpenSeek tests that Open works with SeekOption 1286 t.Run("ObjectOpenSeek", func(t *testing.T) { 1287 skipIfNotOk(t) 1288 obj := findObject(t, remote, file1.Path) 1289 assert.Equal(t, file1Contents[50:], readObject(t, obj, -1, &fs.SeekOption{Offset: 50}), "contents of file1 differ after seek") 1290 }) 1291 1292 // TestObjectOpenRange tests that Open works with RangeOption 1293 // 1294 // go test -v -run 'TestIntegration/Test(Setup|Init|FsMkdir|FsPutFile1|FsPutFile2|FsUpdateFile1|ObjectOpenRange)$' 1295 t.Run("ObjectOpenRange", func(t *testing.T) { 1296 skipIfNotOk(t) 1297 obj := findObject(t, remote, file1.Path) 1298 for _, test := range []struct { 1299 ro fs.RangeOption 1300 wantStart, wantEnd int 1301 }{ 1302 {fs.RangeOption{Start: 5, End: 15}, 5, 16}, 1303 {fs.RangeOption{Start: 80, End: -1}, 80, 100}, 1304 {fs.RangeOption{Start: 81, End: 100000}, 81, 100}, 1305 {fs.RangeOption{Start: -1, End: 20}, 80, 100}, // if start is omitted this means get the final bytes 1306 // {fs.RangeOption{Start: -1, End: -1}, 0, 100}, - this seems to work but the RFC doesn't define it 1307 } { 1308 got := readObject(t, obj, -1, &test.ro) 1309 foundAt := strings.Index(file1Contents, got) 1310 help := fmt.Sprintf("%#v failed want [%d:%d] got [%d:%d]", test.ro, test.wantStart, test.wantEnd, foundAt, foundAt+len(got)) 1311 assert.Equal(t, file1Contents[test.wantStart:test.wantEnd], got, help) 1312 } 1313 }) 1314 1315 // TestObjectPartialRead tests that reading only part of the object does the correct thing 1316 t.Run("ObjectPartialRead", func(t *testing.T) { 1317 skipIfNotOk(t) 1318 obj := findObject(t, remote, file1.Path) 1319 assert.Equal(t, file1Contents[:50], readObject(t, obj, 50), "contents of file1 differ after limited read") 1320 }) 1321 1322 // TestObjectUpdate tests that Update works 1323 t.Run("ObjectUpdate", func(t *testing.T) { 1324 skipIfNotOk(t) 1325 contents := fstest.RandomString(200) 1326 buf := bytes.NewBufferString(contents) 1327 hash := hash.NewMultiHasher() 1328 in := io.TeeReader(buf, hash) 1329 1330 file1.Size = int64(buf.Len()) 1331 obj := findObject(t, remote, file1.Path) 1332 obji := object.NewStaticObjectInfo(file1.Path, file1.ModTime, int64(len(contents)), true, nil, obj.Fs()) 1333 err := obj.Update(context.Background(), in, obji) 1334 require.NoError(t, err) 1335 file1.Hashes = hash.Sums() 1336 1337 // check the object has been updated 1338 file1.Check(t, obj, remote.Precision()) 1339 1340 // Re-read the object and check again 1341 obj = findObject(t, remote, file1.Path) 1342 file1.Check(t, obj, remote.Precision()) 1343 1344 // check contents correct 1345 assert.Equal(t, contents, readObject(t, obj, -1), "contents of updated file1 differ") 1346 file1Contents = contents 1347 }) 1348 1349 // TestObjectStorable tests that Storable works 1350 t.Run("ObjectStorable", func(t *testing.T) { 1351 skipIfNotOk(t) 1352 obj := findObject(t, remote, file1.Path) 1353 require.NotNil(t, !obj.Storable(), "Expecting object to be storable") 1354 }) 1355 1356 // TestFsIsFile tests that an error is returned along with a valid fs 1357 // which points to the parent directory. 1358 t.Run("FsIsFile", func(t *testing.T) { 1359 skipIfNotOk(t) 1360 remoteName := subRemoteName + "/" + file2.Path 1361 file2Copy := file2 1362 file2Copy.Path = "z.txt" 1363 file2Copy.WinPath = "" 1364 fileRemote, err := fs.NewFs(remoteName) 1365 require.NotNil(t, fileRemote) 1366 assert.Equal(t, fs.ErrorIsFile, err) 1367 fstest.CheckListing(t, fileRemote, []fstest.Item{file2Copy}) 1368 }) 1369 1370 // TestFsIsFileNotFound tests that an error is not returned if no object is found 1371 t.Run("FsIsFileNotFound", func(t *testing.T) { 1372 skipIfNotOk(t) 1373 remoteName := subRemoteName + "/not found.txt" 1374 fileRemote, err := fs.NewFs(remoteName) 1375 require.NoError(t, err) 1376 fstest.CheckListing(t, fileRemote, []fstest.Item{}) 1377 }) 1378 1379 // TestPublicLink tests creation of sharable, public links 1380 // go test -v -run 'TestIntegration/Test(Setup|Init|FsMkdir|FsPutFile1|FsPutFile2|FsUpdateFile1|PublicLink)$' 1381 t.Run("PublicLink", func(t *testing.T) { 1382 skipIfNotOk(t) 1383 1384 doPublicLink := remote.Features().PublicLink 1385 if doPublicLink == nil { 1386 t.Skip("FS has no PublicLinker interface") 1387 } 1388 1389 // if object not found 1390 link, err := doPublicLink(context.Background(), file1.Path+"_does_not_exist") 1391 require.Error(t, err, "Expected to get error when file doesn't exist") 1392 require.Equal(t, "", link, "Expected link to be empty on error") 1393 1394 // sharing file for the first time 1395 link1, err := doPublicLink(context.Background(), file1.Path) 1396 require.NoError(t, err) 1397 require.NotEqual(t, "", link1, "Link should not be empty") 1398 1399 link2, err := doPublicLink(context.Background(), file2.Path) 1400 require.NoError(t, err) 1401 require.NotEqual(t, "", link2, "Link should not be empty") 1402 1403 require.NotEqual(t, link1, link2, "Links to different files should differ") 1404 1405 // sharing file for the 2nd time 1406 link1, err = doPublicLink(context.Background(), file1.Path) 1407 require.NoError(t, err) 1408 require.NotEqual(t, "", link1, "Link should not be empty") 1409 1410 // sharing directory for the first time 1411 path := path.Dir(file2.Path) 1412 link3, err := doPublicLink(context.Background(), path) 1413 if err != nil && errors.Cause(err) == fs.ErrorCantShareDirectories { 1414 t.Log("skipping directory tests as not supported on this backend") 1415 } else { 1416 require.NoError(t, err) 1417 require.NotEqual(t, "", link3, "Link should not be empty") 1418 1419 // sharing directory for the second time 1420 link3, err = doPublicLink(context.Background(), path) 1421 require.NoError(t, err) 1422 require.NotEqual(t, "", link3, "Link should not be empty") 1423 1424 // sharing the "root" directory in a subremote 1425 subRemote, _, removeSubRemote, err := fstest.RandomRemote(remoteName, false) 1426 require.NoError(t, err) 1427 defer removeSubRemote() 1428 // ensure sub remote isn't empty 1429 buf := bytes.NewBufferString("somecontent") 1430 obji := object.NewStaticObjectInfo("somefile", time.Now(), int64(buf.Len()), true, nil, nil) 1431 _, err = subRemote.Put(context.Background(), buf, obji) 1432 require.NoError(t, err) 1433 1434 link4, err := subRemote.Features().PublicLink(context.Background(), "") 1435 require.NoError(t, err, "Sharing root in a sub-remote should work") 1436 require.NotEqual(t, "", link4, "Link should not be empty") 1437 } 1438 }) 1439 1440 // TestSetTier tests SetTier and GetTier functionality 1441 t.Run("SetTier", func(t *testing.T) { 1442 skipIfNotSetTier(t) 1443 obj := findObject(t, remote, file1.Path) 1444 setter, ok := obj.(fs.SetTierer) 1445 assert.NotNil(t, ok) 1446 getter, ok := obj.(fs.GetTierer) 1447 assert.NotNil(t, ok) 1448 // If interfaces are supported TiersToTest should contain 1449 // at least one entry 1450 supportedTiers := opt.TiersToTest 1451 assert.NotEmpty(t, supportedTiers) 1452 // test set tier changes on supported storage classes or tiers 1453 for _, tier := range supportedTiers { 1454 err := setter.SetTier(tier) 1455 assert.Nil(t, err) 1456 got := getter.GetTier() 1457 assert.Equal(t, tier, got) 1458 } 1459 }) 1460 1461 // Check to see if Fs that wrap other Objects implement all the optional methods 1462 t.Run("ObjectCheckWrap", func(t *testing.T) { 1463 skipIfNotOk(t) 1464 if opt.SkipObjectCheckWrap { 1465 t.Skip("Skipping FsCheckWrap on this Fs") 1466 } 1467 ft := new(fs.Features).Fill(remote) 1468 if ft.UnWrap == nil { 1469 t.Skip("Not a wrapping Fs") 1470 } 1471 obj := findObject(t, remote, file1.Path) 1472 _, unsupported := fs.ObjectOptionalInterfaces(obj) 1473 for _, name := range unsupported { 1474 if !stringsContains(name, opt.UnimplementableObjectMethods) { 1475 t.Errorf("Missing Object wrapper for %s", name) 1476 } 1477 } 1478 }) 1479 1480 // TestObjectRemove tests Remove 1481 t.Run("ObjectRemove", func(t *testing.T) { 1482 skipIfNotOk(t) 1483 obj := findObject(t, remote, file1.Path) 1484 err := obj.Remove(context.Background()) 1485 require.NoError(t, err) 1486 // check listing without modtime as TestPublicLink may change the modtime 1487 fstest.CheckListingWithPrecision(t, remote, []fstest.Item{file2}, nil, fs.ModTimeNotSupported) 1488 }) 1489 1490 // TestFsPutStream tests uploading files when size is not known in advance 1491 t.Run("FsPutStream", func(t *testing.T) { 1492 skipIfNotOk(t) 1493 if remote.Features().PutStream == nil { 1494 t.Skip("FS has no PutStream interface") 1495 } 1496 1497 file := fstest.Item{ 1498 ModTime: fstest.Time("2001-02-03T04:05:06.499999999Z"), 1499 Path: "piped data.txt", 1500 Size: -1, // use unknown size during upload 1501 } 1502 1503 var ( 1504 err error 1505 obj fs.Object 1506 uploadHash *hash.MultiHasher 1507 contentSize = 100 1508 ) 1509 retry(t, "PutStream", func() error { 1510 contents := fstest.RandomString(contentSize) 1511 buf := bytes.NewBufferString(contents) 1512 uploadHash = hash.NewMultiHasher() 1513 in := io.TeeReader(buf, uploadHash) 1514 1515 file.Size = -1 1516 obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil) 1517 obj, err = remote.Features().PutStream(context.Background(), in, obji) 1518 return err 1519 }) 1520 file.Hashes = uploadHash.Sums() 1521 file.Size = int64(contentSize) // use correct size when checking 1522 file.Check(t, obj, remote.Precision()) 1523 // Re-read the object and check again 1524 obj = findObject(t, remote, file.Path) 1525 file.Check(t, obj, remote.Precision()) 1526 }) 1527 1528 // TestAbout tests the About optional interface 1529 t.Run("ObjectAbout", func(t *testing.T) { 1530 skipIfNotOk(t) 1531 1532 // Check have About 1533 doAbout := remote.Features().About 1534 if doAbout == nil { 1535 t.Skip("FS does not support About") 1536 } 1537 1538 // Can't really check the output much! 1539 usage, err := doAbout(context.Background()) 1540 require.NoError(t, err) 1541 require.NotNil(t, usage) 1542 assert.NotEqual(t, int64(0), usage.Total) 1543 }) 1544 1545 // TestInternal calls InternalTest() on the Fs 1546 t.Run("Internal", func(t *testing.T) { 1547 skipIfNotOk(t) 1548 if it, ok := remote.(InternalTester); ok { 1549 it.InternalTest(t) 1550 } else { 1551 t.Skipf("%T does not implement InternalTester", remote) 1552 } 1553 }) 1554 1555 }) 1556 1557 // TestFsUploadUnknownSize ensures Fs.Put() and Object.Update() don't panic when 1558 // src.Size() == -1 1559 t.Run("FsUploadUnknownSize", func(t *testing.T) { 1560 skipIfNotOk(t) 1561 1562 t.Run("FsPutUnknownSize", func(t *testing.T) { 1563 defer func() { 1564 assert.Nil(t, recover(), "Fs.Put() should not panic when src.Size() == -1") 1565 }() 1566 1567 contents := fstest.RandomString(100) 1568 in := bytes.NewBufferString(contents) 1569 1570 obji := object.NewStaticObjectInfo("unknown-size-put.txt", fstest.Time("2002-02-03T04:05:06.499999999Z"), -1, true, nil, nil) 1571 obj, err := remote.Put(context.Background(), in, obji) 1572 if err == nil { 1573 require.NoError(t, obj.Remove(context.Background()), "successfully uploaded unknown-sized file but failed to remove") 1574 } 1575 // if err != nil: it's okay as long as no panic 1576 }) 1577 1578 t.Run("FsUpdateUnknownSize", func(t *testing.T) { 1579 unknownSizeUpdateFile := fstest.Item{ 1580 ModTime: fstest.Time("2002-02-03T04:05:06.499999999Z"), 1581 Path: "unknown-size-update.txt", 1582 } 1583 1584 testPut(t, remote, &unknownSizeUpdateFile) 1585 1586 defer func() { 1587 assert.Nil(t, recover(), "Object.Update() should not panic when src.Size() == -1") 1588 }() 1589 1590 newContents := fstest.RandomString(200) 1591 in := bytes.NewBufferString(newContents) 1592 1593 obj := findObject(t, remote, unknownSizeUpdateFile.Path) 1594 obji := object.NewStaticObjectInfo(unknownSizeUpdateFile.Path, unknownSizeUpdateFile.ModTime, -1, true, nil, obj.Fs()) 1595 err := obj.Update(context.Background(), in, obji) 1596 if err == nil { 1597 require.NoError(t, obj.Remove(context.Background()), "successfully updated object with unknown-sized source but failed to remove") 1598 } 1599 // if err != nil: it's okay as long as no panic 1600 }) 1601 1602 }) 1603 1604 // TestFsRootCollapse tests if the root of an fs "collapses" to the 1605 // absolute root. It creates a new fs of the same backend type with its 1606 // root set to a *non-existent* folder, and attempts to read the info of 1607 // an object in that folder, whose name is taken from a directory that 1608 // exists in the absolute root. 1609 // This test is added after 1610 // https://github.com/ncw/rclone/issues/3164. 1611 t.Run("FsRootCollapse", func(t *testing.T) { 1612 deepRemoteName := subRemoteName + "/deeper/nonexisting/directory" 1613 deepRemote, err := fs.NewFs(deepRemoteName) 1614 require.NoError(t, err) 1615 1616 colonIndex := strings.IndexRune(deepRemoteName, ':') 1617 firstSlashIndex := strings.IndexRune(deepRemoteName, '/') 1618 firstDir := deepRemoteName[colonIndex+1 : firstSlashIndex] 1619 _, err = deepRemote.NewObject(context.Background(), firstDir) 1620 require.Equal(t, fs.ErrorObjectNotFound, err) 1621 // If err is not fs.ErrorObjectNotFound, it means the backend is 1622 // somehow confused about root and absolute root. 1623 }) 1624 1625 // Purge the folder 1626 err = operations.Purge(context.Background(), remote, "") 1627 require.NoError(t, err) 1628 purged = true 1629 fstest.CheckListing(t, remote, []fstest.Item{}) 1630 1631 // Check purging again if not bucket based 1632 if !isBucketBasedButNotRoot(remote) { 1633 err = operations.Purge(context.Background(), remote, "") 1634 assert.Error(t, err, "Expecting error after on second purge") 1635 } 1636 1637 }) 1638 1639 // Check directory is purged 1640 if !purged { 1641 _ = operations.Purge(context.Background(), remote, "") 1642 } 1643 1644 // Remove the local directory so we don't clutter up /tmp 1645 if strings.HasPrefix(remoteName, "/") { 1646 t.Log("remoteName", remoteName) 1647 // Remove temp directory 1648 err := os.Remove(remoteName) 1649 require.NoError(t, err) 1650 } 1651 }