Skip to content
quarry Artifact [9b03332d33]
Login

Artifact [9b03332d33]

Artifact 9b03332d33b437781f1c18966dd4f3077c43a53611af517f9d712ade0ad003ac:


package zitcli

import (
	"strings"
	"testing"
)

func diffOf(n int) diffResult {
	return diffResult{
		unified: strings.Repeat("x", n),
		entries: []DiffEntry{{Status: "M", Name: "a.go"}},
	}
}

// The bound that matters is bytes. A cap counted in entries lets a
// handful of large merges hold far more memory than the budget any
// operator chose — the same failure the blob cache's note describes,
// and what the old 32-entry diff memo actually did.
func TestTheDiffMemoIsBoundedByBytesNotEntries(t *testing.T) {
	c := newDiffCache(1000)
	// Three entries, but together far past the budget.
	c.put("a", diffOf(400))
	c.put("b", diffOf(400))
	c.put("c", diffOf(400))
	entries, bytes := c.stats()
	if bytes > 1000 {
		t.Errorf("FAIL: holding %d bytes over a 1000-byte budget", bytes)
	}
	if entries == 3 {
		t.Error("FAIL: three oversized diffs all survived — the budget is not being enforced")
	}
	// The oldest went, not a random one: eviction is least-recently-used.
	if _, ok := c.get("a"); ok {
		t.Error("FAIL: the least recently used entry survived")
	}
	if _, ok := c.get("c"); !ok {
		t.Error("FAIL: the most recently used entry was evicted")
	}
}

// A diff that has been read recently must outlive one that has not,
// whatever order they arrived in. The old memo deleted whichever key the
// map iterator happened to yield, so a hot diff was evicted as readily
// as a cold one.
func TestAReadDiffOutlivesAnUnreadOne(t *testing.T) {
	c := newDiffCache(1000)
	c.put("old", diffOf(300))
	c.put("mid", diffOf(300))
	// Reading "old" makes it the most recent, so the next eviction must
	// take "mid" instead.
	if _, ok := c.get("old"); !ok {
		t.Fatal("FAIL: setup — old was not stored")
	}
	c.put("new", diffOf(300))
	c.put("newer", diffOf(300))
	if _, ok := c.get("mid"); ok {
		t.Error("FAIL: the unread entry survived while a read one was evicted")
	}
	if _, ok := c.get("old"); !ok {
		t.Error("FAIL: reading an entry did not protect it from eviction")
	}
}

// A single diff larger than the whole budget is not cached, and is not
// an error. Admitting it would evict everything to hold one item, which
// is worse than missing.
func TestAnOversizedDiffIsNotCached(t *testing.T) {
	c := newDiffCache(100)
	c.put("huge", diffOf(5000))
	if entries, bytes := c.stats(); entries != 0 || bytes != 0 {
		t.Errorf("FAIL: an oversized diff was admitted (%d entries, %d bytes)", entries, bytes)
	}
	if _, ok := c.get("huge"); ok {
		t.Error("FAIL: an oversized diff reads back as a hit")
	}
}

// The entry list counts toward the size. A merge across a thousand
// files carries a thousand of them, and ignoring that would let the
// memo hold well past its budget on wide, shallow diffs.
func TestTheFileListCountsTowardTheBudget(t *testing.T) {
	wide := diffResult{unified: ""}
	for i := 0; i < 500; i++ {
		wide.entries = append(wide.entries, DiffEntry{Status: "M", Name: "some/long/path/file.go"})
	}
	c := newDiffCache(1 << 20)
	c.put("wide", wide)
	if _, bytes := c.stats(); bytes == 0 {
		t.Error("FAIL: a diff with 500 files and no hunks was counted as weighing nothing")
	}
}

// A nil memo is a no-op, not a panic: a Client built without one still
// diffs, it just pays every time.
func TestANilDiffMemoIsHarmless(t *testing.T) {
	var c *diffCache
	c.put("a", diffOf(10))
	if _, ok := c.get("a"); ok {
		t.Error("FAIL: a nil memo returned a hit")
	}
	if entries, bytes := c.stats(); entries != 0 || bytes != 0 {
		t.Errorf("FAIL: a nil memo reported %d entries, %d bytes", entries, bytes)
	}
}