github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/dir_test.go (about) 1 package vfs 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "sort" 8 "testing" 9 "time" 10 "unsafe" 11 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fs/operations" 14 "github.com/rclone/rclone/fstest" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func dirCreate(t *testing.T) (r *fstest.Run, vfs *VFS, dir *Dir, item fstest.Item) { 20 r, vfs = newTestVFS(t) 21 22 file1 := r.WriteObject(context.Background(), "dir/file1", "file1 contents", t1) 23 r.CheckRemoteItems(t, file1) 24 25 node, err := vfs.Stat("dir") 26 require.NoError(t, err) 27 require.True(t, node.IsDir()) 28 29 return r, vfs, node.(*Dir), file1 30 } 31 32 func TestDirMethods(t *testing.T) { 33 _, vfs, dir, _ := dirCreate(t) 34 35 // String 36 assert.Equal(t, "dir/", dir.String()) 37 assert.Equal(t, "<nil *Dir>", (*Dir)(nil).String()) 38 39 // IsDir 40 assert.Equal(t, true, dir.IsDir()) 41 42 // IsFile 43 assert.Equal(t, false, dir.IsFile()) 44 45 // Mode 46 assert.Equal(t, vfs.Opt.DirPerms, dir.Mode()) 47 48 // Name 49 assert.Equal(t, "dir", dir.Name()) 50 51 // Path 52 assert.Equal(t, "dir", dir.Path()) 53 54 // Sys 55 assert.Equal(t, nil, dir.Sys()) 56 57 // SetSys 58 dir.SetSys(42) 59 assert.Equal(t, 42, dir.Sys()) 60 61 // Inode 62 assert.NotEqual(t, uint64(0), dir.Inode()) 63 64 // Node 65 assert.Equal(t, dir, dir.Node()) 66 67 // ModTime 68 assert.WithinDuration(t, t1, dir.ModTime(), 100*365*24*60*60*time.Second) 69 70 // Size 71 assert.Equal(t, int64(0), dir.Size()) 72 73 // Sync 74 assert.NoError(t, dir.Sync()) 75 76 // DirEntry 77 assert.Equal(t, dir.entry, dir.DirEntry()) 78 79 // VFS 80 assert.Equal(t, vfs, dir.VFS()) 81 } 82 83 func TestDirForgetAll(t *testing.T) { 84 _, vfs, dir, file1 := dirCreate(t) 85 86 // Make sure / and dir are in cache 87 _, err := vfs.Stat(file1.Path) 88 require.NoError(t, err) 89 90 root, err := vfs.Root() 91 require.NoError(t, err) 92 93 assert.Equal(t, 1, len(root.items)) 94 assert.Equal(t, 1, len(dir.items)) 95 assert.False(t, root.read.IsZero()) 96 assert.False(t, dir.read.IsZero()) 97 98 dir.ForgetAll() 99 assert.Equal(t, 1, len(root.items)) 100 assert.Equal(t, 0, len(dir.items)) 101 assert.False(t, root.read.IsZero()) 102 assert.True(t, dir.read.IsZero()) 103 104 root.ForgetAll() 105 assert.Equal(t, 0, len(root.items)) 106 assert.Equal(t, 0, len(dir.items)) 107 assert.True(t, root.read.IsZero()) 108 } 109 110 func TestDirForgetPath(t *testing.T) { 111 _, vfs, dir, file1 := dirCreate(t) 112 113 // Make sure / and dir are in cache 114 _, err := vfs.Stat(file1.Path) 115 require.NoError(t, err) 116 117 root, err := vfs.Root() 118 require.NoError(t, err) 119 120 assert.Equal(t, 1, len(root.items)) 121 assert.Equal(t, 1, len(dir.items)) 122 assert.False(t, root.read.IsZero()) 123 assert.False(t, dir.read.IsZero()) 124 125 root.ForgetPath("dir/notfound", fs.EntryObject) 126 assert.Equal(t, 1, len(root.items)) 127 assert.Equal(t, 1, len(dir.items)) 128 assert.False(t, root.read.IsZero()) 129 assert.True(t, dir.read.IsZero()) 130 131 root.ForgetPath("dir", fs.EntryDirectory) 132 assert.Equal(t, 1, len(root.items)) 133 assert.Equal(t, 0, len(dir.items)) 134 assert.True(t, root.read.IsZero()) 135 136 root.ForgetPath("not/in/cache", fs.EntryDirectory) 137 assert.Equal(t, 1, len(root.items)) 138 assert.Equal(t, 0, len(dir.items)) 139 } 140 141 func TestDirWalk(t *testing.T) { 142 r, vfs, _, file1 := dirCreate(t) 143 144 file2 := r.WriteObject(context.Background(), "fil/a/b/c", "super long file", t1) 145 r.CheckRemoteItems(t, file1, file2) 146 147 root, err := vfs.Root() 148 require.NoError(t, err) 149 150 // Forget the cache since we put another object in 151 root.ForgetAll() 152 153 // Read the directories in 154 _, err = vfs.Stat("dir") 155 require.NoError(t, err) 156 _, err = vfs.Stat("fil/a/b") 157 require.NoError(t, err) 158 fil, err := vfs.Stat("fil") 159 require.NoError(t, err) 160 161 var result []string 162 fn := func(d *Dir) { 163 result = append(result, d.path) 164 } 165 166 result = nil 167 root.walk(fn) 168 sort.Strings(result) // sort as there is a map traversal involved 169 assert.Equal(t, []string{"", "dir", "fil", "fil/a", "fil/a/b"}, result) 170 171 assert.Nil(t, root.cachedDir("not found")) 172 if dir := root.cachedDir("dir"); assert.NotNil(t, dir) { 173 result = nil 174 dir.walk(fn) 175 assert.Equal(t, []string{"dir"}, result) 176 } 177 if dir := root.cachedDir("fil"); assert.NotNil(t, dir) { 178 result = nil 179 dir.walk(fn) 180 assert.Equal(t, []string{"fil/a/b", "fil/a", "fil"}, result) 181 } 182 if dir := fil.(*Dir); assert.NotNil(t, dir) { 183 result = nil 184 dir.walk(fn) 185 assert.Equal(t, []string{"fil/a/b", "fil/a", "fil"}, result) 186 } 187 if dir := root.cachedDir("fil/a"); assert.NotNil(t, dir) { 188 result = nil 189 dir.walk(fn) 190 assert.Equal(t, []string{"fil/a/b", "fil/a"}, result) 191 } 192 if dir := fil.(*Dir).cachedDir("a"); assert.NotNil(t, dir) { 193 result = nil 194 dir.walk(fn) 195 assert.Equal(t, []string{"fil/a/b", "fil/a"}, result) 196 } 197 if dir := root.cachedDir("fil/a"); assert.NotNil(t, dir) { 198 result = nil 199 dir.walk(fn) 200 assert.Equal(t, []string{"fil/a/b", "fil/a"}, result) 201 } 202 if dir := root.cachedDir("fil/a/b"); assert.NotNil(t, dir) { 203 result = nil 204 dir.walk(fn) 205 assert.Equal(t, []string{"fil/a/b"}, result) 206 } 207 } 208 209 func TestDirSetModTime(t *testing.T) { 210 _, vfs, dir, _ := dirCreate(t) 211 212 err := dir.SetModTime(t1) 213 require.NoError(t, err) 214 assert.WithinDuration(t, t1, dir.ModTime(), time.Second) 215 216 err = dir.SetModTime(t2) 217 require.NoError(t, err) 218 assert.WithinDuration(t, t2, dir.ModTime(), time.Second) 219 220 vfs.Opt.ReadOnly = true 221 err = dir.SetModTime(t2) 222 assert.Equal(t, EROFS, err) 223 } 224 225 func TestDirStat(t *testing.T) { 226 _, _, dir, _ := dirCreate(t) 227 228 node, err := dir.Stat("file1") 229 require.NoError(t, err) 230 _, ok := node.(*File) 231 assert.True(t, ok) 232 assert.Equal(t, int64(14), node.Size()) 233 assert.Equal(t, "file1", node.Name()) 234 235 _, err = dir.Stat("not found") 236 assert.Equal(t, ENOENT, err) 237 } 238 239 // This lists dir and checks the listing is as expected 240 func checkListing(t *testing.T, dir *Dir, want []string) { 241 var got []string 242 nodes, err := dir.ReadDirAll() 243 require.NoError(t, err) 244 for _, node := range nodes { 245 got = append(got, fmt.Sprintf("%s,%d,%v", node.Name(), node.Size(), node.IsDir())) 246 } 247 assert.Equal(t, want, got) 248 } 249 250 func TestDirReadDirAll(t *testing.T) { 251 r, vfs := newTestVFS(t) 252 253 file1 := r.WriteObject(context.Background(), "dir/file1", "file1 contents", t1) 254 file2 := r.WriteObject(context.Background(), "dir/file2", "file2- contents", t2) 255 file3 := r.WriteObject(context.Background(), "dir/subdir/file3", "file3-- contents", t3) 256 r.CheckRemoteItems(t, file1, file2, file3) 257 258 node, err := vfs.Stat("dir") 259 require.NoError(t, err) 260 dir := node.(*Dir) 261 262 checkListing(t, dir, []string{"file1,14,false", "file2,15,false", "subdir,0,true"}) 263 264 node, err = vfs.Stat("") 265 require.NoError(t, err) 266 root := node.(*Dir) 267 268 checkListing(t, root, []string{"dir,0,true"}) 269 270 node, err = vfs.Stat("dir/subdir") 271 require.NoError(t, err) 272 subdir := node.(*Dir) 273 274 checkListing(t, subdir, []string{"file3,16,false"}) 275 276 t.Run("Virtual", func(t *testing.T) { 277 // Add some virtual entries and check what happens 278 dir.AddVirtual("virtualFile", 17, false) 279 dir.AddVirtual("virtualDir", 0, true) 280 // Remove some existing entries 281 dir.DelVirtual("file2") 282 dir.DelVirtual("subdir") 283 284 checkListing(t, dir, []string{"file1,14,false", "virtualDir,0,true", "virtualFile,17,false"}) 285 286 // Now action the deletes and uploads 287 _ = r.WriteObject(context.Background(), "dir/virtualFile", "virtualFile contents", t1) 288 _ = r.WriteObject(context.Background(), "dir/virtualDir/testFile", "testFile contents", t1) 289 o, err := r.Fremote.NewObject(context.Background(), "dir/file2") 290 require.NoError(t, err) 291 require.NoError(t, o.Remove(context.Background())) 292 require.NoError(t, operations.Purge(context.Background(), r.Fremote, "dir/subdir")) 293 294 // Force a directory reload... 295 dir.invalidateDir("dir") 296 297 checkListing(t, dir, []string{"file1,14,false", "virtualDir,0,true", "virtualFile,20,false"}) 298 299 // check no virtuals left 300 dir.mu.Lock() 301 assert.Nil(t, dir.virtual) 302 dir.mu.Unlock() 303 304 // Add some virtual entries and check what happens 305 dir.AddVirtual("virtualFile2", 100, false) 306 dir.AddVirtual("virtualDir2", 0, true) 307 // Remove some existing entries 308 dir.DelVirtual("file1") 309 310 checkListing(t, dir, []string{"virtualDir,0,true", "virtualDir2,0,true", "virtualFile,20,false", "virtualFile2,100,false"}) 311 312 // Force a directory reload... 313 dir.invalidateDir("dir") 314 315 want := []string{"file1,14,false", "virtualDir,0,true", "virtualDir2,0,true", "virtualFile,20,false", "virtualFile2,100,false"} 316 features := r.Fremote.Features() 317 if features.CanHaveEmptyDirectories { 318 // snip out virtualDir2 which will only be present if can't have empty dirs 319 want = append(want[:2], want[3:]...) 320 } 321 checkListing(t, dir, want) 322 323 // Check that forgetting the root doesn't invalidate the virtual entries 324 root.ForgetAll() 325 326 checkListing(t, dir, want) 327 }) 328 } 329 330 func TestDirOpen(t *testing.T) { 331 _, _, dir, _ := dirCreate(t) 332 333 fd, err := dir.Open(os.O_RDONLY) 334 require.NoError(t, err) 335 _, ok := fd.(*DirHandle) 336 assert.True(t, ok) 337 require.NoError(t, fd.Close()) 338 339 _, err = dir.Open(os.O_WRONLY) 340 assert.Equal(t, EPERM, err) 341 } 342 343 func TestDirCreate(t *testing.T) { 344 _, vfs, dir, _ := dirCreate(t) 345 346 origModTime := dir.ModTime() 347 time.Sleep(100 * time.Millisecond) // for low rez Windows timers 348 file, err := dir.Create("potato", os.O_WRONLY|os.O_CREATE) 349 require.NoError(t, err) 350 assert.Equal(t, int64(0), file.Size()) 351 assert.True(t, dir.ModTime().After(origModTime)) 352 353 fd, err := file.Open(os.O_WRONLY | os.O_CREATE) 354 require.NoError(t, err) 355 356 // FIXME Note that this fails with the current implementation 357 // until the file has been opened. 358 359 // file2, err := vfs.Stat("dir/potato") 360 // require.NoError(t, err) 361 // assert.Equal(t, file, file2) 362 363 n, err := fd.Write([]byte("hello")) 364 require.NoError(t, err) 365 assert.Equal(t, 5, n) 366 367 require.NoError(t, fd.Close()) 368 369 file2, err := vfs.Stat("dir/potato") 370 require.NoError(t, err) 371 assert.Equal(t, int64(5), file2.Size()) 372 373 // Try creating the file again - make sure we get the same file node 374 file3, err := dir.Create("potato", os.O_RDWR|os.O_CREATE) 375 require.NoError(t, err) 376 assert.Equal(t, int64(5), file3.Size()) 377 assert.Equal(t, fmt.Sprintf("%p", file), fmt.Sprintf("%p", file3), "didn't return same node") 378 379 // Test read only fs creating new 380 vfs.Opt.ReadOnly = true 381 _, err = dir.Create("sausage", os.O_WRONLY|os.O_CREATE) 382 assert.Equal(t, EROFS, err) 383 } 384 385 func TestDirMkdir(t *testing.T) { 386 r, vfs, dir, file1 := dirCreate(t) 387 388 _, err := dir.Mkdir("file1") 389 assert.Error(t, err) 390 391 origModTime := dir.ModTime() 392 time.Sleep(100 * time.Millisecond) // for low rez Windows timers 393 sub, err := dir.Mkdir("sub") 394 assert.NoError(t, err) 395 assert.True(t, dir.ModTime().After(origModTime)) 396 397 // check the vfs 398 checkListing(t, dir, []string{"file1,14,false", "sub,0,true"}) 399 checkListing(t, sub, []string(nil)) 400 401 // check the underlying r.Fremote 402 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"dir", "dir/sub"}, r.Fremote.Precision()) 403 404 vfs.Opt.ReadOnly = true 405 _, err = dir.Mkdir("sausage") 406 assert.Equal(t, EROFS, err) 407 } 408 409 func TestDirMkdirSub(t *testing.T) { 410 r, vfs, dir, file1 := dirCreate(t) 411 412 _, err := dir.Mkdir("file1") 413 assert.Error(t, err) 414 415 sub, err := dir.Mkdir("sub") 416 assert.NoError(t, err) 417 418 subsub, err := sub.Mkdir("subsub") 419 assert.NoError(t, err) 420 421 // check the vfs 422 checkListing(t, dir, []string{"file1,14,false", "sub,0,true"}) 423 checkListing(t, sub, []string{"subsub,0,true"}) 424 checkListing(t, subsub, []string(nil)) 425 426 // check the underlying r.Fremote 427 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"dir", "dir/sub", "dir/sub/subsub"}, r.Fremote.Precision()) 428 429 vfs.Opt.ReadOnly = true 430 _, err = dir.Mkdir("sausage") 431 assert.Equal(t, EROFS, err) 432 } 433 434 func TestDirRemove(t *testing.T) { 435 r, vfs, dir, _ := dirCreate(t) 436 437 // check directory is there 438 node, err := vfs.Stat("dir") 439 require.NoError(t, err) 440 assert.True(t, node.IsDir()) 441 442 err = dir.Remove() 443 assert.Equal(t, ENOTEMPTY, err) 444 445 // Delete the sub file 446 node, err = vfs.Stat("dir/file1") 447 require.NoError(t, err) 448 err = node.Remove() 449 require.NoError(t, err) 450 451 // Remove the now empty directory 452 err = dir.Remove() 453 require.NoError(t, err) 454 455 // check directory is not there 456 _, err = vfs.Stat("dir") 457 assert.Equal(t, ENOENT, err) 458 459 // check the vfs 460 root, err := vfs.Root() 461 require.NoError(t, err) 462 checkListing(t, root, []string(nil)) 463 464 // check the underlying r.Fremote 465 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{}, r.Fremote.Precision()) 466 467 // read only check 468 vfs.Opt.ReadOnly = true 469 err = dir.Remove() 470 assert.Equal(t, EROFS, err) 471 } 472 473 func TestDirRemoveAll(t *testing.T) { 474 r, vfs, dir, _ := dirCreate(t) 475 476 // Remove the directory and contents 477 err := dir.RemoveAll() 478 require.NoError(t, err) 479 480 // check the vfs 481 root, err := vfs.Root() 482 require.NoError(t, err) 483 checkListing(t, root, []string(nil)) 484 485 // check the underlying r.Fremote 486 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{}, r.Fremote.Precision()) 487 488 // read only check 489 vfs.Opt.ReadOnly = true 490 err = dir.RemoveAll() 491 assert.Equal(t, EROFS, err) 492 } 493 494 func TestDirRemoveName(t *testing.T) { 495 r, vfs, dir, _ := dirCreate(t) 496 497 origModTime := dir.ModTime() 498 time.Sleep(100 * time.Millisecond) // for low rez Windows timers 499 err := dir.RemoveName("file1") 500 require.NoError(t, err) 501 assert.True(t, dir.ModTime().After(origModTime)) 502 checkListing(t, dir, []string(nil)) 503 root, err := vfs.Root() 504 require.NoError(t, err) 505 checkListing(t, root, []string{"dir,0,true"}) 506 507 // check the underlying r.Fremote 508 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{"dir"}, r.Fremote.Precision()) 509 510 // read only check 511 vfs.Opt.ReadOnly = true 512 err = dir.RemoveName("potato") 513 assert.Equal(t, EROFS, err) 514 } 515 516 func TestDirRename(t *testing.T) { 517 r, vfs, dir, file1 := dirCreate(t) 518 519 features := r.Fremote.Features() 520 if features.DirMove == nil && features.Move == nil && features.Copy == nil { 521 t.Skip("can't rename directories") 522 } 523 524 file3 := r.WriteObject(context.Background(), "dir/file3", "file3 contents!", t1) 525 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file3}, []string{"dir"}, r.Fremote.Precision()) 526 527 root, err := vfs.Root() 528 require.NoError(t, err) 529 530 err = dir.Rename("not found", "tuba", dir) 531 assert.Equal(t, ENOENT, err) 532 533 // Rename a directory 534 err = root.Rename("dir", "dir2", root) 535 assert.NoError(t, err) 536 checkListing(t, root, []string{"dir2,0,true"}) 537 checkListing(t, dir, []string{"file1,14,false", "file3,15,false"}) 538 539 // check the underlying r.Fremote 540 file1.Path = "dir2/file1" 541 file3.Path = "dir2/file3" 542 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file3}, []string{"dir2"}, r.Fremote.Precision()) 543 544 // refetch dir 545 node, err := vfs.Stat("dir2") 546 assert.NoError(t, err) 547 dir = node.(*Dir) 548 549 // Rename a file 550 origModTime := dir.ModTime() 551 time.Sleep(100 * time.Millisecond) // for low rez Windows timers 552 err = dir.Rename("file1", "file2", root) 553 assert.NoError(t, err) 554 assert.True(t, dir.ModTime().After(origModTime)) 555 checkListing(t, root, []string{"dir2,0,true", "file2,14,false"}) 556 checkListing(t, dir, []string{"file3,15,false"}) 557 558 // check the underlying r.Fremote 559 file1.Path = "file2" 560 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file3}, []string{"dir2"}, r.Fremote.Precision()) 561 562 // Rename a file on top of another file 563 err = root.Rename("file2", "file3", dir) 564 assert.NoError(t, err) 565 checkListing(t, root, []string{"dir2,0,true"}) 566 checkListing(t, dir, []string{"file3,14,false"}) 567 568 // check the underlying r.Fremote 569 file1.Path = "dir2/file3" 570 fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"dir2"}, r.Fremote.Precision()) 571 572 // rename an empty directory 573 _, err = root.Mkdir("empty directory") 574 assert.NoError(t, err) 575 checkListing(t, root, []string{ 576 "dir2,0,true", 577 "empty directory,0,true", 578 }) 579 err = root.Rename("empty directory", "renamed empty directory", root) 580 assert.NoError(t, err) 581 checkListing(t, root, []string{ 582 "dir2,0,true", 583 "renamed empty directory,0,true", 584 }) 585 // ...we don't check the underlying f.Fremote because on 586 // bucket-based remotes the directory won't be there 587 588 // read only check 589 vfs.Opt.ReadOnly = true 590 err = dir.Rename("potato", "tuba", dir) 591 assert.Equal(t, EROFS, err) 592 } 593 594 func TestDirStructSize(t *testing.T) { 595 t.Logf("Dir struct has size %d bytes", unsafe.Sizeof(Dir{})) 596 }