rclone/vfs/vfs_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

// Test suite for vfs
package vfs
import (
"context"
"errors"
"fmt"
"io"
"os"
"testing"
"time"
_ "github.com/rclone/rclone/backend/all" // import all the backends
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fstest"
"github.com/rclone/rclone/vfs/vfscommon"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Some times used in the tests
var (
t1 = fstest.Time("2001-02-03T04:05:06.499999999Z")
t2 = fstest.Time("2011-12-25T12:59:59.123456789Z")
t3 = fstest.Time("2011-12-30T12:59:59.000000000Z")
)
// Constants uses in the tests
const (
writeBackDelay = 100 * time.Millisecond // A short writeback delay for testing
waitForWritersDelay = 30 * time.Second // time to wait for existing writers
)
// TestMain drives the tests
func TestMain(m *testing.M) {
fstest.TestMain(m)
}
// Clean up a test VFS
func cleanupVFS(t *testing.T, vfs *VFS) {
vfs.WaitForWriters(waitForWritersDelay)
err := vfs.CleanUp()
require.NoError(t, err)
vfs.Shutdown()
}
// Create a new VFS
func newTestVFSOpt(t *testing.T, opt *vfscommon.Options) (r *fstest.Run, vfs *VFS, cleanup func()) {
r = fstest.NewRun(t)
vfs = New(r.Fremote, opt)
cleanup = func() {
cleanupVFS(t, vfs)
r.Finalise()
}
return r, vfs, cleanup
}
// Create a new VFS with default options
func newTestVFS(t *testing.T) (r *fstest.Run, vfs *VFS, cleanup func()) {
return newTestVFSOpt(t, nil)
}
// Check baseHandle performs as advertised
func TestVFSbaseHandle(t *testing.T) {
fh := baseHandle{}
err := fh.Chdir()
assert.Equal(t, ENOSYS, err)
err = fh.Chmod(0)
assert.Equal(t, ENOSYS, err)
err = fh.Chown(0, 0)
assert.Equal(t, ENOSYS, err)
err = fh.Close()
assert.Equal(t, ENOSYS, err)
fd := fh.Fd()
assert.Equal(t, uintptr(0), fd)
name := fh.Name()
assert.Equal(t, "", name)
_, err = fh.Read(nil)
assert.Equal(t, ENOSYS, err)
_, err = fh.ReadAt(nil, 0)
assert.Equal(t, ENOSYS, err)
_, err = fh.Readdir(0)
assert.Equal(t, ENOSYS, err)
_, err = fh.Readdirnames(0)
assert.Equal(t, ENOSYS, err)
_, err = fh.Seek(0, io.SeekStart)
assert.Equal(t, ENOSYS, err)
_, err = fh.Stat()
assert.Equal(t, ENOSYS, err)
err = fh.Sync()
assert.Equal(t, nil, err)
err = fh.Truncate(0)
assert.Equal(t, ENOSYS, err)
_, err = fh.Write(nil)
assert.Equal(t, ENOSYS, err)
_, err = fh.WriteAt(nil, 0)
assert.Equal(t, ENOSYS, err)
_, err = fh.WriteString("")
assert.Equal(t, ENOSYS, err)
err = fh.Flush()
assert.Equal(t, ENOSYS, err)
err = fh.Release()
assert.Equal(t, ENOSYS, err)
node := fh.Node()
assert.Nil(t, node)
}
// TestNew sees if the New command works properly
func TestVFSNew(t *testing.T) {
// Check active cache has this many entries
checkActiveCacheEntries := func(i int) {
_, count := activeCacheEntries()
assert.Equal(t, i, count)
}
checkActiveCacheEntries(0)
r, vfs, cleanup := newTestVFS(t)
// Check making a VFS with nil options
var defaultOpt = vfscommon.DefaultOpt
defaultOpt.DirPerms |= os.ModeDir
assert.Equal(t, vfs.Opt, defaultOpt)
assert.Equal(t, vfs.f, r.Fremote)
checkActiveCacheEntries(1)
// Check that we get the same VFS if we ask for it again with
// the same options
vfs2 := New(r.Fremote, nil)
assert.Equal(t, fmt.Sprintf("%p", vfs), fmt.Sprintf("%p", vfs2))
checkActiveCacheEntries(1)
// Shut the new VFS down and check the cache still has stuff in
vfs2.Shutdown()
checkActiveCacheEntries(1)
cleanup()
checkActiveCacheEntries(0)
}
// TestNew sees if the New command works properly
func TestVFSNewWithOpts(t *testing.T) {
var opt = vfscommon.DefaultOpt
opt.DirPerms = 0777
opt.FilePerms = 0666
opt.Umask = 0002
_, vfs, cleanup := newTestVFSOpt(t, &opt)
defer cleanup()
assert.Equal(t, os.FileMode(0775)|os.ModeDir, vfs.Opt.DirPerms)
assert.Equal(t, os.FileMode(0664), vfs.Opt.FilePerms)
}
// TestRoot checks root directory is present and correct
func TestVFSRoot(t *testing.T) {
_, vfs, cleanup := newTestVFS(t)
defer cleanup()
root, err := vfs.Root()
require.NoError(t, err)
assert.Equal(t, vfs.root, root)
assert.True(t, root.IsDir())
assert.Equal(t, vfs.Opt.DirPerms.Perm(), root.Mode().Perm())
}
func TestVFSStat(t *testing.T) {
r, vfs, cleanup := newTestVFS(t)
defer cleanup()
file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
file2 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
r.CheckRemoteItems(t, file1, file2)
node, err := vfs.Stat("file1")
require.NoError(t, err)
assert.True(t, node.IsFile())
assert.Equal(t, "file1", node.Name())
node, err = vfs.Stat("dir")
require.NoError(t, err)
assert.True(t, node.IsDir())
assert.Equal(t, "dir", node.Name())
node, err = vfs.Stat("dir/file2")
require.NoError(t, err)
assert.True(t, node.IsFile())
assert.Equal(t, "file2", node.Name())
_, err = vfs.Stat("not found")
assert.Equal(t, os.ErrNotExist, err)
_, err = vfs.Stat("dir/not found")
assert.Equal(t, os.ErrNotExist, err)
_, err = vfs.Stat("not found/not found")
assert.Equal(t, os.ErrNotExist, err)
_, err = vfs.Stat("file1/under a file")
assert.Equal(t, os.ErrNotExist, err)
}
func TestVFSStatParent(t *testing.T) {
r, vfs, cleanup := newTestVFS(t)
defer cleanup()
file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
file2 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
r.CheckRemoteItems(t, file1, file2)
node, leaf, err := vfs.StatParent("file1")
require.NoError(t, err)
assert.True(t, node.IsDir())
assert.Equal(t, "/", node.Name())
assert.Equal(t, "file1", leaf)
node, leaf, err = vfs.StatParent("dir/file2")
require.NoError(t, err)
assert.True(t, node.IsDir())
assert.Equal(t, "dir", node.Name())
assert.Equal(t, "file2", leaf)
node, leaf, err = vfs.StatParent("not found")
require.NoError(t, err)
assert.True(t, node.IsDir())
assert.Equal(t, "/", node.Name())
assert.Equal(t, "not found", leaf)
_, _, err = vfs.StatParent("not found dir/not found")
assert.Equal(t, os.ErrNotExist, err)
_, _, err = vfs.StatParent("file1/under a file")
assert.Equal(t, os.ErrExist, err)
}
func TestVFSOpenFile(t *testing.T) {
r, vfs, cleanup := newTestVFS(t)
defer cleanup()
file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
file2 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
r.CheckRemoteItems(t, file1, file2)
fd, err := vfs.OpenFile("file1", os.O_RDONLY, 0777)
require.NoError(t, err)
assert.NotNil(t, fd)
require.NoError(t, fd.Close())
fd, err = vfs.OpenFile("dir", os.O_RDONLY, 0777)
require.NoError(t, err)
assert.NotNil(t, fd)
require.NoError(t, fd.Close())
fd, err = vfs.OpenFile("dir/new_file.txt", os.O_RDONLY, 0777)
assert.Equal(t, os.ErrNotExist, err)
assert.Nil(t, fd)
fd, err = vfs.OpenFile("dir/new_file.txt", os.O_WRONLY|os.O_CREATE, 0777)
require.NoError(t, err)
assert.NotNil(t, fd)
err = fd.Close()
if !errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
require.NoError(t, err)
}
fd, err = vfs.OpenFile("not found/new_file.txt", os.O_WRONLY|os.O_CREATE, 0777)
assert.Equal(t, os.ErrNotExist, err)
assert.Nil(t, fd)
}
func TestVFSRename(t *testing.T) {
r, vfs, cleanup := newTestVFS(t)
defer cleanup()
features := r.Fremote.Features()
if features.Move == nil && features.Copy == nil {
t.Skip("skip as can't rename files")
}
file1 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
r.CheckRemoteItems(t, file1)
err := vfs.Rename("dir/file2", "dir/file1")
require.NoError(t, err)
file1.Path = "dir/file1"
r.CheckRemoteItems(t, file1)
err = vfs.Rename("dir/file1", "file0")
require.NoError(t, err)
file1.Path = "file0"
r.CheckRemoteItems(t, file1)
err = vfs.Rename("not found/file0", "file0")
assert.Equal(t, os.ErrNotExist, err)
err = vfs.Rename("file0", "not found/file0")
assert.Equal(t, os.ErrNotExist, err)
}
func TestVFSStatfs(t *testing.T) {
r, vfs, cleanup := newTestVFS(t)
defer cleanup()
// pre-conditions
assert.Nil(t, vfs.usage)
assert.True(t, vfs.usageTime.IsZero())
aboutSupported := r.Fremote.Features().About != nil
// read
total, used, free := vfs.Statfs()
if !aboutSupported {
assert.Equal(t, int64(unknownFreeBytes), total)
assert.Equal(t, int64(unknownFreeBytes), free)
assert.Equal(t, int64(0), used)
return // can't test anything else if About not supported
}
require.NotNil(t, vfs.usage)
assert.False(t, vfs.usageTime.IsZero())
if vfs.usage.Total != nil {
assert.Equal(t, *vfs.usage.Total, total)
} else {
assert.True(t, total >= int64(unknownFreeBytes))
}
if vfs.usage.Free != nil {
assert.Equal(t, *vfs.usage.Free, free)
} else {
if vfs.usage.Total != nil && vfs.usage.Used != nil {
assert.Equal(t, free, total-used)
} else {
assert.True(t, free >= int64(unknownFreeBytes))
}
}
if vfs.usage.Used != nil {
assert.Equal(t, *vfs.usage.Used, used)
} else {
assert.Equal(t, int64(0), used)
}
// read cached
oldUsage := vfs.usage
oldTime := vfs.usageTime
total2, used2, free2 := vfs.Statfs()
assert.Equal(t, oldUsage, vfs.usage)
assert.Equal(t, total, total2)
assert.Equal(t, used, used2)
assert.Equal(t, free, free2)
assert.Equal(t, oldTime, vfs.usageTime)
}
func TestFillInMissingSizes(t *testing.T) {
const unknownFree = 10
for _, test := range []struct {
total, free, used int64
wantTotal, wantUsed, wantFree int64
}{
{
total: 20, free: 5, used: 15,
wantTotal: 20, wantFree: 5, wantUsed: 15,
},
{
total: 20, free: 5, used: -1,
wantTotal: 20, wantFree: 5, wantUsed: 15,
},
{
total: 20, free: -1, used: 15,
wantTotal: 20, wantFree: 5, wantUsed: 15,
},
{
total: 20, free: -1, used: -1,
wantTotal: 20, wantFree: 20, wantUsed: 0,
},
{
total: -1, free: 5, used: 15,
wantTotal: 20, wantFree: 5, wantUsed: 15,
},
{
total: -1, free: 15, used: -1,
wantTotal: 15, wantFree: 15, wantUsed: 0,
},
{
total: -1, free: -1, used: 15,
wantTotal: 25, wantFree: 10, wantUsed: 15,
},
{
total: -1, free: -1, used: -1,
wantTotal: 10, wantFree: 10, wantUsed: 0,
},
} {
t.Run(fmt.Sprintf("total=%d,free=%d,used=%d", test.total, test.free, test.used), func(t *testing.T) {
gotTotal, gotUsed, gotFree := fillInMissingSizes(test.total, test.used, test.free, unknownFree)
assert.Equal(t, test.wantTotal, gotTotal, "total")
assert.Equal(t, test.wantUsed, gotUsed, "used")
assert.Equal(t, test.wantFree, gotFree, "free")
})
}
}