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  }