github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/vfs/vfscache/vfscache_test.go (about) 1 package vfscache 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "sort" 10 "testing" 11 "time" 12 13 "github.com/djherbis/times" 14 _ "github.com/rclone/rclone/backend/local" // import the local backend 15 "github.com/rclone/rclone/fstest" 16 "github.com/rclone/rclone/vfs/vfscommon" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 // TestMain drives the tests 22 func TestMain(m *testing.M) { 23 fstest.TestMain(m) 24 } 25 26 // convert c.item to a string 27 func itemAsString(c *Cache) []string { 28 c.itemMu.Lock() 29 defer c.itemMu.Unlock() 30 var out []string 31 for name, item := range c.item { 32 out = append(out, fmt.Sprintf("name=%q isFile=%v opens=%d size=%d", filepath.ToSlash(name), item.isFile, item.opens, item.size)) 33 } 34 sort.Strings(out) 35 return out 36 } 37 38 func TestCacheNew(t *testing.T) { 39 r := fstest.NewRun(t) 40 defer r.Finalise() 41 42 ctx, cancel := context.WithCancel(context.Background()) 43 defer cancel() 44 45 // Disable the cache cleaner as it interferes with these tests 46 opt := vfscommon.DefaultOpt 47 opt.CachePollInterval = 0 48 c, err := New(ctx, r.Fremote, &opt) 49 require.NoError(t, err) 50 51 assert.Contains(t, c.root, "vfs") 52 assert.Contains(t, c.fcache.Root(), filepath.Base(r.Fremote.Root())) 53 assert.Equal(t, []string(nil), itemAsString(c)) 54 55 // mkdir 56 p, err := c.Mkdir("potato") 57 require.NoError(t, err) 58 assert.Equal(t, "potato", filepath.Base(p)) 59 assert.Equal(t, []string{ 60 `name="" isFile=false opens=0 size=0`, 61 }, itemAsString(c)) 62 63 fi, err := os.Stat(filepath.Dir(p)) 64 require.NoError(t, err) 65 assert.True(t, fi.IsDir()) 66 67 // get 68 item := c.get("potato") 69 item2 := c.get("potato") 70 assert.Equal(t, item, item2) 71 assert.WithinDuration(t, time.Now(), item.atime, time.Second) 72 73 // updateTime 74 //.. before 75 t1 := time.Now().Add(-60 * time.Minute) 76 c.updateStat("potato", t1, 0) 77 item = c.get("potato") 78 assert.NotEqual(t, t1, item.atime) 79 assert.Equal(t, 0, item.opens) 80 //..after 81 t2 := time.Now().Add(60 * time.Minute) 82 c.updateStat("potato", t2, 0) 83 item = c.get("potato") 84 assert.Equal(t, t2, item.atime) 85 assert.Equal(t, 0, item.opens) 86 87 // open 88 assert.Equal(t, []string{ 89 `name="" isFile=false opens=0 size=0`, 90 `name="potato" isFile=true opens=0 size=0`, 91 }, itemAsString(c)) 92 c.Open("/potato") 93 assert.Equal(t, []string{ 94 `name="" isFile=false opens=1 size=0`, 95 `name="potato" isFile=true opens=1 size=0`, 96 }, itemAsString(c)) 97 item = c.get("potato") 98 assert.WithinDuration(t, time.Now(), item.atime, time.Second) 99 assert.Equal(t, 1, item.opens) 100 101 // write the file 102 err = ioutil.WriteFile(p, []byte("hello"), 0600) 103 require.NoError(t, err) 104 105 // read its atime 106 fi, err = os.Stat(p) 107 assert.NoError(t, err) 108 atime := times.Get(fi).AccessTime() 109 110 // updateAtimes 111 item = c.get("potato") 112 item.atime = time.Now().Add(-24 * time.Hour) 113 err = c.updateStats() 114 require.NoError(t, err) 115 assert.Equal(t, []string{ 116 `name="" isFile=false opens=1 size=0`, 117 `name="potato" isFile=true opens=1 size=5`, 118 }, itemAsString(c)) 119 item = c.get("potato") 120 assert.Equal(t, atime, item.atime) 121 122 // updateAtimes - not in the cache 123 oldItem := item 124 c.itemMu.Lock() 125 delete(c.item, "potato") // remove from cache 126 c.itemMu.Unlock() 127 err = c.updateStats() 128 require.NoError(t, err) 129 assert.Equal(t, []string{ 130 `name="" isFile=false opens=1 size=0`, 131 `name="potato" isFile=true opens=0 size=5`, 132 }, itemAsString(c)) 133 item = c.get("potato") 134 assert.Equal(t, atime, item.atime) 135 c.itemMu.Lock() 136 c.item["potato"] = oldItem // restore to cache 137 c.itemMu.Unlock() 138 139 // try purging with file open 140 c.purgeOld(10 * time.Second) 141 _, err = os.Stat(p) 142 assert.NoError(t, err) 143 144 // close 145 assert.Equal(t, []string{ 146 `name="" isFile=false opens=1 size=0`, 147 `name="potato" isFile=true opens=1 size=5`, 148 }, itemAsString(c)) 149 c.updateStat("potato", t2, 6) 150 assert.Equal(t, []string{ 151 `name="" isFile=false opens=1 size=0`, 152 `name="potato" isFile=true opens=1 size=6`, 153 }, itemAsString(c)) 154 c.Close("potato/") 155 assert.Equal(t, []string{ 156 `name="" isFile=false opens=0 size=0`, 157 `name="potato" isFile=true opens=0 size=5`, 158 }, itemAsString(c)) 159 item = c.get("potato") 160 assert.WithinDuration(t, time.Now(), item.atime, time.Second) 161 assert.Equal(t, 0, item.opens) 162 163 // try purging with file closed 164 c.purgeOld(10 * time.Second) 165 // ...nothing should happen 166 _, err = os.Stat(p) 167 assert.NoError(t, err) 168 169 //.. purge again with -ve age 170 c.purgeOld(-10 * time.Second) 171 _, err = os.Stat(p) 172 assert.True(t, os.IsNotExist(err)) 173 174 // clean - have tested the internals already 175 c.clean() 176 177 // cleanup 178 err = c.CleanUp() 179 require.NoError(t, err) 180 _, err = os.Stat(c.root) 181 assert.True(t, os.IsNotExist(err)) 182 } 183 184 func TestCacheOpens(t *testing.T) { 185 r := fstest.NewRun(t) 186 defer r.Finalise() 187 188 ctx, cancel := context.WithCancel(context.Background()) 189 defer cancel() 190 191 c, err := New(ctx, r.Fremote, &vfscommon.DefaultOpt) 192 require.NoError(t, err) 193 194 assert.Equal(t, []string(nil), itemAsString(c)) 195 c.Open("potato") 196 assert.Equal(t, []string{ 197 `name="" isFile=false opens=1 size=0`, 198 `name="potato" isFile=true opens=1 size=0`, 199 }, itemAsString(c)) 200 c.Open("potato") 201 assert.Equal(t, []string{ 202 `name="" isFile=false opens=2 size=0`, 203 `name="potato" isFile=true opens=2 size=0`, 204 }, itemAsString(c)) 205 c.Close("potato") 206 assert.Equal(t, []string{ 207 `name="" isFile=false opens=1 size=0`, 208 `name="potato" isFile=true opens=1 size=0`, 209 }, itemAsString(c)) 210 c.Close("potato") 211 assert.Equal(t, []string{ 212 `name="" isFile=false opens=0 size=0`, 213 `name="potato" isFile=true opens=0 size=0`, 214 }, itemAsString(c)) 215 216 c.Open("potato") 217 c.Open("a//b/c/d/one") 218 c.Open("a/b/c/d/e/two") 219 c.Open("a/b/c/d/e/f/three") 220 assert.Equal(t, []string{ 221 `name="" isFile=false opens=4 size=0`, 222 `name="a" isFile=false opens=3 size=0`, 223 `name="a/b" isFile=false opens=3 size=0`, 224 `name="a/b/c" isFile=false opens=3 size=0`, 225 `name="a/b/c/d" isFile=false opens=3 size=0`, 226 `name="a/b/c/d/e" isFile=false opens=2 size=0`, 227 `name="a/b/c/d/e/f" isFile=false opens=1 size=0`, 228 `name="a/b/c/d/e/f/three" isFile=true opens=1 size=0`, 229 `name="a/b/c/d/e/two" isFile=true opens=1 size=0`, 230 `name="a/b/c/d/one" isFile=true opens=1 size=0`, 231 `name="potato" isFile=true opens=1 size=0`, 232 }, itemAsString(c)) 233 c.Close("potato") 234 c.Close("a/b/c/d/one") 235 c.Close("a/b/c/d/e/two") 236 c.Close("a/b/c//d/e/f/three") 237 assert.Equal(t, []string{ 238 `name="" isFile=false opens=0 size=0`, 239 `name="a" isFile=false opens=0 size=0`, 240 `name="a/b" isFile=false opens=0 size=0`, 241 `name="a/b/c" isFile=false opens=0 size=0`, 242 `name="a/b/c/d" isFile=false opens=0 size=0`, 243 `name="a/b/c/d/e" isFile=false opens=0 size=0`, 244 `name="a/b/c/d/e/f" isFile=false opens=0 size=0`, 245 `name="a/b/c/d/e/f/three" isFile=true opens=0 size=0`, 246 `name="a/b/c/d/e/two" isFile=true opens=0 size=0`, 247 `name="a/b/c/d/one" isFile=true opens=0 size=0`, 248 `name="potato" isFile=true opens=0 size=0`, 249 }, itemAsString(c)) 250 } 251 252 // test the open, mkdir, purge, close, purge sequence 253 func TestCacheOpenMkdir(t *testing.T) { 254 r := fstest.NewRun(t) 255 defer r.Finalise() 256 257 ctx, cancel := context.WithCancel(context.Background()) 258 defer cancel() 259 260 // Disable the cache cleaner as it interferes with these tests 261 opt := vfscommon.DefaultOpt 262 opt.CachePollInterval = 0 263 c, err := New(ctx, r.Fremote, &opt) 264 require.NoError(t, err) 265 266 // open 267 c.Open("sub/potato") 268 269 assert.Equal(t, []string{ 270 `name="" isFile=false opens=1 size=0`, 271 `name="sub" isFile=false opens=1 size=0`, 272 `name="sub/potato" isFile=true opens=1 size=0`, 273 }, itemAsString(c)) 274 275 // mkdir 276 p, err := c.Mkdir("sub/potato") 277 require.NoError(t, err) 278 assert.Equal(t, "potato", filepath.Base(p)) 279 assert.Equal(t, []string{ 280 `name="" isFile=false opens=1 size=0`, 281 `name="sub" isFile=false opens=1 size=0`, 282 `name="sub/potato" isFile=true opens=1 size=0`, 283 }, itemAsString(c)) 284 285 // test directory exists 286 fi, err := os.Stat(filepath.Dir(p)) 287 require.NoError(t, err) 288 assert.True(t, fi.IsDir()) 289 290 // clean the cache 291 c.purgeOld(-10 * time.Second) 292 293 // test directory still exists 294 fi, err = os.Stat(filepath.Dir(p)) 295 require.NoError(t, err) 296 assert.True(t, fi.IsDir()) 297 298 // close 299 c.Close("sub/potato") 300 301 assert.Equal(t, []string{ 302 `name="" isFile=false opens=0 size=0`, 303 `name="sub" isFile=false opens=0 size=0`, 304 `name="sub/potato" isFile=true opens=0 size=0`, 305 }, itemAsString(c)) 306 307 // clean the cache 308 c.purgeOld(-10 * time.Second) 309 c.purgeEmptyDirs() 310 311 assert.Equal(t, []string(nil), itemAsString(c)) 312 313 // test directory does not exist 314 _, err = os.Stat(filepath.Dir(p)) 315 require.True(t, os.IsNotExist(err)) 316 } 317 318 func TestCacheCacheDir(t *testing.T) { 319 r := fstest.NewRun(t) 320 defer r.Finalise() 321 322 ctx, cancel := context.WithCancel(context.Background()) 323 defer cancel() 324 325 c, err := New(ctx, r.Fremote, &vfscommon.DefaultOpt) 326 require.NoError(t, err) 327 328 assert.Equal(t, []string(nil), itemAsString(c)) 329 330 c.cacheDir("dir") 331 assert.Equal(t, []string{ 332 `name="" isFile=false opens=0 size=0`, 333 `name="dir" isFile=false opens=0 size=0`, 334 }, itemAsString(c)) 335 336 c.cacheDir("dir/sub") 337 assert.Equal(t, []string{ 338 `name="" isFile=false opens=0 size=0`, 339 `name="dir" isFile=false opens=0 size=0`, 340 `name="dir/sub" isFile=false opens=0 size=0`, 341 }, itemAsString(c)) 342 343 c.cacheDir("dir/sub2/subsub2") 344 assert.Equal(t, []string{ 345 `name="" isFile=false opens=0 size=0`, 346 `name="dir" isFile=false opens=0 size=0`, 347 `name="dir/sub" isFile=false opens=0 size=0`, 348 `name="dir/sub2" isFile=false opens=0 size=0`, 349 `name="dir/sub2/subsub2" isFile=false opens=0 size=0`, 350 }, itemAsString(c)) 351 } 352 353 func TestCachePurgeOld(t *testing.T) { 354 r := fstest.NewRun(t) 355 defer r.Finalise() 356 357 ctx, cancel := context.WithCancel(context.Background()) 358 defer cancel() 359 360 c, err := New(ctx, r.Fremote, &vfscommon.DefaultOpt) 361 require.NoError(t, err) 362 363 // Test funcs 364 var removed []string 365 removedDir := true 366 removeFile := func(name string) { 367 removed = append(removed, filepath.ToSlash(name)) 368 } 369 removeDir := func(name string) bool { 370 if removedDir { 371 removed = append(removed, filepath.ToSlash(name)+"/") 372 } 373 return removedDir 374 } 375 376 removed = nil 377 c._purgeOld(-10*time.Second, removeFile) 378 c._purgeEmptyDirs(removeDir) 379 assert.Equal(t, []string(nil), removed) 380 381 c.Open("sub/dir2/potato2") 382 c.Open("sub/dir/potato") 383 c.Close("sub/dir2/potato2") 384 c.Open("sub/dir/potato") 385 386 assert.Equal(t, []string{ 387 `name="" isFile=false opens=2 size=0`, 388 `name="sub" isFile=false opens=2 size=0`, 389 `name="sub/dir" isFile=false opens=2 size=0`, 390 `name="sub/dir/potato" isFile=true opens=2 size=0`, 391 `name="sub/dir2" isFile=false opens=0 size=0`, 392 `name="sub/dir2/potato2" isFile=true opens=0 size=0`, 393 }, itemAsString(c)) 394 395 removed = nil 396 removedDir = true 397 c._purgeOld(-10*time.Second, removeFile) 398 c._purgeEmptyDirs(removeDir) 399 assert.Equal(t, []string{ 400 "sub/dir2/potato2", 401 "sub/dir2/", 402 }, removed) 403 404 c.Close("sub/dir/potato") 405 406 removed = nil 407 removedDir = true 408 c._purgeOld(-10*time.Second, removeFile) 409 c._purgeEmptyDirs(removeDir) 410 assert.Equal(t, []string(nil), removed) 411 412 c.Close("sub/dir/potato") 413 414 assert.Equal(t, []string{ 415 `name="" isFile=false opens=0 size=0`, 416 `name="sub" isFile=false opens=0 size=0`, 417 `name="sub/dir" isFile=false opens=0 size=0`, 418 `name="sub/dir/potato" isFile=true opens=0 size=0`, 419 }, itemAsString(c)) 420 421 removed = nil 422 removedDir = false 423 c._purgeOld(10*time.Second, removeFile) 424 c._purgeEmptyDirs(removeDir) 425 assert.Equal(t, []string(nil), removed) 426 427 assert.Equal(t, []string{ 428 `name="" isFile=false opens=0 size=0`, 429 `name="sub" isFile=false opens=0 size=0`, 430 `name="sub/dir" isFile=false opens=0 size=0`, 431 `name="sub/dir/potato" isFile=true opens=0 size=0`, 432 }, itemAsString(c)) 433 434 removed = nil 435 removedDir = true 436 c._purgeOld(-10*time.Second, removeFile) 437 c._purgeEmptyDirs(removeDir) 438 assert.Equal(t, []string{ 439 "sub/dir/potato", 440 "sub/dir/", 441 "sub/", 442 "/", 443 }, removed) 444 445 assert.Equal(t, []string(nil), itemAsString(c)) 446 } 447 448 func TestCachePurgeOverQuota(t *testing.T) { 449 r := fstest.NewRun(t) 450 defer r.Finalise() 451 452 ctx, cancel := context.WithCancel(context.Background()) 453 defer cancel() 454 455 // Disable the cache cleaner as it interferes with these tests 456 opt := vfscommon.DefaultOpt 457 opt.CachePollInterval = 0 458 c, err := New(ctx, r.Fremote, &opt) 459 require.NoError(t, err) 460 461 // Test funcs 462 var removed []string 463 remove := func(name string) { 464 removed = append(removed, filepath.ToSlash(name)) 465 c.Remove(name) 466 } 467 468 removed = nil 469 c._purgeOverQuota(-1, remove) 470 assert.Equal(t, []string(nil), removed) 471 472 removed = nil 473 c._purgeOverQuota(0, remove) 474 assert.Equal(t, []string(nil), removed) 475 476 removed = nil 477 c._purgeOverQuota(1, remove) 478 assert.Equal(t, []string(nil), removed) 479 480 // Make some test files 481 c.Open("sub/dir/potato") 482 p, err := c.Mkdir("sub/dir/potato") 483 require.NoError(t, err) 484 err = ioutil.WriteFile(p, []byte("hello"), 0600) 485 require.NoError(t, err) 486 487 p, err = c.Mkdir("sub/dir2/potato2") 488 c.Open("sub/dir2/potato2") 489 require.NoError(t, err) 490 err = ioutil.WriteFile(p, []byte("hello2"), 0600) 491 require.NoError(t, err) 492 493 assert.Equal(t, []string{ 494 `name="" isFile=false opens=2 size=0`, 495 `name="sub" isFile=false opens=2 size=0`, 496 `name="sub/dir" isFile=false opens=1 size=0`, 497 `name="sub/dir/potato" isFile=true opens=1 size=0`, 498 `name="sub/dir2" isFile=false opens=1 size=0`, 499 `name="sub/dir2/potato2" isFile=true opens=1 size=0`, 500 }, itemAsString(c)) 501 502 // Check nothing removed 503 removed = nil 504 c._purgeOverQuota(1, remove) 505 assert.Equal(t, []string(nil), removed) 506 507 // Close the files 508 c.Close("sub/dir/potato") 509 c.Close("sub/dir2/potato2") 510 511 assert.Equal(t, []string{ 512 `name="" isFile=false opens=0 size=0`, 513 `name="sub" isFile=false opens=0 size=0`, 514 `name="sub/dir" isFile=false opens=0 size=0`, 515 `name="sub/dir/potato" isFile=true opens=0 size=5`, 516 `name="sub/dir2" isFile=false opens=0 size=0`, 517 `name="sub/dir2/potato2" isFile=true opens=0 size=6`, 518 }, itemAsString(c)) 519 520 // Update the stats to read the total size 521 err = c.updateStats() 522 require.NoError(t, err) 523 assert.Equal(t, int64(11), c.used) 524 525 // make potato2 definitely after potato 526 t1 := time.Now().Add(10 * time.Second) 527 c.updateStat("sub/dir2/potato2", t1, 6) 528 529 // Check only potato removed to get below quota 530 removed = nil 531 c._purgeOverQuota(10, remove) 532 assert.Equal(t, []string{ 533 "sub/dir/potato", 534 }, removed) 535 assert.Equal(t, int64(6), c.used) 536 537 assert.Equal(t, []string{ 538 `name="" isFile=false opens=0 size=0`, 539 `name="sub" isFile=false opens=0 size=0`, 540 `name="sub/dir" isFile=false opens=0 size=0`, 541 `name="sub/dir2" isFile=false opens=0 size=0`, 542 `name="sub/dir2/potato2" isFile=true opens=0 size=6`, 543 }, itemAsString(c)) 544 545 // Put potato back 546 c.Open("sub/dir/potato") 547 p, err = c.Mkdir("sub/dir/potato") 548 require.NoError(t, err) 549 err = ioutil.WriteFile(p, []byte("hello"), 0600) 550 require.NoError(t, err) 551 c.Close("sub/dir/potato") 552 553 // Update the stats to read the total size 554 err = c.updateStats() 555 require.NoError(t, err) 556 assert.Equal(t, int64(11), c.used) 557 558 assert.Equal(t, []string{ 559 `name="" isFile=false opens=0 size=0`, 560 `name="sub" isFile=false opens=0 size=0`, 561 `name="sub/dir" isFile=false opens=0 size=0`, 562 `name="sub/dir/potato" isFile=true opens=0 size=5`, 563 `name="sub/dir2" isFile=false opens=0 size=0`, 564 `name="sub/dir2/potato2" isFile=true opens=0 size=6`, 565 }, itemAsString(c)) 566 567 // make potato definitely after potato2 568 t2 := t1.Add(20 * time.Second) 569 c.updateStat("sub/dir/potato", t2, 5) 570 571 // Check only potato2 removed to get below quota 572 removed = nil 573 c._purgeOverQuota(10, remove) 574 assert.Equal(t, []string{ 575 "sub/dir2/potato2", 576 }, removed) 577 assert.Equal(t, int64(5), c.used) 578 c.purgeEmptyDirs() 579 580 assert.Equal(t, []string{ 581 `name="" isFile=false opens=0 size=0`, 582 `name="sub" isFile=false opens=0 size=0`, 583 `name="sub/dir" isFile=false opens=0 size=0`, 584 `name="sub/dir/potato" isFile=true opens=0 size=5`, 585 }, itemAsString(c)) 586 587 // Now purge everything 588 removed = nil 589 c._purgeOverQuota(1, remove) 590 assert.Equal(t, []string{ 591 "sub/dir/potato", 592 }, removed) 593 assert.Equal(t, int64(0), c.used) 594 c.purgeEmptyDirs() 595 596 assert.Equal(t, []string(nil), itemAsString(c)) 597 598 // Check nothing left behind 599 c.clean() 600 assert.Equal(t, int64(0), c.used) 601 assert.Equal(t, []string(nil), itemAsString(c)) 602 }