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