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 }