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  }