github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfscache/item_test.go (about) 1 package vfscache 2 3 // FIXME need to test async writeback here 4 5 import ( 6 "context" 7 "fmt" 8 "io" 9 "math/rand" 10 "os" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/rclone/rclone/fs" 16 "github.com/rclone/rclone/fstest" 17 "github.com/rclone/rclone/lib/random" 18 "github.com/rclone/rclone/lib/readers" 19 "github.com/rclone/rclone/vfs/vfscommon" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 ) 23 24 var zeroes = string(make([]byte, 100)) 25 26 func newItemTestCache(t *testing.T) (r *fstest.Run, c *Cache) { 27 opt := vfscommon.DefaultOpt 28 29 // Disable the cache cleaner as it interferes with these tests 30 opt.CachePollInterval = 0 31 32 // Disable synchronous write 33 opt.WriteBack = 0 34 35 return newTestCacheOpt(t, opt) 36 } 37 38 // Check the object has contents 39 func checkObject(t *testing.T, r *fstest.Run, remote string, contents string) { 40 obj, err := r.Fremote.NewObject(context.Background(), remote) 41 require.NoError(t, err) 42 in, err := obj.Open(context.Background()) 43 require.NoError(t, err) 44 buf, err := io.ReadAll(in) 45 require.NoError(t, err) 46 require.NoError(t, in.Close()) 47 assert.Equal(t, contents, string(buf)) 48 } 49 50 func newFileLength(t *testing.T, r *fstest.Run, c *Cache, remote string, length int) (contents string, obj fs.Object, item *Item) { 51 contents = random.String(length) 52 r.WriteObject(context.Background(), remote, contents, time.Now()) 53 item, _ = c.get(remote) 54 obj, err := r.Fremote.NewObject(context.Background(), remote) 55 require.NoError(t, err) 56 return 57 } 58 59 func newFile(t *testing.T, r *fstest.Run, c *Cache, remote string) (contents string, obj fs.Object, item *Item) { 60 return newFileLength(t, r, c, remote, 100) 61 } 62 63 func TestItemExists(t *testing.T) { 64 _, c := newItemTestCache(t) 65 item, _ := c.get("potato") 66 67 assert.False(t, item.Exists()) 68 require.NoError(t, item.Open(nil)) 69 assert.True(t, item.Exists()) 70 require.NoError(t, item.Close(nil)) 71 assert.True(t, item.Exists()) 72 item.remove("test") 73 assert.False(t, item.Exists()) 74 } 75 76 func TestItemGetSize(t *testing.T) { 77 r, c := newItemTestCache(t) 78 item, _ := c.get("potato") 79 require.NoError(t, item.Open(nil)) 80 81 size, err := item.GetSize() 82 require.NoError(t, err) 83 assert.Equal(t, int64(0), size) 84 85 n, err := item.WriteAt([]byte("hello"), 0) 86 require.NoError(t, err) 87 assert.Equal(t, 5, n) 88 89 size, err = item.GetSize() 90 require.NoError(t, err) 91 assert.Equal(t, int64(5), size) 92 93 require.NoError(t, item.Close(nil)) 94 checkObject(t, r, "potato", "hello") 95 } 96 97 func TestItemDirty(t *testing.T) { 98 r, c := newItemTestCache(t) 99 item, _ := c.get("potato") 100 require.NoError(t, item.Open(nil)) 101 102 assert.Equal(t, false, item.IsDirty()) 103 104 n, err := item.WriteAt([]byte("hello"), 0) 105 require.NoError(t, err) 106 assert.Equal(t, 5, n) 107 108 assert.Equal(t, true, item.IsDirty()) 109 110 require.NoError(t, item.Close(nil)) 111 112 // Sync writeback so expect clean here 113 assert.Equal(t, false, item.IsDirty()) 114 115 item.Dirty() 116 117 assert.Equal(t, true, item.IsDirty()) 118 checkObject(t, r, "potato", "hello") 119 } 120 121 func TestItemSync(t *testing.T) { 122 _, c := newItemTestCache(t) 123 item, _ := c.get("potato") 124 125 require.Error(t, item.Sync()) 126 127 require.NoError(t, item.Open(nil)) 128 129 require.NoError(t, item.Sync()) 130 131 require.NoError(t, item.Close(nil)) 132 } 133 134 func TestItemTruncateNew(t *testing.T) { 135 r, c := newItemTestCache(t) 136 item, _ := c.get("potato") 137 138 require.Error(t, item.Truncate(0)) 139 140 require.NoError(t, item.Open(nil)) 141 142 require.NoError(t, item.Truncate(100)) 143 144 size, err := item.GetSize() 145 require.NoError(t, err) 146 assert.Equal(t, int64(100), size) 147 148 // Check the Close callback works 149 callbackCalled := false 150 callback := func(o fs.Object) { 151 callbackCalled = true 152 assert.Equal(t, "potato", o.Remote()) 153 assert.Equal(t, int64(100), o.Size()) 154 } 155 require.NoError(t, item.Close(callback)) 156 assert.True(t, callbackCalled) 157 158 checkObject(t, r, "potato", zeroes) 159 } 160 161 func TestItemTruncateExisting(t *testing.T) { 162 r, c := newItemTestCache(t) 163 164 contents, obj, item := newFile(t, r, c, "existing") 165 166 require.Error(t, item.Truncate(40)) 167 checkObject(t, r, "existing", contents) 168 169 require.NoError(t, item.Open(obj)) 170 171 require.NoError(t, item.Truncate(40)) 172 173 require.NoError(t, item.Truncate(60)) 174 175 require.NoError(t, item.Close(nil)) 176 177 checkObject(t, r, "existing", contents[:40]+zeroes[:20]) 178 } 179 180 func TestItemReadAt(t *testing.T) { 181 r, c := newItemTestCache(t) 182 183 contents, obj, item := newFile(t, r, c, "existing") 184 buf := make([]byte, 10) 185 186 _, err := item.ReadAt(buf, 10) 187 require.Error(t, err) 188 189 require.NoError(t, item.Open(obj)) 190 191 n, err := item.ReadAt(buf, 10) 192 assert.Equal(t, 10, n) 193 require.NoError(t, err) 194 assert.Equal(t, contents[10:20], string(buf[:n])) 195 196 n, err = item.ReadAt(buf, 95) 197 assert.Equal(t, 5, n) 198 assert.Equal(t, io.EOF, err) 199 assert.Equal(t, contents[95:], string(buf[:n])) 200 201 n, err = item.ReadAt(buf, 1000) 202 assert.Equal(t, 0, n) 203 assert.Equal(t, io.EOF, err) 204 assert.Equal(t, contents[:0], string(buf[:n])) 205 206 n, err = item.ReadAt(buf, -1) 207 assert.Equal(t, 0, n) 208 assert.Equal(t, io.EOF, err) 209 assert.Equal(t, contents[:0], string(buf[:n])) 210 211 require.NoError(t, item.Close(nil)) 212 } 213 214 func TestItemWriteAtNew(t *testing.T) { 215 r, c := newItemTestCache(t) 216 item, _ := c.get("potato") 217 buf := make([]byte, 10) 218 219 _, err := item.WriteAt(buf, 10) 220 require.Error(t, err) 221 222 require.NoError(t, item.Open(nil)) 223 224 assert.Equal(t, int64(0), item.getDiskSize()) 225 226 n, err := item.WriteAt([]byte("HELLO"), 10) 227 require.NoError(t, err) 228 assert.Equal(t, 5, n) 229 230 // FIXME we account for the sparse data we've "written" to 231 // disk here so this is actually 5 bytes higher than expected 232 assert.Equal(t, int64(15), item.getDiskSize()) 233 234 n, err = item.WriteAt([]byte("THEND"), 20) 235 require.NoError(t, err) 236 assert.Equal(t, 5, n) 237 238 assert.Equal(t, int64(25), item.getDiskSize()) 239 240 require.NoError(t, item.Close(nil)) 241 242 checkObject(t, r, "potato", zeroes[:10]+"HELLO"+zeroes[:5]+"THEND") 243 } 244 245 func TestItemWriteAtExisting(t *testing.T) { 246 r, c := newItemTestCache(t) 247 248 contents, obj, item := newFile(t, r, c, "existing") 249 250 require.NoError(t, item.Open(obj)) 251 252 n, err := item.WriteAt([]byte("HELLO"), 10) 253 require.NoError(t, err) 254 assert.Equal(t, 5, n) 255 256 n, err = item.WriteAt([]byte("THEND"), 95) 257 require.NoError(t, err) 258 assert.Equal(t, 5, n) 259 260 n, err = item.WriteAt([]byte("THEVERYEND"), 120) 261 require.NoError(t, err) 262 assert.Equal(t, 10, n) 263 264 require.NoError(t, item.Close(nil)) 265 266 checkObject(t, r, "existing", contents[:10]+"HELLO"+contents[15:95]+"THEND"+zeroes[:20]+"THEVERYEND") 267 } 268 269 func TestItemLoadMeta(t *testing.T) { 270 r, c := newItemTestCache(t) 271 272 contents, obj, item := newFile(t, r, c, "existing") 273 _ = contents 274 275 // Open the object to create metadata for it 276 require.NoError(t, item.Open(obj)) 277 require.NoError(t, item.Close(nil)) 278 info := item.info 279 280 // Remove the item from the cache 281 c.mu.Lock() 282 delete(c.item, item.name) 283 c.mu.Unlock() 284 285 // Reload the item so we have to load the metadata 286 item2, _ := c._get("existing") 287 require.NoError(t, item2.Open(obj)) 288 info2 := item.info 289 require.NoError(t, item2.Close(nil)) 290 291 // Check that the item is different 292 assert.NotEqual(t, item, item2) 293 // ... but the info is the same 294 assert.Equal(t, info, info2) 295 } 296 297 func TestItemReload(t *testing.T) { 298 r, c := newItemTestCache(t) 299 300 contents, obj, item := newFile(t, r, c, "existing") 301 _ = contents 302 303 // Open the object to create metadata for it 304 require.NoError(t, item.Open(obj)) 305 306 // Make it dirty 307 n, err := item.WriteAt([]byte("THEENDMYFRIEND"), 95) 308 require.NoError(t, err) 309 assert.Equal(t, 14, n) 310 assert.True(t, item.IsDirty()) 311 312 // Close the file to pacify Windows, but don't call item.Close() 313 item.mu.Lock() 314 require.NoError(t, item.fd.Close()) 315 item.fd = nil 316 item.mu.Unlock() 317 318 // Remove the item from the cache 319 c.mu.Lock() 320 delete(c.item, item.name) 321 c.mu.Unlock() 322 323 // Reload the item so we have to load the metadata and restart 324 // the transfer 325 item2, _ := c._get("existing") 326 require.NoError(t, item2.reload(context.Background())) 327 assert.False(t, item2.IsDirty()) 328 329 // Check that the item is different 330 assert.NotEqual(t, item, item2) 331 332 // And check the contents got written back to the remote 333 checkObject(t, r, "existing", contents[:95]+"THEENDMYFRIEND") 334 335 // And check that AddVirtual was called 336 assert.Equal(t, []avInfo{ 337 {Remote: "existing", Size: 109, IsDir: false}, 338 }, avInfos) 339 } 340 341 func TestItemReloadRemoteGone(t *testing.T) { 342 r, c := newItemTestCache(t) 343 344 contents, obj, item := newFile(t, r, c, "existing") 345 _ = contents 346 347 // Open the object to create metadata for it 348 require.NoError(t, item.Open(obj)) 349 350 size, err := item.GetSize() 351 require.NoError(t, err) 352 assert.Equal(t, int64(100), size) 353 354 // Read something to instantiate the cache file 355 buf := make([]byte, 10) 356 _, err = item.ReadAt(buf, 10) 357 require.NoError(t, err) 358 359 // Test cache file present 360 _, err = os.Stat(item.c.toOSPath(item.name)) 361 require.NoError(t, err) 362 363 require.NoError(t, item.Close(nil)) 364 365 // Remove the remote object 366 require.NoError(t, obj.Remove(context.Background())) 367 368 // Re-open with no object 369 require.NoError(t, item.Open(nil)) 370 371 // Check size is now 0 372 size, err = item.GetSize() 373 require.NoError(t, err) 374 assert.Equal(t, int64(0), size) 375 376 // Test cache file is now empty 377 fi, err := os.Stat(item.c.toOSPath(item.name)) 378 require.NoError(t, err) 379 assert.Equal(t, int64(0), fi.Size()) 380 381 require.NoError(t, item.Close(nil)) 382 } 383 384 func TestItemReloadCacheStale(t *testing.T) { 385 r, c := newItemTestCache(t) 386 387 contents, obj, item := newFile(t, r, c, "existing") 388 389 // Open the object to create metadata for it 390 require.NoError(t, item.Open(obj)) 391 392 size, err := item.GetSize() 393 require.NoError(t, err) 394 assert.Equal(t, int64(100), size) 395 396 // Read something to instantiate the cache file 397 buf := make([]byte, 10) 398 _, err = item.ReadAt(buf, 10) 399 require.NoError(t, err) 400 401 // Test cache file present 402 _, err = os.Stat(item.c.toOSPath(item.name)) 403 require.NoError(t, err) 404 405 require.NoError(t, item.Close(nil)) 406 407 // Update the remote to something different 408 contents2, obj, item := newFileLength(t, r, c, "existing", 110) 409 assert.NotEqual(t, contents, contents2) 410 411 // Re-open with updated object 412 oldFingerprint := item.info.Fingerprint 413 assert.NotEqual(t, "", oldFingerprint) 414 require.NoError(t, item.Open(obj)) 415 416 // Make sure fingerprint was updated 417 assert.NotEqual(t, oldFingerprint, item.info.Fingerprint) 418 assert.NotEqual(t, "", item.info.Fingerprint) 419 420 // Check size is now 110 421 size, err = item.GetSize() 422 require.NoError(t, err) 423 assert.Equal(t, int64(110), size) 424 425 // Test cache file is now correct size 426 fi, err := os.Stat(item.c.toOSPath(item.name)) 427 require.NoError(t, err) 428 assert.Equal(t, int64(110), fi.Size()) 429 430 // Write to the file to make it dirty 431 // This checks we aren't re-using stale data 432 n, err := item.WriteAt([]byte("HELLO"), 0) 433 require.NoError(t, err) 434 assert.Equal(t, 5, n) 435 assert.Equal(t, true, item.IsDirty()) 436 437 require.NoError(t, item.Close(nil)) 438 439 // Now check with all that swizzling stuff around that the 440 // object is correct 441 442 checkObject(t, r, "existing", "HELLO"+contents2[5:]) 443 } 444 445 func TestItemReadWrite(t *testing.T) { 446 r, c := newItemTestCache(t) 447 const ( 448 size = 50*1024*1024 + 123 449 fileName = "large" 450 ) 451 452 item, _ := c.get(fileName) 453 require.NoError(t, item.Open(nil)) 454 455 // Create the test file 456 in := readers.NewPatternReader(size) 457 buf := make([]byte, 1024*1024) 458 buf2 := make([]byte, 1024*1024) 459 offset := int64(0) 460 for { 461 n, err := in.Read(buf) 462 n2, err2 := item.WriteAt(buf[:n], offset) 463 offset += int64(n2) 464 require.NoError(t, err2) 465 require.Equal(t, n, n2) 466 if err == io.EOF { 467 break 468 } 469 require.NoError(t, err) 470 } 471 472 // Check it is the right size 473 readSize, err := item.GetSize() 474 require.NoError(t, err) 475 assert.Equal(t, int64(size), readSize) 476 477 require.NoError(t, item.Close(nil)) 478 479 assert.False(t, item.remove(fileName)) 480 481 obj, err := r.Fremote.NewObject(context.Background(), fileName) 482 require.NoError(t, err) 483 assert.Equal(t, int64(size), obj.Size()) 484 485 // read and check a block of size N at offset 486 // It returns eof true if the end of file has been reached 487 readCheckBuf := func(t *testing.T, in io.ReadSeeker, buf, buf2 []byte, item *Item, offset int64, N int) (n int, eof bool) { 488 what := fmt.Sprintf("buf=%p, buf2=%p, item=%p, offset=%d, N=%d", buf, buf2, item, offset, N) 489 n, err := item.ReadAt(buf, offset) 490 491 _, err2 := in.Seek(offset, io.SeekStart) 492 require.NoError(t, err2, what) 493 n2, err2 := in.Read(buf2[:n]) 494 require.Equal(t, n, n2, what) 495 assert.Equal(t, buf[:n], buf2[:n2], what) 496 assert.Equal(t, buf[:n], buf2[:n2], what) 497 498 if err == io.EOF { 499 return n, true 500 } 501 require.NoError(t, err, what) 502 require.NoError(t, err2, what) 503 return n, false 504 } 505 readCheck := func(t *testing.T, item *Item, offset int64, N int) (n int, eof bool) { 506 return readCheckBuf(t, in, buf, buf2, item, offset, N) 507 } 508 509 // Read it back sequentially 510 t.Run("Sequential", func(t *testing.T) { 511 require.NoError(t, item.Open(obj)) 512 assert.False(t, item.present()) 513 offset := int64(0) 514 for { 515 n, eof := readCheck(t, item, offset, len(buf)) 516 offset += int64(n) 517 if eof { 518 break 519 } 520 } 521 assert.Equal(t, int64(size), offset) 522 require.NoError(t, item.Close(nil)) 523 assert.False(t, item.remove(fileName)) 524 }) 525 526 // Read it back randomly 527 t.Run("Random", func(t *testing.T) { 528 require.NoError(t, item.Open(obj)) 529 assert.False(t, item.present()) 530 for !item.present() { 531 blockSize := rand.Intn(len(buf)) 532 offset := rand.Int63n(size+2*int64(blockSize)) - int64(blockSize) 533 if offset < 0 { 534 offset = 0 535 } 536 _, _ = readCheck(t, item, offset, blockSize) 537 } 538 require.NoError(t, item.Close(nil)) 539 assert.False(t, item.remove(fileName)) 540 }) 541 542 // Read it back randomly concurrently 543 t.Run("RandomConcurrent", func(t *testing.T) { 544 require.NoError(t, item.Open(obj)) 545 assert.False(t, item.present()) 546 var wg sync.WaitGroup 547 for i := 0; i < 8; i++ { 548 wg.Add(1) 549 go func() { 550 defer wg.Done() 551 in := readers.NewPatternReader(size) 552 buf := make([]byte, 1024*1024) 553 buf2 := make([]byte, 1024*1024) 554 for !item.present() { 555 blockSize := rand.Intn(len(buf)) 556 offset := rand.Int63n(size+2*int64(blockSize)) - int64(blockSize) 557 if offset < 0 { 558 offset = 0 559 } 560 _, _ = readCheckBuf(t, in, buf, buf2, item, offset, blockSize) 561 } 562 }() 563 } 564 wg.Wait() 565 require.NoError(t, item.Close(nil)) 566 assert.False(t, item.remove(fileName)) 567 }) 568 569 // Read it back in reverse which creates the maximum number of 570 // downloaders 571 t.Run("Reverse", func(t *testing.T) { 572 require.NoError(t, item.Open(obj)) 573 assert.False(t, item.present()) 574 offset := int64(size) 575 for { 576 blockSize := len(buf) 577 offset -= int64(blockSize) 578 if offset < 0 { 579 offset = 0 580 blockSize += int(offset) 581 } 582 _, _ = readCheck(t, item, offset, blockSize) 583 if offset == 0 { 584 break 585 } 586 } 587 require.NoError(t, item.Close(nil)) 588 assert.False(t, item.remove(fileName)) 589 }) 590 }