|
| 1 | +// Copyright 2017 The Go Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +package cache |
| 6 | + |
| 7 | +import ( |
| 8 | + "bytes" |
| 9 | + "encoding/binary" |
| 10 | + "fmt" |
| 11 | + "io/ioutil" |
| 12 | + "os" |
| 13 | + "path/filepath" |
| 14 | + "testing" |
| 15 | + "time" |
| 16 | +) |
| 17 | + |
| 18 | +func init() { |
| 19 | + verify = false // even if GODEBUG is set |
| 20 | +} |
| 21 | + |
| 22 | +func TestBasic(t *testing.T) { |
| 23 | + dir, err := ioutil.TempDir("", "cachetest-") |
| 24 | + if err != nil { |
| 25 | + t.Fatal(err) |
| 26 | + } |
| 27 | + defer os.RemoveAll(dir) |
| 28 | + _, err = Open(filepath.Join(dir, "notexist")) |
| 29 | + if err == nil { |
| 30 | + t.Fatal(`Open("tmp/notexist") succeeded, want failure`) |
| 31 | + } |
| 32 | + |
| 33 | + cdir := filepath.Join(dir, "c1") |
| 34 | + if err := os.Mkdir(cdir, 0777); err != nil { |
| 35 | + t.Fatal(err) |
| 36 | + } |
| 37 | + |
| 38 | + c1, err := Open(cdir) |
| 39 | + if err != nil { |
| 40 | + t.Fatalf("Open(c1) (create): %v", err) |
| 41 | + } |
| 42 | + if err := c1.putIndexEntry(dummyID(1), dummyID(12), 13, true); err != nil { |
| 43 | + t.Fatalf("addIndexEntry: %v", err) |
| 44 | + } |
| 45 | + if err := c1.putIndexEntry(dummyID(1), dummyID(2), 3, true); err != nil { // overwrite entry |
| 46 | + t.Fatalf("addIndexEntry: %v", err) |
| 47 | + } |
| 48 | + if entry, err := c1.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 { |
| 49 | + t.Fatalf("c1.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3) |
| 50 | + } |
| 51 | + |
| 52 | + c2, err := Open(cdir) |
| 53 | + if err != nil { |
| 54 | + t.Fatalf("Open(c2) (reuse): %v", err) |
| 55 | + } |
| 56 | + if entry, err := c2.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 { |
| 57 | + t.Fatalf("c2.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3) |
| 58 | + } |
| 59 | + if err := c2.putIndexEntry(dummyID(2), dummyID(3), 4, true); err != nil { |
| 60 | + t.Fatalf("addIndexEntry: %v", err) |
| 61 | + } |
| 62 | + if entry, err := c1.Get(dummyID(2)); err != nil || entry.OutputID != dummyID(3) || entry.Size != 4 { |
| 63 | + t.Fatalf("c1.Get(2) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(3), 4) |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +func TestGrowth(t *testing.T) { |
| 68 | + dir, err := ioutil.TempDir("", "cachetest-") |
| 69 | + if err != nil { |
| 70 | + t.Fatal(err) |
| 71 | + } |
| 72 | + defer os.RemoveAll(dir) |
| 73 | + |
| 74 | + c, err := Open(dir) |
| 75 | + if err != nil { |
| 76 | + t.Fatalf("Open: %v", err) |
| 77 | + } |
| 78 | + |
| 79 | + n := 10000 |
| 80 | + if testing.Short() { |
| 81 | + n = 1000 |
| 82 | + } |
| 83 | + |
| 84 | + for i := 0; i < n; i++ { |
| 85 | + if err := c.putIndexEntry(dummyID(i), dummyID(i*99), int64(i)*101, true); err != nil { |
| 86 | + t.Fatalf("addIndexEntry: %v", err) |
| 87 | + } |
| 88 | + id := ActionID(dummyID(i)) |
| 89 | + entry, err := c.Get(id) |
| 90 | + if err != nil { |
| 91 | + t.Fatalf("Get(%x): %v", id, err) |
| 92 | + } |
| 93 | + if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 { |
| 94 | + t.Errorf("Get(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101) |
| 95 | + } |
| 96 | + } |
| 97 | + for i := 0; i < n; i++ { |
| 98 | + id := ActionID(dummyID(i)) |
| 99 | + entry, err := c.Get(id) |
| 100 | + if err != nil { |
| 101 | + t.Fatalf("Get2(%x): %v", id, err) |
| 102 | + } |
| 103 | + if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 { |
| 104 | + t.Errorf("Get2(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101) |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +func TestVerifyPanic(t *testing.T) { |
| 110 | + os.Setenv("GODEBUG", "gocacheverify=1") |
| 111 | + initEnv() |
| 112 | + defer func() { |
| 113 | + os.Unsetenv("GODEBUG") |
| 114 | + verify = false |
| 115 | + }() |
| 116 | + |
| 117 | + if !verify { |
| 118 | + t.Fatal("initEnv did not set verify") |
| 119 | + } |
| 120 | + |
| 121 | + dir, err := ioutil.TempDir("", "cachetest-") |
| 122 | + if err != nil { |
| 123 | + t.Fatal(err) |
| 124 | + } |
| 125 | + defer os.RemoveAll(dir) |
| 126 | + |
| 127 | + c, err := Open(dir) |
| 128 | + if err != nil { |
| 129 | + t.Fatalf("Open: %v", err) |
| 130 | + } |
| 131 | + |
| 132 | + id := ActionID(dummyID(1)) |
| 133 | + if err := c.PutBytes(id, []byte("abc")); err != nil { |
| 134 | + t.Fatal(err) |
| 135 | + } |
| 136 | + |
| 137 | + defer func() { |
| 138 | + if err := recover(); err != nil { |
| 139 | + t.Log(err) |
| 140 | + return |
| 141 | + } |
| 142 | + }() |
| 143 | + c.PutBytes(id, []byte("def")) |
| 144 | + t.Fatal("mismatched Put did not panic in verify mode") |
| 145 | +} |
| 146 | + |
| 147 | +func dummyID(x int) [HashSize]byte { |
| 148 | + var out [HashSize]byte |
| 149 | + binary.LittleEndian.PutUint64(out[:], uint64(x)) |
| 150 | + return out |
| 151 | +} |
| 152 | + |
| 153 | +func TestCacheTrim(t *testing.T) { |
| 154 | + dir, err := ioutil.TempDir("", "cachetest-") |
| 155 | + if err != nil { |
| 156 | + t.Fatal(err) |
| 157 | + } |
| 158 | + defer os.RemoveAll(dir) |
| 159 | + |
| 160 | + c, err := Open(dir) |
| 161 | + if err != nil { |
| 162 | + t.Fatalf("Open: %v", err) |
| 163 | + } |
| 164 | + const start = 1000000000 |
| 165 | + now := int64(start) |
| 166 | + c.now = func() time.Time { return time.Unix(now, 0) } |
| 167 | + |
| 168 | + checkTime := func(name string, mtime int64) { |
| 169 | + t.Helper() |
| 170 | + file := filepath.Join(c.dir, name[:2], name) |
| 171 | + info, err := os.Stat(file) |
| 172 | + if err != nil { |
| 173 | + t.Fatal(err) |
| 174 | + } |
| 175 | + if info.ModTime().Unix() != mtime { |
| 176 | + t.Fatalf("%s mtime = %d, want %d", name, info.ModTime().Unix(), mtime) |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + id := ActionID(dummyID(1)) |
| 181 | + c.PutBytes(id, []byte("abc")) |
| 182 | + entry, _ := c.Get(id) |
| 183 | + c.PutBytes(ActionID(dummyID(2)), []byte("def")) |
| 184 | + mtime := now |
| 185 | + checkTime(fmt.Sprintf("%x-a", id), mtime) |
| 186 | + checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime) |
| 187 | + |
| 188 | + // Get should not change recent mtimes. |
| 189 | + now = start + 10 |
| 190 | + c.Get(id) |
| 191 | + checkTime(fmt.Sprintf("%x-a", id), mtime) |
| 192 | + checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime) |
| 193 | + |
| 194 | + // Get should change distant mtimes. |
| 195 | + now = start + 5000 |
| 196 | + mtime2 := now |
| 197 | + if _, err := c.Get(id); err != nil { |
| 198 | + t.Fatal(err) |
| 199 | + } |
| 200 | + c.OutputFile(entry.OutputID) |
| 201 | + checkTime(fmt.Sprintf("%x-a", id), mtime2) |
| 202 | + checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime2) |
| 203 | + |
| 204 | + // Trim should leave everything alone: it's all too new. |
| 205 | + c.Trim() |
| 206 | + if _, err := c.Get(id); err != nil { |
| 207 | + t.Fatal(err) |
| 208 | + } |
| 209 | + c.OutputFile(entry.OutputID) |
| 210 | + data, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt")) |
| 211 | + if err != nil { |
| 212 | + t.Fatal(err) |
| 213 | + } |
| 214 | + checkTime(fmt.Sprintf("%x-a", dummyID(2)), start) |
| 215 | + |
| 216 | + // Trim less than a day later should not do any work at all. |
| 217 | + now = start + 80000 |
| 218 | + c.Trim() |
| 219 | + if _, err := c.Get(id); err != nil { |
| 220 | + t.Fatal(err) |
| 221 | + } |
| 222 | + c.OutputFile(entry.OutputID) |
| 223 | + data2, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt")) |
| 224 | + if err != nil { |
| 225 | + t.Fatal(err) |
| 226 | + } |
| 227 | + if !bytes.Equal(data, data2) { |
| 228 | + t.Fatalf("second trim did work: %q -> %q", data, data2) |
| 229 | + } |
| 230 | + |
| 231 | + // Fast forward and do another trim just before the 5 day cutoff. |
| 232 | + // Note that because of usedQuantum the cutoff is actually 5 days + 1 hour. |
| 233 | + // We used c.Get(id) just now, so 5 days later it should still be kept. |
| 234 | + // On the other hand almost a full day has gone by since we wrote dummyID(2) |
| 235 | + // and we haven't looked at it since, so 5 days later it should be gone. |
| 236 | + now += 5 * 86400 |
| 237 | + checkTime(fmt.Sprintf("%x-a", dummyID(2)), start) |
| 238 | + c.Trim() |
| 239 | + if _, err := c.Get(id); err != nil { |
| 240 | + t.Fatal(err) |
| 241 | + } |
| 242 | + c.OutputFile(entry.OutputID) |
| 243 | + mtime3 := now |
| 244 | + if _, err := c.Get(dummyID(2)); err == nil { // haven't done a Get for this since original write above |
| 245 | + t.Fatalf("Trim did not remove dummyID(2)") |
| 246 | + } |
| 247 | + |
| 248 | + // The c.Get(id) refreshed id's mtime again. |
| 249 | + // Check that another 5 days later it is still not gone, |
| 250 | + // but check by using checkTime, which doesn't bring mtime forward. |
| 251 | + now += 5 * 86400 |
| 252 | + c.Trim() |
| 253 | + checkTime(fmt.Sprintf("%x-a", id), mtime3) |
| 254 | + checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3) |
| 255 | + |
| 256 | + // Half a day later Trim should still be a no-op, because there was a Trim recently. |
| 257 | + // Even though the entry for id is now old enough to be trimmed, |
| 258 | + // it gets a reprieve until the time comes for a new Trim scan. |
| 259 | + now += 86400 / 2 |
| 260 | + c.Trim() |
| 261 | + checkTime(fmt.Sprintf("%x-a", id), mtime3) |
| 262 | + checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3) |
| 263 | + |
| 264 | + // Another half a day later, Trim should actually run, and it should remove id. |
| 265 | + now += 86400/2 + 1 |
| 266 | + c.Trim() |
| 267 | + if _, err := c.Get(dummyID(1)); err == nil { |
| 268 | + t.Fatal("Trim did not remove dummyID(1)") |
| 269 | + } |
| 270 | +} |
0 commit comments