github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/file_test.go (about)

     1  package vfs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"testing"
     9  	"unsafe"
    10  
    11  	"github.com/rclone/rclone/fs"
    12  	"github.com/rclone/rclone/fs/operations"
    13  	"github.com/rclone/rclone/fstest"
    14  	"github.com/rclone/rclone/fstest/mockfs"
    15  	"github.com/rclone/rclone/fstest/mockobject"
    16  	"github.com/rclone/rclone/vfs/vfscommon"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func fileCreate(t *testing.T, mode vfscommon.CacheMode) (r *fstest.Run, vfs *VFS, fh *File, item fstest.Item) {
    22  	opt := vfscommon.DefaultOpt
    23  	opt.CacheMode = mode
    24  	opt.WriteBack = writeBackDelay
    25  	r, vfs = newTestVFSOpt(t, &opt)
    26  
    27  	file1 := r.WriteObject(context.Background(), "dir/file1", "file1 contents", t1)
    28  	r.CheckRemoteItems(t, file1)
    29  
    30  	node, err := vfs.Stat("dir/file1")
    31  	require.NoError(t, err)
    32  	require.True(t, node.Mode().IsRegular())
    33  
    34  	return r, vfs, node.(*File), file1
    35  }
    36  
    37  func TestFileMethods(t *testing.T) {
    38  	r, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
    39  
    40  	// String
    41  	assert.Equal(t, "dir/file1", file.String())
    42  	assert.Equal(t, "<nil *File>", (*File)(nil).String())
    43  
    44  	// IsDir
    45  	assert.Equal(t, false, file.IsDir())
    46  
    47  	// IsFile
    48  	assert.Equal(t, true, file.IsFile())
    49  
    50  	// Mode
    51  	assert.Equal(t, vfs.Opt.FilePerms, file.Mode())
    52  
    53  	// Name
    54  	assert.Equal(t, "file1", file.Name())
    55  
    56  	// Path
    57  	assert.Equal(t, "dir/file1", file.Path())
    58  
    59  	// Sys
    60  	assert.Equal(t, nil, file.Sys())
    61  
    62  	// SetSys
    63  	file.SetSys(42)
    64  	assert.Equal(t, 42, file.Sys())
    65  
    66  	// Inode
    67  	assert.NotEqual(t, uint64(0), file.Inode())
    68  
    69  	// Node
    70  	assert.Equal(t, file, file.Node())
    71  
    72  	// ModTime
    73  	assert.WithinDuration(t, t1, file.ModTime(), r.Fremote.Precision())
    74  
    75  	// Size
    76  	assert.Equal(t, int64(14), file.Size())
    77  
    78  	// Sync
    79  	assert.NoError(t, file.Sync())
    80  
    81  	// DirEntry
    82  	assert.Equal(t, file.o, file.DirEntry())
    83  
    84  	// Dir
    85  	assert.Equal(t, file.d, file.Dir())
    86  
    87  	// VFS
    88  	assert.Equal(t, vfs, file.VFS())
    89  }
    90  
    91  func testFileSetModTime(t *testing.T, cacheMode vfscommon.CacheMode, open bool, write bool) {
    92  	if !canSetModTimeValue {
    93  		t.Skip("can't set mod time")
    94  	}
    95  	r, vfs, file, file1 := fileCreate(t, cacheMode)
    96  	if !canSetModTime(t, r) {
    97  		t.Skip("can't set mod time")
    98  	}
    99  
   100  	var (
   101  		err      error
   102  		fd       Handle
   103  		contents = "file1 contents"
   104  	)
   105  	if open {
   106  		// Open with write intent
   107  		if cacheMode != vfscommon.CacheModeOff {
   108  			fd, err = file.Open(os.O_WRONLY)
   109  			if write {
   110  				contents = "hello contents"
   111  			}
   112  		} else {
   113  			// Can't write without O_TRUNC with CacheMode Off
   114  			fd, err = file.Open(os.O_WRONLY | os.O_TRUNC)
   115  			if write {
   116  				contents = "hello"
   117  			} else {
   118  				contents = ""
   119  			}
   120  		}
   121  		require.NoError(t, err)
   122  
   123  		// Write some data
   124  		if write {
   125  			_, err = fd.WriteString("hello")
   126  			require.NoError(t, err)
   127  		}
   128  	}
   129  
   130  	err = file.SetModTime(t2)
   131  	require.NoError(t, err)
   132  
   133  	if open {
   134  		require.NoError(t, fd.Close())
   135  		vfs.WaitForWriters(waitForWritersDelay)
   136  	}
   137  
   138  	file1 = fstest.NewItem(file1.Path, contents, t2)
   139  	r.CheckRemoteItems(t, file1)
   140  
   141  	vfs.Opt.ReadOnly = true
   142  	err = file.SetModTime(t2)
   143  	assert.Equal(t, EROFS, err)
   144  }
   145  
   146  // Test various combinations of setting mod times with and
   147  // without the cache and with and without opening or writing
   148  // to the file.
   149  //
   150  // Each of these tests a different path through the VFS code.
   151  func TestFileSetModTime(t *testing.T) {
   152  	for _, cacheMode := range []vfscommon.CacheMode{vfscommon.CacheModeOff, vfscommon.CacheModeFull} {
   153  		for _, open := range []bool{false, true} {
   154  			for _, write := range []bool{false, true} {
   155  				if write && !open {
   156  					continue
   157  				}
   158  				t.Run(fmt.Sprintf("cache=%v,open=%v,write=%v", cacheMode, open, write), func(t *testing.T) {
   159  					testFileSetModTime(t, cacheMode, open, write)
   160  				})
   161  			}
   162  		}
   163  	}
   164  }
   165  
   166  func fileCheckContents(t *testing.T, file *File) {
   167  	fd, err := file.Open(os.O_RDONLY)
   168  	require.NoError(t, err)
   169  
   170  	contents, err := io.ReadAll(fd)
   171  	require.NoError(t, err)
   172  	assert.Equal(t, "file1 contents", string(contents))
   173  
   174  	require.NoError(t, fd.Close())
   175  }
   176  
   177  func TestFileOpenRead(t *testing.T) {
   178  	_, _, file, _ := fileCreate(t, vfscommon.CacheModeOff)
   179  
   180  	fileCheckContents(t, file)
   181  }
   182  
   183  func TestFileOpenReadUnknownSize(t *testing.T) {
   184  	var (
   185  		contents = []byte("file contents")
   186  		remote   = "file.txt"
   187  		ctx      = context.Background()
   188  	)
   189  
   190  	// create a mock object which returns size -1
   191  	o := mockobject.New(remote).WithContent(contents, mockobject.SeekModeNone)
   192  	o.SetUnknownSize(true)
   193  	assert.Equal(t, int64(-1), o.Size())
   194  
   195  	// add it to a mock fs
   196  	fMock, err := mockfs.NewFs(context.Background(), "test", "root", nil)
   197  	require.NoError(t, err)
   198  	f := fMock.(*mockfs.Fs)
   199  	f.AddObject(o)
   200  	testObj, err := f.NewObject(ctx, remote)
   201  	require.NoError(t, err)
   202  	assert.Equal(t, int64(-1), testObj.Size())
   203  
   204  	// create a VFS from that mockfs
   205  	vfs := New(f, nil)
   206  	defer cleanupVFS(t, vfs)
   207  
   208  	// find the file
   209  	node, err := vfs.Stat(remote)
   210  	require.NoError(t, err)
   211  	require.True(t, node.IsFile())
   212  	file := node.(*File)
   213  
   214  	// open it
   215  	fd, err := file.openRead()
   216  	require.NoError(t, err)
   217  	assert.Equal(t, int64(0), fd.Size())
   218  
   219  	// check the contents are not empty even though size is empty
   220  	gotContents, err := io.ReadAll(fd)
   221  	require.NoError(t, err)
   222  	assert.Equal(t, contents, gotContents)
   223  	t.Logf("gotContents = %q", gotContents)
   224  
   225  	// check that file size has been updated
   226  	assert.Equal(t, int64(len(contents)), fd.Size())
   227  
   228  	require.NoError(t, fd.Close())
   229  }
   230  
   231  func TestFileOpenWrite(t *testing.T) {
   232  	_, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
   233  
   234  	fd, err := file.openWrite(os.O_WRONLY | os.O_TRUNC)
   235  	require.NoError(t, err)
   236  
   237  	newContents := []byte("this is some new contents")
   238  	n, err := fd.Write(newContents)
   239  	require.NoError(t, err)
   240  	assert.Equal(t, len(newContents), n)
   241  	require.NoError(t, fd.Close())
   242  
   243  	assert.Equal(t, int64(25), file.Size())
   244  
   245  	vfs.Opt.ReadOnly = true
   246  	_, err = file.openWrite(os.O_WRONLY | os.O_TRUNC)
   247  	assert.Equal(t, EROFS, err)
   248  }
   249  
   250  func TestFileRemove(t *testing.T) {
   251  	r, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
   252  
   253  	err := file.Remove()
   254  	require.NoError(t, err)
   255  
   256  	r.CheckRemoteItems(t)
   257  
   258  	vfs.Opt.ReadOnly = true
   259  	err = file.Remove()
   260  	assert.Equal(t, EROFS, err)
   261  }
   262  
   263  func TestFileRemoveAll(t *testing.T) {
   264  	r, vfs, file, _ := fileCreate(t, vfscommon.CacheModeOff)
   265  
   266  	err := file.RemoveAll()
   267  	require.NoError(t, err)
   268  
   269  	r.CheckRemoteItems(t)
   270  
   271  	vfs.Opt.ReadOnly = true
   272  	err = file.RemoveAll()
   273  	assert.Equal(t, EROFS, err)
   274  }
   275  
   276  func TestFileOpen(t *testing.T) {
   277  	_, _, file, _ := fileCreate(t, vfscommon.CacheModeOff)
   278  
   279  	fd, err := file.Open(os.O_RDONLY)
   280  	require.NoError(t, err)
   281  	_, ok := fd.(*ReadFileHandle)
   282  	assert.True(t, ok)
   283  	require.NoError(t, fd.Close())
   284  
   285  	fd, err = file.Open(os.O_WRONLY)
   286  	assert.NoError(t, err)
   287  	_, ok = fd.(*WriteFileHandle)
   288  	assert.True(t, ok)
   289  	require.NoError(t, fd.Close())
   290  
   291  	fd, err = file.Open(os.O_RDWR)
   292  	assert.NoError(t, err)
   293  	_, ok = fd.(*WriteFileHandle)
   294  	assert.True(t, ok)
   295  	require.NoError(t, fd.Close())
   296  
   297  	_, err = file.Open(3)
   298  	assert.Equal(t, EPERM, err)
   299  }
   300  
   301  func testFileRename(t *testing.T, mode vfscommon.CacheMode, inCache bool, forceCache bool) {
   302  	r, vfs, file, item := fileCreate(t, mode)
   303  
   304  	if !operations.CanServerSideMove(r.Fremote) {
   305  		t.Skip("skip as can't rename files")
   306  	}
   307  
   308  	rootDir, err := vfs.Root()
   309  	require.NoError(t, err)
   310  
   311  	// force the file into the cache if required
   312  	if forceCache {
   313  		// write the file with read and write
   314  		fd, err := file.Open(os.O_RDWR | os.O_CREATE | os.O_TRUNC)
   315  		require.NoError(t, err)
   316  
   317  		n, err := fd.Write([]byte("file1 contents"))
   318  		require.NoError(t, err)
   319  		require.Equal(t, 14, n)
   320  
   321  		require.NoError(t, file.SetModTime(item.ModTime))
   322  
   323  		err = fd.Close()
   324  		require.NoError(t, err)
   325  	}
   326  	vfs.WaitForWriters(waitForWritersDelay)
   327  
   328  	// check file in cache
   329  	if inCache {
   330  		// read contents to get file in cache
   331  		fileCheckContents(t, file)
   332  		assert.True(t, vfs.cache.Exists(item.Path))
   333  	}
   334  
   335  	dir := file.Dir()
   336  
   337  	// start with "dir/file1"
   338  	r.CheckRemoteItems(t, item)
   339  
   340  	// rename file to "newLeaf"
   341  	err = dir.Rename("file1", "newLeaf", rootDir)
   342  	require.NoError(t, err)
   343  
   344  	item.Path = "newLeaf"
   345  	r.CheckRemoteItems(t, item)
   346  
   347  	// check file in cache
   348  	if inCache {
   349  		assert.True(t, vfs.cache.Exists(item.Path))
   350  	}
   351  
   352  	// check file exists in the vfs layer at its new name
   353  	_, err = vfs.Stat("newLeaf")
   354  	require.NoError(t, err)
   355  
   356  	// rename it back to "dir/file1"
   357  	err = rootDir.Rename("newLeaf", "file1", dir)
   358  	require.NoError(t, err)
   359  
   360  	item.Path = "dir/file1"
   361  	r.CheckRemoteItems(t, item)
   362  
   363  	// check file in cache
   364  	if inCache {
   365  		assert.True(t, vfs.cache.Exists(item.Path))
   366  	}
   367  
   368  	// now try renaming it with the file open
   369  	// first open it and write to it but don't close it
   370  	fd, err := file.Open(os.O_WRONLY | os.O_TRUNC)
   371  	require.NoError(t, err)
   372  	newContents := []byte("this is some new contents")
   373  	_, err = fd.Write(newContents)
   374  	require.NoError(t, err)
   375  
   376  	// rename file to "newLeaf"
   377  	err = dir.Rename("file1", "newLeaf", rootDir)
   378  	require.NoError(t, err)
   379  	newItem := fstest.NewItem("newLeaf", string(newContents), item.ModTime)
   380  
   381  	// check file has been renamed immediately in the cache
   382  	if inCache {
   383  		assert.True(t, vfs.cache.Exists("newLeaf"))
   384  	}
   385  
   386  	// check file exists in the vfs layer at its new name
   387  	_, err = vfs.Stat("newLeaf")
   388  	require.NoError(t, err)
   389  
   390  	// Close the file
   391  	require.NoError(t, fd.Close())
   392  
   393  	// Check file has now been renamed on the remote
   394  	item.Path = "newLeaf"
   395  	vfs.WaitForWriters(waitForWritersDelay)
   396  	fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{newItem}, nil, fs.ModTimeNotSupported)
   397  }
   398  
   399  func TestFileRename(t *testing.T) {
   400  	for _, test := range []struct {
   401  		mode       vfscommon.CacheMode
   402  		inCache    bool
   403  		forceCache bool
   404  	}{
   405  		{mode: vfscommon.CacheModeOff, inCache: false},
   406  		{mode: vfscommon.CacheModeMinimal, inCache: false},
   407  		{mode: vfscommon.CacheModeMinimal, inCache: true, forceCache: true},
   408  		{mode: vfscommon.CacheModeWrites, inCache: false},
   409  		{mode: vfscommon.CacheModeWrites, inCache: true, forceCache: true},
   410  		{mode: vfscommon.CacheModeFull, inCache: true},
   411  	} {
   412  		t.Run(fmt.Sprintf("%v,forceCache=%v", test.mode, test.forceCache), func(t *testing.T) {
   413  			testFileRename(t, test.mode, test.inCache, test.forceCache)
   414  		})
   415  	}
   416  }
   417  
   418  func TestFileStructSize(t *testing.T) {
   419  	t.Logf("File struct has size %d bytes", unsafe.Sizeof(File{}))
   420  }