123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- 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)
- }
|