diff --git a/fstest/fstest.go b/fstest/fstest.go index 9c4ae4c7d..1924379c7 100644 --- a/fstest/fstest.go +++ b/fstest/fstest.go @@ -263,13 +263,15 @@ func filterEmptyDirs(t *testing.T, items []Item, expectedDirs []string) (newExpe return newExpectedDirs } -// CheckListingWithPrecision checks the fs to see if it has the +// CheckListingWithRoot checks the fs to see if it has the // expected contents with the given precision. // // If expectedDirs is non nil then we check those too. Note that no // directories returned is also OK as some remotes don't return // directories. -func CheckListingWithPrecision(t *testing.T, f fs.Fs, items []Item, expectedDirs []string, precision time.Duration) { +// +// dir is the directory used for the listing. +func CheckListingWithRoot(t *testing.T, f fs.Fs, dir string, items []Item, expectedDirs []string, precision time.Duration) { if expectedDirs != nil && !f.Features().CanHaveEmptyDirectories { expectedDirs = filterEmptyDirs(t, items, expectedDirs) } @@ -285,7 +287,7 @@ func CheckListingWithPrecision(t *testing.T, f fs.Fs, items []Item, expectedDirs gotListing := "" listingOK := false for i := 1; i <= retries; i++ { - objs, dirs, err = walk.GetAll(ctx, f, "", true, -1) + objs, dirs, err = walk.GetAll(ctx, f, dir, true, -1) if err != nil && err != fs.ErrorDirNotFound { t.Fatalf("Error listing: %v", err) } @@ -336,6 +338,16 @@ func CheckListingWithPrecision(t *testing.T, f fs.Fs, items []Item, expectedDirs } } +// CheckListingWithPrecision checks the fs to see if it has the +// expected contents with the given precision. +// +// If expectedDirs is non nil then we check those too. Note that no +// directories returned is also OK as some remotes don't return +// directories. +func CheckListingWithPrecision(t *testing.T, f fs.Fs, items []Item, expectedDirs []string, precision time.Duration) { + CheckListingWithRoot(t, f, "", items, expectedDirs, precision) +} + // CheckListing checks the fs to see if it has the expected contents func CheckListing(t *testing.T, f fs.Fs, items []Item) { precision := f.Precision() diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go index 40cb96a44..677a73ec0 100644 --- a/fstest/fstests/fstests.go +++ b/fstest/fstests/fstests.go @@ -26,6 +26,7 @@ import ( "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/fserrors" + "github.com/rclone/rclone/fs/fspath" "github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/object" "github.com/rclone/rclone/fs/operations" @@ -1377,6 +1378,105 @@ func Run(t *testing.T, opt *Opt) { fstest.CheckListing(t, fileRemote, []fstest.Item{}) }) + // Test that things work from the root + t.Run("FromRoot", func(t *testing.T) { + if features := remote.Features(); features.BucketBased && !features.BucketBasedRootOK { + t.Skip("Can't list from root on this remote") + } + + configName, configLeaf := fspath.Parse(subRemoteName) + if configName == "" { + configName, configLeaf = path.Split(subRemoteName) + } else { + configName += ":" + } + t.Logf("Opening root remote %q path %q from %q", configName, configLeaf, subRemoteName) + rootRemote, err := fs.NewFs(configName) + require.NoError(t, err) + + file1Root := file1 + file1Root.Path = path.Join(configLeaf, file1Root.Path) + file2Root := file2 + file2Root.Path = path.Join(configLeaf, file2Root.Path) + file2Root.WinPath = path.Join(configLeaf, file2Root.WinPath) + var dirs []string + dir := file2.Path + for { + dir = path.Dir(dir) + if dir == "" || dir == "." || dir == "/" { + break + } + dirs = append(dirs, path.Join(configLeaf, dir)) + } + + // Check that we can see file1 and file2 from the root + t.Run("List", func(t *testing.T) { + fstest.CheckListingWithRoot(t, rootRemote, configLeaf, []fstest.Item{file1Root, file2Root}, dirs, rootRemote.Precision()) + }) + + // Check that that listing the entries is OK + t.Run("ListEntries", func(t *testing.T) { + entries, err := rootRemote.List(context.Background(), configLeaf) + require.NoError(t, err) + fstest.CompareItems(t, entries, []fstest.Item{file1Root}, dirs[len(dirs)-1:], rootRemote.Precision(), "ListEntries") + }) + + // List the root with ListR + t.Run("ListR", func(t *testing.T) { + doListR := rootRemote.Features().ListR + if doListR == nil { + t.Skip("FS has no ListR interface") + } + file1Found, file2Found := false, false + stopTime := time.Now().Add(10 * time.Second) + errTooMany := errors.New("too many files") + errFound := errors.New("found") + err := doListR(context.Background(), "", func(entries fs.DirEntries) error { + for _, entry := range entries { + remote := entry.Remote() + if remote == file1Root.Path { + file1Found = true + } + if remote == file2Root.Path { + file2Found = true + } + if file1Found && file2Found { + return errFound + } + } + if time.Now().After(stopTime) { + return errTooMany + } + return nil + }) + if err != errFound && err != errTooMany { + assert.NoError(t, err) + } + if err != errTooMany { + assert.True(t, file1Found, "file1Root not found") + assert.True(t, file2Found, "file2Root not found") + } else { + t.Logf("Too many files to list - giving up") + } + }) + + // Create a new file + t.Run("Put", func(t *testing.T) { + file3Root := fstest.Item{ + ModTime: time.Now(), + Path: path.Join(configLeaf, "created from root.txt"), + } + _, file3Obj := testPut(t, rootRemote, &file3Root) + fstest.CheckListingWithRoot(t, rootRemote, configLeaf, []fstest.Item{file1Root, file2Root, file3Root}, nil, rootRemote.Precision()) + + // And then remove it + t.Run("Remove", func(t *testing.T) { + require.NoError(t, file3Obj.Remove(context.Background())) + fstest.CheckListingWithRoot(t, rootRemote, configLeaf, []fstest.Item{file1Root, file2Root}, nil, rootRemote.Precision()) + }) + }) + }) + // TestPublicLink tests creation of sharable, public links // go test -v -run 'TestIntegration/Test(Setup|Init|FsMkdir|FsPutFile1|FsPutFile2|FsUpdateFile1|PublicLink)$' t.Run("PublicLink", func(t *testing.T) {