rclone/vfs/vfstest/file.go

342 lines
11 KiB
Go

package vfstest
import (
"os"
"runtime"
"testing"
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/vfs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestFileModTime tests mod times on files
func TestFileModTime(t *testing.T) {
run.skipIfNoFUSE(t)
run.createFile(t, "file", "123")
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
err := run.os.Chtimes(run.path("file"), mtime, mtime)
require.NoError(t, err)
info, err := run.os.Stat(run.path("file"))
require.NoError(t, err)
// avoid errors because of timezone differences
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
run.rm(t, "file")
}
// run.os.Create without opening for write too
func osCreate(name string) (vfs.OsFiler, error) {
return run.os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
}
// run.os.Create with append
func osAppend(name string) (vfs.OsFiler, error) {
return run.os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0666)
}
// TestFileModTimeWithOpenWriters tests mod time on open files
func TestFileModTimeWithOpenWriters(t *testing.T) {
run.skipIfNoFUSE(t)
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows")
}
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
filepath := run.path("cp-archive-test")
f, err := osCreate(filepath)
require.NoError(t, err)
_, err = f.Write([]byte{104, 105})
require.NoError(t, err)
err = run.os.Chtimes(filepath, mtime, mtime)
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
run.waitForWriters()
info, err := run.os.Stat(filepath)
require.NoError(t, err)
// avoid errors because of timezone differences
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
run.rm(t, "cp-archive-test")
}
// TestSymlinks tests all the api of the VFS / Mount symlinks support
func TestSymlinks(t *testing.T) {
run.skipIfNoFUSE(t)
{
// VFS only implements os.Stat, which return information to target for symlinks, getting symlink information would require os.Lstat implementation.
// We will not bother to add Lstat implemented, but in the test we can just call os.Lstat which return the information needed when !useVFS
// this is a link to a directory
// ldl, _ := os.Lstat("/tmp/kkk/link_dir")
// ld, _ := os.Stat("/tmp/kkk/link_dir")
// LINK_DIR: Lrwxrwxrwx, false <-> drwxr-xr-x, true
// fs.Logf(nil, "LINK_DIR: %v, %v <-> %v, %v", ldl.Mode(), ldl.IsDir(), ld.Mode(), ld.IsDir())
// This is a link to a regular file
// lfl, _ := os.Lstat("/tmp/kkk/link_file")
// lf, _ := os.Stat("/tmp/kkk/link_file")
// LINK_FILE: Lrwxrwxrwx, false <-> -rw-r--r--, false
// fs.Logf(nil, "LINK_FILE: %v, %v <-> %v, %v", lfl.Mode(), lfl.IsDir(), lf.Mode(), lf.IsDir())
}
suffix := ""
if run.useVFS || !run.vfsOpt.Links {
suffix = fs.LinkSuffix
}
fs.Logf(nil, "Links: %v, useVFS: %v, suffix: %v", run.vfsOpt.Links, run.useVFS, suffix)
run.mkdir(t, "dir1")
run.mkdir(t, "dir1/sub1dir1")
run.createFile(t, "dir1/file1", "potato")
run.mkdir(t, "dir2")
run.mkdir(t, "dir2/sub1dir2")
run.createFile(t, "dir2/file1", "chicken")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
// Link to a file
run.relativeSymlink(t, "dir1/file1", "dir1file1_link"+suffix)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1file1_link"+suffix+" 10")
if run.vfsOpt.Links {
if run.useVFS {
run.checkMode(t, "dir1file1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.LinkPerms)
} else {
run.checkMode(t, "dir1file1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.FilePerms)
}
} else {
run.checkMode(t, "dir1file1_link"+suffix, run.vfsOpt.FilePerms, run.vfsOpt.FilePerms)
}
assert.Equal(t, "dir1/file1", run.readlink(t, "dir1file1_link"+suffix))
if !run.useVFS && run.vfsOpt.Links {
assert.Equal(t, "potato", run.readFile(t, "dir1file1_link"+suffix))
err := writeFile(run.path("dir1file1_link"+suffix), []byte("carrot"), 0600)
require.NoError(t, err)
assert.Equal(t, "carrot", run.readFile(t, "dir1file1_link"+suffix))
assert.Equal(t, "carrot", run.readFile(t, "dir1/file1"))
} else {
assert.Equal(t, "dir1/file1", run.readFile(t, "dir1file1_link"+suffix))
}
err := run.os.Rename(run.path("dir1file1_link"+suffix), run.path("dir1file1_link")+"_bla"+suffix)
require.NoError(t, err)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1file1_link_bla"+suffix+" 10")
assert.Equal(t, "dir1/file1", run.readlink(t, "dir1file1_link_bla"+suffix))
run.rm(t, "dir1file1_link_bla"+suffix)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
// Link to a dir
run.relativeSymlink(t, "dir1", "dir1_link"+suffix)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1_link"+suffix+" 4")
if run.vfsOpt.Links {
if run.useVFS {
run.checkMode(t, "dir1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.LinkPerms)
} else {
run.checkMode(t, "dir1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.DirPerms)
}
} else {
run.checkMode(t, "dir1_link"+suffix, run.vfsOpt.FilePerms, run.vfsOpt.FilePerms)
}
assert.Equal(t, "dir1", run.readlink(t, "dir1_link"+suffix))
fh, err := run.os.OpenFile(run.path("dir1_link"+suffix), os.O_WRONLY, 0600)
if !run.useVFS && run.vfsOpt.Links {
require.Error(t, err)
dirLinksEntries := make(dirMap)
run.readLocal(t, dirLinksEntries, "dir1_link"+suffix)
assert.Equal(t, 2, len(dirLinksEntries))
dir1Entries := make(dirMap)
run.readLocal(t, dir1Entries, "dir1")
assert.Equal(t, 2, len(dir1Entries))
} else {
require.NoError(t, err)
// Don't care about the result, in some cache mode the file can't be opened for writing, so closing would trigger an err
_ = fh.Close()
assert.Equal(t, "dir1", run.readFile(t, "dir1_link"+suffix))
}
err = run.os.Rename(run.path("dir1_link"+suffix), run.path("dir1_link")+"_bla"+suffix)
require.NoError(t, err)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1_link_bla"+suffix+" 4")
assert.Equal(t, "dir1", run.readlink(t, "dir1_link_bla"+suffix))
run.rm(t, "dir1_link_bla"+suffix) // run.rmdir works fine as well
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
// Corner case #1 - We do not allow creating regular and symlink files having the same name (ie, test.txt and test.txt.rclonelink)
// Symlink first, then regular
{
link1Name := "link1.txt" + suffix
run.relativeSymlink(t, "dir1/file1", link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt"+suffix+" 10")
fh, err = run.os.OpenFile(run.path("link1.txt"), os.O_WRONLY|os.O_CREATE, run.vfsOpt.FilePerms)
// On real mount with links enabled, that open the symlink target as expected, else that fails to create a new file
if !run.useVFS && run.vfsOpt.Links {
assert.Equal(t, true, err == nil)
// Don't care about the result, in some cache mode the file can't be opened for writing, so closing would trigger an err
_ = fh.Close()
} else {
assert.Equal(t, true, err != nil)
}
run.rm(t, link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
// Regular first, then symlink
{
link1Name := "link1.txt" + suffix
run.createFile(t, "link1.txt", "")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt 0")
err = run.os.Symlink(".", run.path(link1Name))
assert.Equal(t, true, err != nil)
run.rm(t, "link1.txt")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
// Corner case #2 - We do not allow creating directory and symlink file having the same name (ie, test and test.rclonelink)
// Symlink first, then directory
{
link1Name := "link1" + suffix
run.relativeSymlink(t, ".", link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1"+suffix+" 1")
err = run.os.Mkdir(run.path("link1"), run.vfsOpt.DirPerms)
assert.Equal(t, true, err != nil)
run.rm(t, link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
// Directory first, then symlink
{
link1Name := "link1" + suffix
run.mkdir(t, "link1")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1/")
err = run.os.Symlink(".", run.path(link1Name))
assert.Equal(t, true, err != nil)
run.rm(t, "link1")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
// Corner case #3 - We do not allow moving directory or file having the same name in a target (ie, test and test.rclonelink)
// Move symlink -> regular file
{
link1Name := "link1.txt" + suffix
run.relativeSymlink(t, ".", link1Name)
run.createFile(t, "dir1/link1.txt", "")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt"+suffix+" 1|dir1/link1.txt 0")
err = run.os.Rename(run.path(link1Name), run.path("dir1/"+link1Name))
assert.Equal(t, true, err != nil)
run.rm(t, link1Name)
run.rm(t, "dir1/link1.txt")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
// Move regular file -> symlink
{
link1Name := "link1.txt" + suffix
run.createFile(t, "link1.txt", "")
run.relativeSymlink(t, ".", "dir1/"+link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt 0|dir1/link1.txt"+suffix+" 1")
err = run.os.Rename(run.path("link1.txt"), run.path("dir1/link1.txt"))
assert.Equal(t, true, err != nil)
run.rm(t, "link1.txt")
run.rm(t, "dir1/"+link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
// Move symlink -> directory
{
link1Name := "link1" + suffix
run.relativeSymlink(t, ".", link1Name)
run.mkdir(t, "dir1/link1")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1"+suffix+" 1|dir1/link1/")
err = run.os.Rename(run.path(link1Name), run.path("dir1/"+link1Name))
assert.Equal(t, true, err != nil)
run.rm(t, link1Name)
run.rm(t, "dir1/link1")
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
// Move directory -> symlink
{
link1Name := "dir1/link1" + suffix
run.mkdir(t, "link1")
run.relativeSymlink(t, ".", link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1/|dir1/link1"+suffix+" 1")
err = run.os.Rename(run.path("link1"), run.path("dir1/link1"))
assert.Equal(t, true, err != nil)
run.rm(t, "link1")
run.rm(t, link1Name)
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
}
}