123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- package vfs
- import (
- "context"
- "fmt"
- "io"
- "os"
- "testing"
- "unsafe"
- "github.com/rclone/rclone/fs"
- "github.com/rclone/rclone/fs/operations"
- "github.com/rclone/rclone/fstest"
- "github.com/rclone/rclone/fstest/mockfs"
- "github.com/rclone/rclone/fstest/mockobject"
- "github.com/rclone/rclone/vfs/vfscommon"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func fileCreate(t *testing.T, mode vfscommon.CacheMode) (r *fstest.Run, vfs *VFS, fh *File, item fstest.Item) {
- opt := vfscommon.DefaultOpt
- opt.CacheMode = mode
- opt.WriteBack = writeBackDelay
- r, vfs = newTestVFSOpt(t, &opt)
- file1 := r.WriteObject(context.Background(), "dir/file1", "file1 contents", t1)
- r.CheckRemoteItems(t, file1)
- node, err := vfs.Stat("dir/file1")
- require.NoError(t, err)
- require.True(t, node.Mode().IsRegular())
- return r, vfs, node.(*File), file1
- }
- func TestFileMethods(t *testing.T) {
- r, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
- // String
- assert.Equal(t, "dir/file1", file.String())
- assert.Equal(t, "<nil *File>", (*File)(nil).String())
- // IsDir
- assert.Equal(t, false, file.IsDir())
- // IsFile
- assert.Equal(t, true, file.IsFile())
- // Mode
- assert.Equal(t, vfs.Opt.FilePerms, file.Mode())
- // Name
- assert.Equal(t, "file1", file.Name())
- // Path
- assert.Equal(t, "dir/file1", file.Path())
- // Sys
- assert.Equal(t, nil, file.Sys())
- // SetSys
- file.SetSys(42)
- assert.Equal(t, 42, file.Sys())
- // Inode
- assert.NotEqual(t, uint64(0), file.Inode())
- // Node
- assert.Equal(t, file, file.Node())
- // ModTime
- assert.WithinDuration(t, t1, file.ModTime(), r.Fremote.Precision())
- // Size
- assert.Equal(t, int64(14), file.Size())
- // Sync
- assert.NoError(t, file.Sync())
- // DirEntry
- assert.Equal(t, file.o, file.DirEntry())
- // Dir
- assert.Equal(t, file.d, file.Dir())
- // VFS
- assert.Equal(t, vfs, file.VFS())
- }
- func testFileSetModTime(t *testing.T, cacheMode vfscommon.CacheMode, open bool, write bool) {
- if !canSetModTimeValue {
- t.Skip("can't set mod time")
- }
- r, vfs, file, file1 := fileCreate(t, cacheMode)
- if !canSetModTime(t, r) {
- t.Skip("can't set mod time")
- }
- var (
- err error
- fd Handle
- contents = "file1 contents"
- )
- if open {
- // Open with write intent
- if cacheMode != vfscommon.CacheModeOff {
- fd, err = file.Open(os.O_WRONLY)
- if write {
- contents = "hello contents"
- }
- } else {
- // Can't write without O_TRUNC with CacheMode Off
- fd, err = file.Open(os.O_WRONLY | os.O_TRUNC)
- if write {
- contents = "hello"
- } else {
- contents = ""
- }
- }
- require.NoError(t, err)
- // Write some data
- if write {
- _, err = fd.WriteString("hello")
- require.NoError(t, err)
- }
- }
- err = file.SetModTime(t2)
- require.NoError(t, err)
- if open {
- require.NoError(t, fd.Close())
- vfs.WaitForWriters(waitForWritersDelay)
- }
- file1 = fstest.NewItem(file1.Path, contents, t2)
- r.CheckRemoteItems(t, file1)
- vfs.Opt.ReadOnly = true
- err = file.SetModTime(t2)
- assert.Equal(t, EROFS, err)
- }
- // Test various combinations of setting mod times with and
- // without the cache and with and without opening or writing
- // to the file.
- //
- // Each of these tests a different path through the VFS code.
- func TestFileSetModTime(t *testing.T) {
- for _, cacheMode := range []vfscommon.CacheMode{vfscommon.CacheModeOff, vfscommon.CacheModeFull} {
- for _, open := range []bool{false, true} {
- for _, write := range []bool{false, true} {
- if write && !open {
- continue
- }
- t.Run(fmt.Sprintf("cache=%v,open=%v,write=%v", cacheMode, open, write), func(t *testing.T) {
- testFileSetModTime(t, cacheMode, open, write)
- })
- }
- }
- }
- }
- func fileCheckContents(t *testing.T, file *File) {
- fd, err := file.Open(os.O_RDONLY)
- require.NoError(t, err)
- contents, err := io.ReadAll(fd)
- require.NoError(t, err)
- assert.Equal(t, "file1 contents", string(contents))
- require.NoError(t, fd.Close())
- }
- func TestFileOpenRead(t *testing.T) {
- _, _, file, _ := fileCreate(t, vfscommon.CacheModeOff)
- fileCheckContents(t, file)
- }
- func TestFileOpenReadUnknownSize(t *testing.T) {
- var (
- contents = []byte("file contents")
- remote = "file.txt"
- ctx = context.Background()
- )
- // create a mock object which returns size -1
- o := mockobject.New(remote).WithContent(contents, mockobject.SeekModeNone)
- o.SetUnknownSize(true)
- assert.Equal(t, int64(-1), o.Size())
- // add it to a mock fs
- fMock, err := mockfs.NewFs(context.Background(), "test", "root", nil)
- require.NoError(t, err)
- f := fMock.(*mockfs.Fs)
- f.AddObject(o)
- testObj, err := f.NewObject(ctx, remote)
- require.NoError(t, err)
- assert.Equal(t, int64(-1), testObj.Size())
- // create a VFS from that mockfs
- vfs := New(f, nil)
- defer cleanupVFS(t, vfs)
- // find the file
- node, err := vfs.Stat(remote)
- require.NoError(t, err)
- require.True(t, node.IsFile())
- file := node.(*File)
- // open it
- fd, err := file.openRead()
- require.NoError(t, err)
- assert.Equal(t, int64(0), fd.Size())
- // check the contents are not empty even though size is empty
- gotContents, err := io.ReadAll(fd)
- require.NoError(t, err)
- assert.Equal(t, contents, gotContents)
- t.Logf("gotContents = %q", gotContents)
- // check that file size has been updated
- assert.Equal(t, int64(len(contents)), fd.Size())
- require.NoError(t, fd.Close())
- }
- func TestFileOpenWrite(t *testing.T) {
- _, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
- fd, err := file.openWrite(os.O_WRONLY | os.O_TRUNC)
- require.NoError(t, err)
- newContents := []byte("this is some new contents")
- n, err := fd.Write(newContents)
- require.NoError(t, err)
- assert.Equal(t, len(newContents), n)
- require.NoError(t, fd.Close())
- assert.Equal(t, int64(25), file.Size())
- vfs.Opt.ReadOnly = true
- _, err = file.openWrite(os.O_WRONLY | os.O_TRUNC)
- assert.Equal(t, EROFS, err)
- }
- func TestFileRemove(t *testing.T) {
- r, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
- err := file.Remove()
- require.NoError(t, err)
- r.CheckRemoteItems(t)
- vfs.Opt.ReadOnly = true
- err = file.Remove()
- assert.Equal(t, EROFS, err)
- }
- func TestFileRemoveAll(t *testing.T) {
- r, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
- err := file.RemoveAll()
- require.NoError(t, err)
- r.CheckRemoteItems(t)
- vfs.Opt.ReadOnly = true
- err = file.RemoveAll()
- assert.Equal(t, EROFS, err)
- }
- func TestFileOpen(t *testing.T) {
- _, _, file, _ := fileCreate(t, vfscommon.CacheModeOff)
- fd, err := file.Open(os.O_RDONLY)
- require.NoError(t, err)
- _, ok := fd.(*ReadFileHandle)
- assert.True(t, ok)
- require.NoError(t, fd.Close())
- fd, err = file.Open(os.O_WRONLY)
- assert.NoError(t, err)
- _, ok = fd.(*WriteFileHandle)
- assert.True(t, ok)
- require.NoError(t, fd.Close())
- fd, err = file.Open(os.O_RDWR)
- assert.NoError(t, err)
- _, ok = fd.(*WriteFileHandle)
- assert.True(t, ok)
- require.NoError(t, fd.Close())
- _, err = file.Open(3)
- assert.Equal(t, EPERM, err)
- }
- func testFileRename(t *testing.T, mode vfscommon.CacheMode, inCache bool, forceCache bool) {
- r, vfs, file, item := fileCreate(t, mode)
- if !operations.CanServerSideMove(r.Fremote) {
- t.Skip("skip as can't rename files")
- }
- rootDir, err := vfs.Root()
- require.NoError(t, err)
- // force the file into the cache if required
- if forceCache {
- // write the file with read and write
- fd, err := file.Open(os.O_RDWR | os.O_CREATE | os.O_TRUNC)
- require.NoError(t, err)
- n, err := fd.Write([]byte("file1 contents"))
- require.NoError(t, err)
- require.Equal(t, 14, n)
- require.NoError(t, file.SetModTime(item.ModTime))
- err = fd.Close()
- require.NoError(t, err)
- }
- vfs.WaitForWriters(waitForWritersDelay)
- // check file in cache
- if inCache {
- // read contents to get file in cache
- fileCheckContents(t, file)
- assert.True(t, vfs.cache.Exists(item.Path))
- }
- dir := file.Dir()
- // start with "dir/file1"
- r.CheckRemoteItems(t, item)
- // rename file to "newLeaf"
- err = dir.Rename("file1", "newLeaf", rootDir)
- require.NoError(t, err)
- item.Path = "newLeaf"
- r.CheckRemoteItems(t, item)
- // check file in cache
- if inCache {
- assert.True(t, vfs.cache.Exists(item.Path))
- }
- // check file exists in the vfs layer at its new name
- _, err = vfs.Stat("newLeaf")
- require.NoError(t, err)
- // rename it back to "dir/file1"
- err = rootDir.Rename("newLeaf", "file1", dir)
- require.NoError(t, err)
- item.Path = "dir/file1"
- r.CheckRemoteItems(t, item)
- // check file in cache
- if inCache {
- assert.True(t, vfs.cache.Exists(item.Path))
- }
- // now try renaming it with the file open
- // first open it and write to it but don't close it
- fd, err := file.Open(os.O_WRONLY | os.O_TRUNC)
- require.NoError(t, err)
- newContents := []byte("this is some new contents")
- _, err = fd.Write(newContents)
- require.NoError(t, err)
- // rename file to "newLeaf"
- err = dir.Rename("file1", "newLeaf", rootDir)
- require.NoError(t, err)
- newItem := fstest.NewItem("newLeaf", string(newContents), item.ModTime)
- // check file has been renamed immediately in the cache
- if inCache {
- assert.True(t, vfs.cache.Exists("newLeaf"))
- }
- // check file exists in the vfs layer at its new name
- _, err = vfs.Stat("newLeaf")
- require.NoError(t, err)
- // Close the file
- require.NoError(t, fd.Close())
- // Check file has now been renamed on the remote
- item.Path = "newLeaf"
- vfs.WaitForWriters(waitForWritersDelay)
- fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{newItem}, nil, fs.ModTimeNotSupported)
- }
- func TestFileRename(t *testing.T) {
- for _, test := range []struct {
- mode vfscommon.CacheMode
- inCache bool
- forceCache bool
- }{
- {mode: vfscommon.CacheModeOff, inCache: false},
- {mode: vfscommon.CacheModeMinimal, inCache: false},
- {mode: vfscommon.CacheModeMinimal, inCache: true, forceCache: true},
- {mode: vfscommon.CacheModeWrites, inCache: false},
- {mode: vfscommon.CacheModeWrites, inCache: true, forceCache: true},
- {mode: vfscommon.CacheModeFull, inCache: true},
- } {
- t.Run(fmt.Sprintf("%v,forceCache=%v", test.mode, test.forceCache), func(t *testing.T) {
- testFileRename(t, test.mode, test.inCache, test.forceCache)
- })
- }
- }
- func TestFileStructSize(t *testing.T) {
- t.Logf("File struct has size %d bytes", unsafe.Sizeof(File{}))
- }
|