github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/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 }