mirror of
https://github.com/rclone/rclone.git
synced 2024-12-02 13:34:02 +08:00
23f8dea182
Name() method was originally left out and defaulted to the base class which always returns empty. This trigerred incorrect behavior in serve nfs where it relied on the Name() of the interafce to figure out what file it was modifying. This method is copied from RWFileHandle struct. Added extra assert in the tests.
386 lines
9.4 KiB
Go
386 lines
9.4 KiB
Go
package vfs
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fstest"
|
|
"github.com/rclone/rclone/lib/random"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Open a file for write
|
|
func writeHandleCreate(t *testing.T) (r *fstest.Run, vfs *VFS, fh *WriteFileHandle) {
|
|
r, vfs = newTestVFS(t)
|
|
|
|
h, err := vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777)
|
|
require.NoError(t, err)
|
|
fh, ok := h.(*WriteFileHandle)
|
|
require.True(t, ok)
|
|
|
|
return r, vfs, fh
|
|
}
|
|
|
|
// Test write when underlying storage is readonly, must be run as non-root
|
|
func TestWriteFileHandleReadonly(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skipf("Skipping test on %s", runtime.GOOS)
|
|
}
|
|
if *fstest.RemoteName != "" {
|
|
t.Skip("Skipping test on non local remote")
|
|
}
|
|
r, vfs, fh := writeHandleCreate(t)
|
|
|
|
// Name
|
|
assert.Equal(t, "file1", fh.Name())
|
|
|
|
// Write a file, so underlying remote will be created
|
|
_, err := fh.Write([]byte("hello"))
|
|
assert.NoError(t, err)
|
|
|
|
err = fh.Close()
|
|
assert.NoError(t, err)
|
|
|
|
var info os.FileInfo
|
|
info, err = os.Stat(r.FremoteName)
|
|
assert.NoError(t, err)
|
|
|
|
// Remove write permission
|
|
oldMode := info.Mode()
|
|
err = os.Chmod(r.FremoteName, oldMode^(oldMode&0222))
|
|
assert.NoError(t, err)
|
|
|
|
var h Handle
|
|
h, err = vfs.OpenFile("file2", os.O_WRONLY|os.O_CREATE, 0777)
|
|
require.NoError(t, err)
|
|
|
|
var ok bool
|
|
fh, ok = h.(*WriteFileHandle)
|
|
require.True(t, ok)
|
|
|
|
// error is propagated to Close()
|
|
_, err = fh.Write([]byte("hello"))
|
|
assert.NoError(t, err)
|
|
|
|
err = fh.Close()
|
|
assert.NotNil(t, err)
|
|
|
|
// Remove should fail
|
|
err = vfs.Remove("file1")
|
|
assert.NotNil(t, err)
|
|
|
|
// Only file1 should exist
|
|
_, err = vfs.Stat("file1")
|
|
assert.NoError(t, err)
|
|
|
|
_, err = vfs.Stat("file2")
|
|
assert.Equal(t, true, errors.Is(err, os.ErrNotExist))
|
|
|
|
// Restore old permission
|
|
err = os.Chmod(r.FremoteName, oldMode)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestWriteFileHandleMethods(t *testing.T) {
|
|
r, vfs, fh := writeHandleCreate(t)
|
|
|
|
// String
|
|
assert.Equal(t, "file1 (w)", fh.String())
|
|
assert.Equal(t, "<nil *WriteFileHandle>", (*WriteFileHandle)(nil).String())
|
|
assert.Equal(t, "<nil *WriteFileHandle.file>", new(WriteFileHandle).String())
|
|
|
|
// Node
|
|
node := fh.Node()
|
|
assert.Equal(t, "file1", node.Name())
|
|
|
|
// Offset #1
|
|
assert.Equal(t, int64(0), fh.Offset())
|
|
assert.Equal(t, int64(0), node.Size())
|
|
|
|
// Write (smoke test only since heavy lifting done in WriteAt)
|
|
n, err := fh.Write([]byte("hello"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, n)
|
|
|
|
// Offset #2
|
|
assert.Equal(t, int64(5), fh.Offset())
|
|
assert.Equal(t, int64(5), node.Size())
|
|
|
|
// Stat
|
|
var fi os.FileInfo
|
|
fi, err = fh.Stat()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(5), fi.Size())
|
|
assert.Equal(t, "file1", fi.Name())
|
|
|
|
// Read
|
|
var buf = make([]byte, 16)
|
|
_, err = fh.Read(buf)
|
|
assert.Equal(t, EPERM, err)
|
|
|
|
// ReadAt
|
|
_, err = fh.ReadAt(buf, 0)
|
|
assert.Equal(t, EPERM, err)
|
|
|
|
// Sync
|
|
err = fh.Sync()
|
|
assert.NoError(t, err)
|
|
|
|
// Truncate - can only truncate where the file pointer is
|
|
err = fh.Truncate(5)
|
|
assert.NoError(t, err)
|
|
err = fh.Truncate(6)
|
|
assert.Equal(t, EPERM, err)
|
|
|
|
// Close
|
|
assert.NoError(t, fh.Close())
|
|
|
|
// Check double close
|
|
err = fh.Close()
|
|
assert.Equal(t, ECLOSED, err)
|
|
|
|
// check vfs
|
|
root, err := vfs.Root()
|
|
require.NoError(t, err)
|
|
checkListing(t, root, []string{"file1,5,false"})
|
|
|
|
// check the underlying r.Fremote but not the modtime
|
|
file1 := fstest.NewItem("file1", "hello", t1)
|
|
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{}, fs.ModTimeNotSupported)
|
|
|
|
// Check trying to open the file now it exists then closing it
|
|
// immediately is OK
|
|
h, err := vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777)
|
|
require.NoError(t, err)
|
|
assert.NoError(t, h.Close())
|
|
checkListing(t, root, []string{"file1,5,false"})
|
|
|
|
// Check trying to open the file and writing it now it exists
|
|
// returns an error
|
|
h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777)
|
|
require.NoError(t, err)
|
|
_, err = h.Write([]byte("hello1"))
|
|
require.Equal(t, EPERM, err)
|
|
assert.NoError(t, h.Close())
|
|
checkListing(t, root, []string{"file1,5,false"})
|
|
|
|
// Check opening the file with O_TRUNC does actually truncate
|
|
// it even if we don't write to it
|
|
h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
|
|
require.NoError(t, err)
|
|
err = h.Close()
|
|
if !errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
|
|
assert.NoError(t, err)
|
|
checkListing(t, root, []string{"file1,0,false"})
|
|
}
|
|
|
|
// Check opening the file with O_TRUNC and writing does work
|
|
h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
|
|
require.NoError(t, err)
|
|
_, err = h.WriteString("hello12")
|
|
require.NoError(t, err)
|
|
assert.NoError(t, h.Close())
|
|
checkListing(t, root, []string{"file1,7,false"})
|
|
}
|
|
|
|
func TestWriteFileHandleWriteAt(t *testing.T) {
|
|
r, vfs, fh := writeHandleCreate(t)
|
|
|
|
// Preconditions
|
|
assert.Equal(t, int64(0), fh.offset)
|
|
assert.False(t, fh.writeCalled)
|
|
|
|
// Write the data
|
|
n, err := fh.WriteAt([]byte("hello"), 0)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, n)
|
|
|
|
// After write
|
|
assert.Equal(t, int64(5), fh.offset)
|
|
assert.True(t, fh.writeCalled)
|
|
|
|
// Check can't seek
|
|
n, err = fh.WriteAt([]byte("hello"), 100)
|
|
assert.Equal(t, ESPIPE, err)
|
|
assert.Equal(t, 0, n)
|
|
|
|
// Write more data
|
|
n, err = fh.WriteAt([]byte(" world"), 5)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 6, n)
|
|
|
|
// Close
|
|
assert.NoError(t, fh.Close())
|
|
|
|
// Check can't write on closed handle
|
|
n, err = fh.WriteAt([]byte("hello"), 0)
|
|
assert.Equal(t, ECLOSED, err)
|
|
assert.Equal(t, 0, n)
|
|
|
|
// check vfs
|
|
root, err := vfs.Root()
|
|
require.NoError(t, err)
|
|
checkListing(t, root, []string{"file1,11,false"})
|
|
|
|
// check the underlying r.Fremote but not the modtime
|
|
file1 := fstest.NewItem("file1", "hello world", t1)
|
|
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{}, fs.ModTimeNotSupported)
|
|
}
|
|
|
|
func TestWriteFileHandleFlush(t *testing.T) {
|
|
_, vfs, fh := writeHandleCreate(t)
|
|
|
|
// Check Flush already creates file for unwritten handles, without closing it
|
|
err := fh.Flush()
|
|
assert.NoError(t, err)
|
|
assert.False(t, fh.closed)
|
|
root, err := vfs.Root()
|
|
assert.NoError(t, err)
|
|
checkListing(t, root, []string{"file1,0,false"})
|
|
|
|
// Write some data
|
|
n, err := fh.Write([]byte("hello"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, n)
|
|
|
|
// Check Flush closes file if write called
|
|
err = fh.Flush()
|
|
assert.NoError(t, err)
|
|
assert.True(t, fh.closed)
|
|
|
|
// Check flush does nothing if called again
|
|
err = fh.Flush()
|
|
assert.NoError(t, err)
|
|
assert.True(t, fh.closed)
|
|
|
|
// Check file was written properly
|
|
root, err = vfs.Root()
|
|
assert.NoError(t, err)
|
|
checkListing(t, root, []string{"file1,5,false"})
|
|
}
|
|
|
|
func TestWriteFileHandleRelease(t *testing.T) {
|
|
_, _, fh := writeHandleCreate(t)
|
|
|
|
// Check Release closes file
|
|
err := fh.Release()
|
|
if errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
|
|
t.Logf("skipping test: %v", err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.True(t, fh.closed)
|
|
|
|
// Check Release does nothing if called again
|
|
err = fh.Release()
|
|
assert.NoError(t, err)
|
|
assert.True(t, fh.closed)
|
|
}
|
|
|
|
var (
|
|
canSetModTimeOnce sync.Once
|
|
canSetModTimeValue = true
|
|
)
|
|
|
|
// returns whether the remote can set modtime
|
|
func canSetModTime(t *testing.T, r *fstest.Run) bool {
|
|
canSetModTimeOnce.Do(func() {
|
|
mtime1 := time.Date(2008, time.November, 18, 17, 32, 31, 0, time.UTC)
|
|
_ = r.WriteObject(context.Background(), "time_test", "stuff", mtime1)
|
|
obj, err := r.Fremote.NewObject(context.Background(), "time_test")
|
|
require.NoError(t, err)
|
|
mtime2 := time.Date(2009, time.November, 18, 17, 32, 31, 0, time.UTC)
|
|
err = obj.SetModTime(context.Background(), mtime2)
|
|
switch err {
|
|
case nil:
|
|
canSetModTimeValue = true
|
|
case fs.ErrorCantSetModTime, fs.ErrorCantSetModTimeWithoutDelete:
|
|
canSetModTimeValue = false
|
|
default:
|
|
require.NoError(t, err)
|
|
}
|
|
require.NoError(t, obj.Remove(context.Background()))
|
|
fs.Debugf(nil, "Can set mod time: %v", canSetModTimeValue)
|
|
})
|
|
return canSetModTimeValue
|
|
}
|
|
|
|
// tests mod time on open files
|
|
func TestWriteFileModTimeWithOpenWriters(t *testing.T) {
|
|
r, vfs, fh := writeHandleCreate(t)
|
|
|
|
if !canSetModTime(t, r) {
|
|
t.Skip("can't set mod time")
|
|
}
|
|
|
|
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
|
|
|
|
_, err := fh.Write([]byte{104, 105})
|
|
require.NoError(t, err)
|
|
|
|
err = fh.Node().SetModTime(mtime)
|
|
require.NoError(t, err)
|
|
|
|
err = fh.Close()
|
|
require.NoError(t, err)
|
|
|
|
info, err := vfs.Stat("file1")
|
|
require.NoError(t, err)
|
|
|
|
if r.Fremote.Precision() != fs.ModTimeNotSupported {
|
|
// avoid errors because of timezone differences
|
|
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
|
|
}
|
|
}
|
|
|
|
func testFileReadAt(t *testing.T, n int) {
|
|
_, vfs, fh := writeHandleCreate(t)
|
|
|
|
contents := []byte(random.String(n))
|
|
if n != 0 {
|
|
written, err := fh.Write(contents)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, n, written)
|
|
}
|
|
|
|
// Close the file without writing to it if n==0
|
|
err := fh.Close()
|
|
if errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
|
|
t.Logf("skipping test: %v", err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
|
|
// read the file back in using ReadAt into a buffer
|
|
// this simulates what mount does
|
|
rd, err := vfs.OpenFile("file1", os.O_RDONLY, 0)
|
|
require.NoError(t, err)
|
|
|
|
buf := make([]byte, 1024)
|
|
read, err := rd.ReadAt(buf, 0)
|
|
if err != io.EOF {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, read, n)
|
|
assert.Equal(t, contents, buf[:read])
|
|
|
|
err = rd.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestFileReadAtZeroLength(t *testing.T) {
|
|
testFileReadAt(t, 0)
|
|
}
|
|
|
|
func TestFileReadAtNonZeroLength(t *testing.T) {
|
|
testFileReadAt(t, 100)
|
|
}
|