github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/store/cache_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package store_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"strconv"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/osutil"
    33  	"github.com/snapcore/snapd/store"
    34  	"github.com/snapcore/snapd/testutil"
    35  )
    36  
    37  type cacheSuite struct {
    38  	cm       *store.CacheManager
    39  	tmp      string
    40  	maxItems int
    41  }
    42  
    43  var _ = Suite(&cacheSuite{})
    44  
    45  func (s *cacheSuite) SetUpTest(c *C) {
    46  	s.tmp = c.MkDir()
    47  
    48  	s.maxItems = 5
    49  	s.cm = store.NewCacheManager(c.MkDir(), s.maxItems)
    50  	// sanity
    51  	c.Check(s.cm.Count(), Equals, 0)
    52  }
    53  
    54  func (s *cacheSuite) makeTestFile(c *C, name, content string) string {
    55  	return s.makeTestFileInDir(c, c.MkDir(), name, content)
    56  }
    57  func (s *cacheSuite) makeTestFileInDir(c *C, dir, name, content string) string {
    58  	p := filepath.Join(dir, name)
    59  	err := ioutil.WriteFile(p, []byte(content), 0644)
    60  	c.Assert(err, IsNil)
    61  	return p
    62  }
    63  
    64  func (s *cacheSuite) TestPutMany(c *C) {
    65  	dataDir, err := os.Open(c.MkDir())
    66  	c.Assert(err, IsNil)
    67  	defer dataDir.Close()
    68  	cacheDir, err := os.Open(s.cm.CacheDir())
    69  	c.Assert(err, IsNil)
    70  	defer cacheDir.Close()
    71  
    72  	for i := 1; i < s.maxItems+10; i++ {
    73  		p := s.makeTestFileInDir(c, dataDir.Name(), fmt.Sprintf("f%d", i), fmt.Sprintf("%d", i))
    74  		err := s.cm.Put(fmt.Sprintf("cacheKey-%d", i), p)
    75  		c.Check(err, IsNil)
    76  
    77  		// Remove the test file again, it is now only in the cache
    78  		err = os.Remove(p)
    79  		c.Assert(err, IsNil)
    80  		// We need to sync the (meta)data here or the test is racy
    81  		c.Assert(dataDir.Sync(), IsNil)
    82  		c.Assert(cacheDir.Sync(), IsNil)
    83  
    84  		if i <= s.maxItems {
    85  			c.Check(s.cm.Count(), Equals, i)
    86  		} else {
    87  			// Count() will include the cache plus the
    88  			// newly added test file. This latest testfile
    89  			// had a link count of 2 when "cm.Put()" is
    90  			// called because it still exists outside of
    91  			// the cache dir so it's considered "free" in
    92  			// the cache. This is why we need to have
    93  			// Count()-1 here.
    94  			c.Check(s.cm.Count()-1, Equals, s.maxItems)
    95  		}
    96  	}
    97  }
    98  
    99  func (s *cacheSuite) TestGetNotExistant(c *C) {
   100  	err := s.cm.Get("hash-not-in-cache", "some-target-path")
   101  	c.Check(err, ErrorMatches, `link .*: no such file or directory`)
   102  }
   103  
   104  func (s *cacheSuite) TestGet(c *C) {
   105  	canary := "some content"
   106  	p := s.makeTestFile(c, "foo", canary)
   107  	err := s.cm.Put("some-cache-key", p)
   108  	c.Assert(err, IsNil)
   109  
   110  	targetPath := filepath.Join(s.tmp, "new-location")
   111  	err = s.cm.Get("some-cache-key", targetPath)
   112  	c.Check(err, IsNil)
   113  	c.Check(osutil.FileExists(targetPath), Equals, true)
   114  	c.Assert(targetPath, testutil.FileEquals, canary)
   115  }
   116  
   117  func (s *cacheSuite) makeTestFiles(c *C, n int) (cacheKeys []string, testFiles []string) {
   118  	cacheKeys = make([]string, n)
   119  	testFiles = make([]string, n)
   120  	for i := 0; i < n; i++ {
   121  		p := s.makeTestFile(c, fmt.Sprintf("f%d", i), strconv.Itoa(i))
   122  		cacheKey := fmt.Sprintf("cacheKey-%d", i)
   123  		cacheKeys[i] = cacheKey
   124  		s.cm.Put(cacheKey, p)
   125  
   126  		// keep track of the test files
   127  		testFiles[i] = p
   128  
   129  		// mtime is not very granular
   130  		time.Sleep(10 * time.Millisecond)
   131  	}
   132  	return cacheKeys, testFiles
   133  }
   134  
   135  func (s *cacheSuite) TestClenaup(c *C) {
   136  	cacheKeys, testFiles := s.makeTestFiles(c, s.maxItems+2)
   137  
   138  	// Nothing was removed at this point because the test files are
   139  	// still in place and we just hardlink to them. The cache cleanup
   140  	// will only clean files with a link-count of 1.
   141  	c.Check(s.cm.Count(), Equals, s.maxItems+2)
   142  
   143  	// Remove the test files again, they are now only in the cache.
   144  	for _, p := range testFiles {
   145  		err := os.Remove(p)
   146  		c.Assert(err, IsNil)
   147  	}
   148  	s.cm.Cleanup()
   149  
   150  	// the oldest files are removed from the cache
   151  	c.Check(osutil.FileExists(filepath.Join(s.cm.CacheDir(), cacheKeys[0])), Equals, false)
   152  	c.Check(osutil.FileExists(filepath.Join(s.cm.CacheDir(), cacheKeys[1])), Equals, false)
   153  
   154  	// the newest files are still there
   155  	c.Check(osutil.FileExists(filepath.Join(s.cm.CacheDir(), cacheKeys[2])), Equals, true)
   156  	c.Check(osutil.FileExists(filepath.Join(s.cm.CacheDir(), cacheKeys[len(cacheKeys)-1])), Equals, true)
   157  }
   158  
   159  func (s *cacheSuite) TestClenaupContinuesOnError(c *C) {
   160  	cacheKeys, testFiles := s.makeTestFiles(c, s.maxItems+2)
   161  	for _, p := range testFiles {
   162  		err := os.Remove(p)
   163  		c.Assert(err, IsNil)
   164  	}
   165  
   166  	// simulate error with the removal of a file in cachedir
   167  	restore := store.MockOsRemove(func(name string) error {
   168  		if name == filepath.Join(s.cm.CacheDir(), cacheKeys[0]) {
   169  			return fmt.Errorf("simulated error")
   170  		}
   171  		return os.Remove(name)
   172  	})
   173  	defer restore()
   174  
   175  	// verify that cleanup returns the last error
   176  	err := s.cm.Cleanup()
   177  	c.Check(err, ErrorMatches, "simulated error")
   178  
   179  	// and also verify that the cache still got cleaned up
   180  	c.Check(s.cm.Count(), Equals, s.maxItems)
   181  
   182  	// even though the "unremovable" file is still in the cache
   183  	c.Check(osutil.FileExists(filepath.Join(s.cm.CacheDir(), cacheKeys[0])), Equals, true)
   184  }
   185  
   186  func (s *cacheSuite) TestHardLinkCount(c *C) {
   187  	p := filepath.Join(s.tmp, "foo")
   188  	err := ioutil.WriteFile(p, nil, 0644)
   189  	c.Assert(err, IsNil)
   190  
   191  	// trivial case
   192  	fi, err := os.Stat(p)
   193  	c.Assert(err, IsNil)
   194  	n, err := store.HardLinkCount(fi)
   195  	c.Assert(err, IsNil)
   196  	c.Check(n, Equals, uint64(1))
   197  
   198  	// add some hardlinks
   199  	for i := 0; i < 10; i++ {
   200  		err := os.Link(p, filepath.Join(s.tmp, strconv.Itoa(i)))
   201  		c.Assert(err, IsNil)
   202  	}
   203  	fi, err = os.Stat(p)
   204  	c.Assert(err, IsNil)
   205  	n, err = store.HardLinkCount(fi)
   206  	c.Assert(err, IsNil)
   207  	c.Check(n, Equals, uint64(11))
   208  
   209  	// and remove one
   210  	err = os.Remove(filepath.Join(s.tmp, "0"))
   211  	c.Assert(err, IsNil)
   212  	fi, err = os.Stat(p)
   213  	c.Assert(err, IsNil)
   214  	n, err = store.HardLinkCount(fi)
   215  	c.Assert(err, IsNil)
   216  	c.Check(n, Equals, uint64(10))
   217  }