Home | History | Annotate | Download | only in cache
      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 TestCacheLog(t *testing.T) {
    148 	dir, err := ioutil.TempDir("", "cachetest-")
    149 	if err != nil {
    150 		t.Fatal(err)
    151 	}
    152 	defer os.RemoveAll(dir)
    153 
    154 	c, err := Open(dir)
    155 	if err != nil {
    156 		t.Fatalf("Open: %v", err)
    157 	}
    158 	c.now = func() time.Time { return time.Unix(1e9, 0) }
    159 
    160 	id := ActionID(dummyID(1))
    161 	c.Get(id)
    162 	c.PutBytes(id, []byte("abc"))
    163 	c.Get(id)
    164 
    165 	c, err = Open(dir)
    166 	if err != nil {
    167 		t.Fatalf("Open #2: %v", err)
    168 	}
    169 	c.now = func() time.Time { return time.Unix(1e9+1, 0) }
    170 	c.Get(id)
    171 
    172 	id2 := ActionID(dummyID(2))
    173 	c.Get(id2)
    174 	c.PutBytes(id2, []byte("abc"))
    175 	c.Get(id2)
    176 	c.Get(id)
    177 
    178 	data, err := ioutil.ReadFile(filepath.Join(dir, "log.txt"))
    179 	if err != nil {
    180 		t.Fatal(err)
    181 	}
    182 	want := `1000000000 miss 0100000000000000000000000000000000000000000000000000000000000000
    183 1000000000 put 0100000000000000000000000000000000000000000000000000000000000000 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3
    184 1000000000 get 0100000000000000000000000000000000000000000000000000000000000000
    185 1000000001 get 0100000000000000000000000000000000000000000000000000000000000000
    186 1000000001 miss 0200000000000000000000000000000000000000000000000000000000000000
    187 1000000001 put 0200000000000000000000000000000000000000000000000000000000000000 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3
    188 1000000001 get 0200000000000000000000000000000000000000000000000000000000000000
    189 1000000001 get 0100000000000000000000000000000000000000000000000000000000000000
    190 `
    191 	if string(data) != want {
    192 		t.Fatalf("log:\n%s\nwant:\n%s", string(data), want)
    193 	}
    194 }
    195 
    196 func dummyID(x int) [HashSize]byte {
    197 	var out [HashSize]byte
    198 	binary.LittleEndian.PutUint64(out[:], uint64(x))
    199 	return out
    200 }
    201 
    202 func TestCacheTrim(t *testing.T) {
    203 	dir, err := ioutil.TempDir("", "cachetest-")
    204 	if err != nil {
    205 		t.Fatal(err)
    206 	}
    207 	defer os.RemoveAll(dir)
    208 
    209 	c, err := Open(dir)
    210 	if err != nil {
    211 		t.Fatalf("Open: %v", err)
    212 	}
    213 	const start = 1000000000
    214 	now := int64(start)
    215 	c.now = func() time.Time { return time.Unix(now, 0) }
    216 
    217 	checkTime := func(name string, mtime int64) {
    218 		t.Helper()
    219 		file := filepath.Join(c.dir, name[:2], name)
    220 		info, err := os.Stat(file)
    221 		if err != nil {
    222 			t.Fatal(err)
    223 		}
    224 		if info.ModTime().Unix() != mtime {
    225 			t.Fatalf("%s mtime = %d, want %d", name, info.ModTime().Unix(), mtime)
    226 		}
    227 	}
    228 
    229 	id := ActionID(dummyID(1))
    230 	c.PutBytes(id, []byte("abc"))
    231 	entry, _ := c.Get(id)
    232 	c.PutBytes(ActionID(dummyID(2)), []byte("def"))
    233 	mtime := now
    234 	checkTime(fmt.Sprintf("%x-a", id), mtime)
    235 	checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
    236 
    237 	// Get should not change recent mtimes.
    238 	now = start + 10
    239 	c.Get(id)
    240 	checkTime(fmt.Sprintf("%x-a", id), mtime)
    241 	checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
    242 
    243 	// Get should change distant mtimes.
    244 	now = start + 5000
    245 	mtime2 := now
    246 	if _, err := c.Get(id); err != nil {
    247 		t.Fatal(err)
    248 	}
    249 	c.OutputFile(entry.OutputID)
    250 	checkTime(fmt.Sprintf("%x-a", id), mtime2)
    251 	checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime2)
    252 
    253 	// Trim should leave everything alone: it's all too new.
    254 	c.Trim()
    255 	if _, err := c.Get(id); err != nil {
    256 		t.Fatal(err)
    257 	}
    258 	c.OutputFile(entry.OutputID)
    259 	data, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt"))
    260 	if err != nil {
    261 		t.Fatal(err)
    262 	}
    263 	checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
    264 
    265 	// Trim less than a day later should not do any work at all.
    266 	now = start + 80000
    267 	c.Trim()
    268 	if _, err := c.Get(id); err != nil {
    269 		t.Fatal(err)
    270 	}
    271 	c.OutputFile(entry.OutputID)
    272 	data2, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt"))
    273 	if err != nil {
    274 		t.Fatal(err)
    275 	}
    276 	if !bytes.Equal(data, data2) {
    277 		t.Fatalf("second trim did work: %q -> %q", data, data2)
    278 	}
    279 
    280 	// Fast forward and do another trim just before the 5 day cutoff.
    281 	// Note that because of usedQuantum the cutoff is actually 5 days + 1 hour.
    282 	// We used c.Get(id) just now, so 5 days later it should still be kept.
    283 	// On the other hand almost a full day has gone by since we wrote dummyID(2)
    284 	// and we haven't looked at it since, so 5 days later it should be gone.
    285 	now += 5 * 86400
    286 	checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
    287 	c.Trim()
    288 	if _, err := c.Get(id); err != nil {
    289 		t.Fatal(err)
    290 	}
    291 	c.OutputFile(entry.OutputID)
    292 	mtime3 := now
    293 	if _, err := c.Get(dummyID(2)); err == nil { // haven't done a Get for this since original write above
    294 		t.Fatalf("Trim did not remove dummyID(2)")
    295 	}
    296 
    297 	// The c.Get(id) refreshed id's mtime again.
    298 	// Check that another 5 days later it is still not gone,
    299 	// but check by using checkTime, which doesn't bring mtime forward.
    300 	now += 5 * 86400
    301 	c.Trim()
    302 	checkTime(fmt.Sprintf("%x-a", id), mtime3)
    303 	checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
    304 
    305 	// Half a day later Trim should still be a no-op, because there was a Trim recently.
    306 	// Even though the entry for id is now old enough to be trimmed,
    307 	// it gets a reprieve until the time comes for a new Trim scan.
    308 	now += 86400 / 2
    309 	c.Trim()
    310 	checkTime(fmt.Sprintf("%x-a", id), mtime3)
    311 	checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
    312 
    313 	// Another half a day later, Trim should actually run, and it should remove id.
    314 	now += 86400/2 + 1
    315 	c.Trim()
    316 	if _, err := c.Get(dummyID(1)); err == nil {
    317 		t.Fatal("Trim did not remove dummyID(1)")
    318 	}
    319 }
    320