rclone/vfs/file_test.go
database64128 a7a8372976
🧪 fstest: fix time tests on Windows and add convenience methods to check local and remote fs with precision
Previously only the fs being checked on gets passed to
GetModifyWindow(). However, in most tests, the test files are
generated in the local fs and transferred to the remote fs. So the
local fs time precision has to be taken into account.

This meant that on Windows the time tests failed because the
local fs has a time precision of 100ns. Checking remote items uploaded
from local fs on Windows also requires a modify window of 100ns.
2021-11-09 11:43:36 +00:00

421 lines
10 KiB
Go

package vfs
import (
"context"
"fmt"
"io/ioutil"
"os"
"testing"
"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, cleanup func()) {
opt := vfscommon.DefaultOpt
opt.CacheMode = mode
opt.WriteBack = writeBackDelay
r, vfs, cleanup = 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, cleanup
}
func TestFileMethods(t *testing.T) {
r, vfs, file, _, cleanup := fileCreate(t, vfscommon.CacheModeOff)
defer cleanup()
// 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, cleanup := fileCreate(t, cacheMode)
defer cleanup()
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 := ioutil.ReadAll(fd)
require.NoError(t, err)
assert.Equal(t, "file1 contents", string(contents))
require.NoError(t, fd.Close())
}
func TestFileOpenRead(t *testing.T) {
_, _, file, _, cleanup := fileCreate(t, vfscommon.CacheModeOff)
defer cleanup()
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
f := mockfs.NewFs(context.Background(), "test", "root")
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 := ioutil.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, _, cleanup := fileCreate(t, vfscommon.CacheModeOff)
defer cleanup()
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, _, cleanup := fileCreate(t, vfscommon.CacheModeOff)
defer cleanup()
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, _, cleanup := fileCreate(t, vfscommon.CacheModeOff)
defer cleanup()
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, _, cleanup := fileCreate(t, vfscommon.CacheModeOff)
defer cleanup()
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, cleanup := fileCreate(t, mode)
defer cleanup()
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)
})
}
}