Skip to content
quarry Artifact [43bd612388]
Login

Artifact [43bd612388]

Artifact 43bd61238813e144371431c43d4ffe0db42d2a2aadcc8f22edef3cc9b834a418:


package zitcli

import (
	"strings"
	"testing"
)

// The budget is in BYTES because the values are file contents: an
// entry-counted cache with a 30MB blob in it is a memory leak with a
// polite name.
func TestBlobCacheEvictsByBytes(t *testing.T) {
	c := newBlobCache(100)
	c.put("a", make([]byte, 60))
	c.put("b", make([]byte, 30))
	if n, b := c.stats(); n != 2 || b != 90 {
		t.Fatalf("entries=%d bytes=%d, want 2/90", n, b)
	}

	// Admitting c must evict the least recently used, not the oldest
	// inserted — so touch a first.
	if _, ok := c.get("a"); !ok {
		t.Fatal("a missing")
	}
	c.put("d", make([]byte, 30))
	if _, ok := c.get("b"); ok {
		t.Error("evicted the recently-used entry instead of the least-recently-used")
	}
	if _, ok := c.get("a"); !ok {
		t.Error("a was evicted despite being used most recently")
	}
	if _, b := c.stats(); b > 100 {
		t.Errorf("cache holds %d bytes, over its 100-byte budget", b)
	}
}

// A value bigger than the whole budget must be refused rather than
// admitted by evicting everything: holding one item at the cost of all
// the others is worse than a miss.
func TestBlobCacheRefusesOversizedValues(t *testing.T) {
	c := newBlobCache(100)
	c.put("small", []byte("keep me"))
	c.put("huge", make([]byte, 500))

	if _, ok := c.get("huge"); ok {
		t.Error("cached a value larger than the entire budget")
	}
	if _, ok := c.get("small"); !ok {
		t.Error("an oversized put evicted a value that fit")
	}
}

// Replacing a key must not double-count its bytes.
func TestBlobCacheReplaceAccountsCorrectly(t *testing.T) {
	c := newBlobCache(100)
	c.put("k", make([]byte, 40))
	c.put("k", make([]byte, 10))
	if n, b := c.stats(); n != 1 || b != 10 {
		t.Fatalf("entries=%d bytes=%d, want 1/10", n, b)
	}
}

// Past the forge-wide ceiling, the cache doing the put gives up its own
// oldest entries; a quiet repository's cache is untouched. This is what
// stopped thirty-three per-repository budgets adding up to more memory
// than the machine had.
func TestCeilingEvictsThePuttingCacheOnly(t *testing.T) {
	var f ceiling
	f.max.Store(100)
	quiet, busy := newBlobCache(1000), newBlobCache(1000)
	quiet.ceil, busy.ceil = &f, &f

	quiet.put("q", make([]byte, 40))
	for _, k := range []string{"a", "b", "c", "d"} {
		busy.put(k, make([]byte, 20))
	}
	if f.used.Load() > 100 {
		t.Fatalf("ceiling holds %d bytes, over its 100", f.used.Load())
	}
	if _, ok := quiet.get("q"); !ok {
		t.Fatal("a quiet cache lost its entry to another cache's traffic")
	}
	if _, ok := busy.get("a"); ok {
		t.Error("the busy cache kept its oldest entry past the ceiling")
	}
	if _, ok := busy.get("d"); !ok {
		t.Error("the busy cache dropped its newest entry instead of its oldest")
	}
}

// When the putting cache has nothing of its own left to give, the new
// value is not kept, rather than taking the forge over its ceiling.
func TestCeilingRefusesWhenNothingOfOwnToEvict(t *testing.T) {
	var f ceiling
	f.max.Store(100)
	quiet, busy := newBlobCache(1000), newBlobCache(1000)
	quiet.ceil, busy.ceil = &f, &f
	quiet.put("q", make([]byte, 90))
	busy.put("x", make([]byte, 30))
	if _, ok := busy.get("x"); ok {
		t.Error("cached a value that took the forge past its ceiling")
	}
	if n, b := busy.stats(); n != 0 || b != 0 || f.used.Load() != 90 {
		t.Errorf("busy holds %d/%d, ceiling %d; want 0/0 and 90", n, b, f.used.Load())
	}
}

// A blob is charged for the buffer it lives in, not its length: zit's
// output is read into a buffer that grows by doubling.
func TestBlobChargedByCapacity(t *testing.T) {
	c := newBlobCache(1 << 20)
	c.ceil = &ceiling{}
	c.put("k", make([]byte, 10, 64))
	if _, b := c.stats(); b != 64 {
		t.Errorf("charged %d bytes for a 64-byte buffer", b)
	}
}

// Listings are bounded by bytes: one listing of a large store weighs
// more than many of a small one.
func TestListingCacheEvictsByBytes(t *testing.T) {
	big := make([]LsEntry, 1000)
	for i := range big {
		big[i] = LsEntry{Blob: strings.Repeat("b", 64), Name: "some/path/name.c", Perm: "-"}
	}
	one := newListingCache(0).sizeOf(big)
	c := newListingCache(one + one/2)
	c.ceil = &ceiling{}
	c.put("v1", big)
	c.put("v2", big)
	if n, b := c.stats(); n != 1 || b > one+one/2 {
		t.Fatalf("entries=%d bytes=%d, want 1 within %d", n, b, one+one/2)
	}
	if _, ok := c.get("v2"); !ok {
		t.Error("kept the older listing instead of the newer")
	}
}