go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/caching/cache/cache_test.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cache 16 17 import ( 18 "bytes" 19 "context" 20 "crypto" 21 "io" 22 "math" 23 "os" 24 "path/filepath" 25 "testing" 26 27 "go.chromium.org/luci/common/system/filesystem" 28 29 . "github.com/smartystreets/goconvey/convey" 30 ) 31 32 func testCache(t *testing.T, c *Cache) HexDigests { 33 var expected HexDigests 34 Convey(`Common tests performed on a cache of objects.`, func() { 35 // c's policies must have MaxItems == 2 and MaxSize == 1024. 36 td := t.TempDir() 37 ctx := context.Background() 38 39 h := crypto.SHA1 40 fakeDigest := HexDigest("0123456789012345678901234567890123456789") 41 badDigest := HexDigest("012345678901234567890123456789012345678") 42 emptyContent := []byte{} 43 emptyDigest := HashBytes(h, emptyContent) 44 file1Content := []byte("foo") 45 file1Digest := HashBytes(h, file1Content) 46 file2Content := []byte("foo bar") 47 file2Digest := HashBytes(h, file2Content) 48 hardlinkContent := []byte("hardlink") 49 hardlinkDigest := HashBytes(h, hardlinkContent) 50 largeContent := bytes.Repeat([]byte("A"), 1023) 51 largeDigest := HashBytes(h, largeContent) 52 tooLargeContent := bytes.Repeat([]byte("A"), 1025) 53 tooLargeDigest := HashBytes(h, tooLargeContent) 54 55 So(c.Keys(), ShouldResemble, HexDigests{}) 56 57 So(c.Touch(fakeDigest), ShouldBeFalse) 58 So(c.Touch(badDigest), ShouldBeFalse) 59 60 c.Evict(fakeDigest) 61 c.Evict(badDigest) 62 63 r, err := c.Read(fakeDigest) 64 So(r, ShouldBeNil) 65 So(err, ShouldNotBeNil) 66 r, err = c.Read(badDigest) 67 So(r, ShouldBeNil) 68 So(err, ShouldNotBeNil) 69 70 // It's too large to fit in the cache. 71 So(c.Add(ctx, tooLargeDigest, bytes.NewBuffer(tooLargeContent)), ShouldNotBeNil) 72 73 // It gets discarded because it's too large. 74 So(c.Add(ctx, largeDigest, bytes.NewBuffer(largeContent)), ShouldBeNil) 75 So(c.Add(ctx, emptyDigest, bytes.NewBuffer(emptyContent)), ShouldBeNil) 76 So(c.Add(ctx, emptyDigest, bytes.NewBuffer(emptyContent)), ShouldBeNil) 77 So(c.Keys(), ShouldResemble, HexDigests{emptyDigest, largeDigest}) 78 c.Evict(emptyDigest) 79 So(c.Keys(), ShouldResemble, HexDigests{largeDigest}) 80 So(c.Add(ctx, emptyDigest, bytes.NewBuffer(emptyContent)), ShouldBeNil) 81 82 So(c.Add(ctx, file1Digest, bytes.NewBuffer(file1Content)), ShouldBeNil) 83 So(c.Touch(emptyDigest), ShouldBeTrue) 84 So(c.Add(ctx, file2Digest, bytes.NewBuffer(file2Content)), ShouldBeNil) 85 86 r, err = c.Read(file1Digest) 87 So(r, ShouldBeNil) 88 So(err, ShouldNotBeNil) 89 r, err = c.Read(file2Digest) 90 So(err, ShouldBeNil) 91 actual, err := io.ReadAll(r) 92 So(r.Close(), ShouldBeNil) 93 So(err, ShouldBeNil) 94 So(actual, ShouldResemble, file2Content) 95 96 expected = HexDigests{file2Digest, emptyDigest} 97 So(c.Keys(), ShouldResemble, expected) 98 99 dest := filepath.Join(td, "foo") 100 So(c.Hardlink(fakeDigest, dest, os.FileMode(0600)), ShouldNotBeNil) 101 So(c.Hardlink(badDigest, dest, os.FileMode(0600)), ShouldNotBeNil) 102 So(c.Hardlink(file2Digest, dest, os.FileMode(0600)), ShouldBeNil) 103 // See comment about the fact that it may or may not work. 104 _ = c.Hardlink(file2Digest, dest, os.FileMode(0600)) 105 actual, err = os.ReadFile(dest) 106 So(err, ShouldBeNil) 107 So(actual, ShouldResemble, file2Content) 108 109 dest = filepath.Join(td, "hardlink") 110 So(c.AddWithHardlink(ctx, hardlinkDigest, bytes.NewBuffer(hardlinkContent), dest, os.ModePerm), 111 ShouldBeNil) 112 actual, err = os.ReadFile(dest) 113 So(err, ShouldBeNil) 114 So(actual, ShouldResemble, hardlinkContent) 115 116 // |emptyDigest| is evicted. 117 expected = HexDigests{hardlinkDigest, file2Digest} 118 119 So(c.Close(), ShouldBeNil) 120 }) 121 return expected 122 } 123 124 func TestNew(t *testing.T) { 125 Convey(`Test the disk-based cache of objects.`, t, func() { 126 td := t.TempDir() 127 128 pol := Policies{MaxSize: 1024, MaxItems: 2} 129 h := crypto.SHA1 130 c, err := New(pol, td, h) 131 So(err, ShouldBeNil) 132 expected := testCache(t, c) 133 134 c, err = New(pol, td, h) 135 So(err, ShouldBeNil) 136 So(c.Keys(), ShouldResemble, expected) 137 So(c.Close(), ShouldBeNil) 138 139 curdir, err := os.Getwd() 140 So(err, ShouldBeNil) 141 defer func() { 142 So(os.Chdir(curdir), ShouldBeNil) 143 }() 144 145 So(os.Chdir(td), ShouldBeNil) 146 147 rel, err := filepath.Rel(td, t.TempDir()) 148 So(err, ShouldBeNil) 149 So(filepath.IsAbs(rel), ShouldBeFalse) 150 _, err = New(pol, rel, h) 151 So(err, ShouldBeNil) 152 }) 153 154 Convey(`invalid state.json`, t, func() { 155 dir := t.TempDir() 156 state := filepath.Join(dir, "state.json") 157 invalid := filepath.Join(dir, "invalid file") 158 So(os.WriteFile(state, []byte("invalid"), os.ModePerm), ShouldBeNil) 159 So(os.WriteFile(invalid, []byte("invalid"), os.ModePerm), ShouldBeNil) 160 161 c, err := New(Policies{}, dir, crypto.SHA1) 162 So(err, ShouldNotBeNil) 163 if c == nil { 164 t.Errorf("c should not be nil: %v", err) 165 } 166 So(c, ShouldNotBeNil) 167 168 So(c.statePath(), ShouldEqual, state) 169 170 // invalid files should be removed. 171 empty, err := filesystem.IsEmptyDir(dir) 172 So(err, ShouldBeNil) 173 So(empty, ShouldBeTrue) 174 175 So(c.Close(), ShouldBeNil) 176 }) 177 178 Convey(`MinFreeSpace too big`, t, func() { 179 ctx := context.Background() 180 dir := t.TempDir() 181 h := crypto.SHA1 182 c, err := New(Policies{MaxSize: 10, MinFreeSpace: math.MaxInt64}, dir, h) 183 So(err, ShouldBeNil) 184 185 file1Content := []byte("foo") 186 file1Digest := HashBytes(h, file1Content) 187 So(c.Add(ctx, file1Digest, bytes.NewBuffer(file1Content)), ShouldBeNil) 188 189 So(c.Close(), ShouldBeNil) 190 }) 191 192 Convey(`MaxSize 0`, t, func() { 193 ctx := context.Background() 194 dir := t.TempDir() 195 h := crypto.SHA1 196 c, err := New(Policies{MaxSize: 0, MaxItems: 1}, dir, h) 197 So(err, ShouldBeNil) 198 199 file1Content := []byte("foo") 200 file1Digest := HashBytes(h, file1Content) 201 So(c.Add(ctx, file1Digest, bytes.NewBuffer(file1Content)), ShouldBeNil) 202 So(c.Keys(), ShouldHaveLength, 1) 203 So(c.Close(), ShouldBeNil) 204 }) 205 206 Convey(`HardLink will update used`, t, func() { 207 dir := t.TempDir() 208 h := crypto.SHA1 209 onDiskContent := []byte("on disk") 210 onDiskDigest := HashBytes(h, onDiskContent) 211 notOnDiskContent := []byte("not on disk") 212 notOnDiskDigest := HashBytes(h, notOnDiskContent) 213 214 c, err := New(Policies{}, dir, h) 215 defer func() { So(c.Close(), ShouldBeNil) }() 216 217 So(err, ShouldBeNil) 218 So(c, ShouldNotBeNil) 219 perm := os.ModePerm 220 So(os.WriteFile(c.itemPath(onDiskDigest), onDiskContent, perm), ShouldBeNil) 221 222 So(c.Used(), ShouldBeEmpty) 223 So(c.Hardlink(notOnDiskDigest, filepath.Join(dir, "not_on_disk"), perm), ShouldNotBeNil) 224 So(c.Used(), ShouldBeEmpty) 225 So(c.Hardlink(onDiskDigest, filepath.Join(dir, "on_disk"), perm), ShouldBeNil) 226 So(c.Used(), ShouldHaveLength, 1) 227 }) 228 229 Convey(`AddFileWithoutValidation`, t, func() { 230 ctx := context.Background() 231 dir := t.TempDir() 232 cache := filepath.Join(dir, "cache") 233 h := crypto.SHA1 234 c, err := New(Policies{ 235 MaxSize: 1, 236 MaxItems: 1, 237 }, cache, h) 238 defer func() { So(c.Close(), ShouldBeNil) }() 239 So(err, ShouldBeNil) 240 241 empty := filepath.Join(dir, "empty") 242 So(os.WriteFile(empty, nil, 0600), ShouldBeNil) 243 244 emptyHash := HashBytes(h, nil) 245 246 So(c.AddFileWithoutValidation(ctx, emptyHash, empty), ShouldBeNil) 247 248 So(c.Touch(emptyHash), ShouldBeTrue) 249 250 // Adding already existing file is fine. 251 So(c.AddFileWithoutValidation(ctx, emptyHash, empty), ShouldBeNil) 252 253 empty2 := filepath.Join(dir, "empty2") 254 So(os.WriteFile(empty2, nil, 0600), ShouldBeNil) 255 So(c.AddFileWithoutValidation(ctx, emptyHash, empty2), ShouldBeNil) 256 }) 257 }