From 55676a35757bcbf2fa40cc3fd144ba412c65b658 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Tue, 9 Jun 2026 05:15:58 +0000 Subject: internal/cache: add (and move clock to internal/cache/clock) --- internal/cache/clock/shard_test.go | 149 +++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 internal/cache/clock/shard_test.go (limited to 'internal/cache/clock/shard_test.go') diff --git a/internal/cache/clock/shard_test.go b/internal/cache/clock/shard_test.go new file mode 100644 index 00000000..d974a30e --- /dev/null +++ b/internal/cache/clock/shard_test.go @@ -0,0 +1,149 @@ +package clock //nolint:testpackage + +import "testing" + +func TestShardEvictsOldest(t *testing.T) { + t.Parallel() + + shard := newShard[string, string](8) + shard.add("a", "a", 4) + shard.add("b", "b", 4) + shard.add("c", "c", 4) // overflows, evicts + + if _, ok := shard.peek("a"); ok { + t.Fatalf("a should have been evicted") + } + + for _, key := range []string{"b", "c"} { + if _, ok := shard.peek(key); !ok { + t.Fatalf("%s should remain present", key) + } + } + + if got := shard.loadWeight(); got != 8 { + t.Fatalf("weight = %d, want 8", got) + } + + if got := shard.len(); got != 2 { + t.Fatalf("len = %d, want 2", got) + } +} + +func TestShardGetGivesSecondChance(t *testing.T) { + t.Parallel() + + shard := newShard[string, string](8) + shard.add("a", "a", 4) + shard.add("b", "b", 4) + shard.add("c", "c", 4) // a evicted; b and c survive + + if _, ok := shard.get("b"); !ok { + t.Fatalf("get(b) should hit") + } + + shard.add("d", "d", 4) // b was just touched, so c is evicted instead + + if _, ok := shard.peek("c"); ok { + t.Fatalf("c should have been evicted after b was touched") + } + + for _, key := range []string{"b", "d"} { + if _, ok := shard.peek(key); !ok { + t.Fatalf("%s should remain present", key) + } + } +} + +func TestShardPeekGivesNoSecondChance(t *testing.T) { + t.Parallel() + + shard := newShard[string, string](8) + shard.add("a", "a", 4) + shard.add("b", "b", 4) + shard.add("c", "c", 4) // a is evicted; b and c survive with cleared bits + + if _, ok := shard.peek("b"); !ok { + t.Fatalf("peek(b) should hit") + } + + shard.add("d", "d", 4) // peek did not refresh b, so b is evicted + + if _, ok := shard.peek("b"); ok { + t.Fatalf("b should have been evicted; peek must not grant a second chance") + } + + for _, key := range []string{"c", "d"} { + if _, ok := shard.peek(key); !ok { + t.Fatalf("%s should remain present", key) + } + } +} + +func TestShardReplaceUpdatesWeight(t *testing.T) { + t.Parallel() + + shard := newShard[string, string](100) + shard.add("a", "old", 4) + shard.add("a", "new", 6) + + if got, ok := shard.peek("a"); !ok || got != "new" { + t.Fatalf("peek(a) = (%q, %v), want (new, true)", got, ok) + } + + if got := shard.loadWeight(); got != 6 { + t.Fatalf("weight = %d, want 6", got) + } + + if got := shard.len(); got != 1 { + t.Fatalf("len = %d, want 1", got) + } +} + +func TestShardRejectsOversized(t *testing.T) { + t.Parallel() + + shard := newShard[string, string](5) + shard.add("a", "x", 3) + + if shard.add("b", "big", 6) { + t.Fatalf("oversized add should report false") + } + + if shard.add("a", "huge", 6) { + t.Fatalf("oversized replace should report false") + } + + if got, ok := shard.peek("a"); !ok || got != "x" { + t.Fatalf("peek(a) = (%q, %v), want (x, true); cache must be unchanged", got, ok) + } + + if _, ok := shard.peek("b"); ok { + t.Fatalf("b must not have been admitted") + } + + if got := shard.loadWeight(); got != 3 { + t.Fatalf("weight = %d, want 3", got) + } +} + +func TestShardClear(t *testing.T) { + t.Parallel() + + shard := newShard[string, string](100) + shard.add("a", "a", 4) + shard.add("b", "b", 4) + + shard.clear() + + if got := shard.loadWeight(); got != 0 { + t.Fatalf("weight = %d, want 0", got) + } + + if got := shard.len(); got != 0 { + t.Fatalf("len = %d, want 0", got) + } + + if _, ok := shard.peek("a"); ok { + t.Fatalf("a should be gone after clear") + } +} -- cgit v1.3.1-10-gc9f91