All checks were successful
check / check (push) Successful in 5s
Clears the final 80 golangci-lint findings under the canonical .golangci.yml (sha256 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb), taking the repo from red to green: script/cibuild exits 0. - wsl_v5 (60): blank line above defer/go statements sharing no variable with the line above; blank-line-only diff. - sqlclosecheck (10): the package-local CloseRows helper hid the close from the analyzer. Helper removed; all 18 call sites now defer an inline rows.Close(), preserving the fatal-on-close-error path. No resource leak existed - the rows were always being closed. - prealloc (3): append targets given a starting capacity. - revive (3): package-name findings suppressed with per-site directives pending the naming decision tracked in #76. No gosec suppressions are needed under the pinned linter. .golangci.yml, Dockerfile, Makefile, .gitea/ and script/ are byte-identical to main. Verified with script/cibuild (digest-pinned golangci-lint v2.12.2), not make check - the latter resolves the linter from PATH and is not a trustworthy gate here; see #78. Closes #59.
240 lines
3.9 KiB
Go
240 lines
3.9 KiB
Go
package vaultik //nolint:testpackage // exercises unexported blobDiskCache
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
func TestBlobDiskCache_BasicGetPut(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, err := newBlobDiskCache(1 << 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = cache.Close() }()
|
|
|
|
data := []byte("hello world")
|
|
|
|
err = cache.Put("key1", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, ok := cache.Get("key1")
|
|
if !ok {
|
|
t.Fatal("expected cache hit")
|
|
}
|
|
|
|
if !bytes.Equal(got, data) {
|
|
t.Fatalf("got %q, want %q", got, data)
|
|
}
|
|
|
|
_, ok = cache.Get("nonexistent")
|
|
if ok {
|
|
t.Fatal("expected cache miss")
|
|
}
|
|
}
|
|
|
|
func TestBlobDiskCache_EvictionUnderPressure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
maxBytes := int64(1000)
|
|
|
|
cache, err := newBlobDiskCache(maxBytes)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = cache.Close() }()
|
|
|
|
for i := range 5 {
|
|
data := make([]byte, 300)
|
|
|
|
err = cache.Put(fmt.Sprintf("key%d", i), data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if cache.Size() > maxBytes {
|
|
t.Fatalf("cache size %d exceeds max %d", cache.Size(), maxBytes)
|
|
}
|
|
|
|
if !cache.Has("key4") {
|
|
t.Fatal("expected key4 to be cached")
|
|
}
|
|
|
|
if cache.Has("key0") {
|
|
t.Fatal("expected key0 to be evicted")
|
|
}
|
|
}
|
|
|
|
func TestBlobDiskCache_OversizedEntryRejected(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, err := newBlobDiskCache(100)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = cache.Close() }()
|
|
|
|
data := make([]byte, 200)
|
|
|
|
err = cache.Put("big", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if cache.Has("big") {
|
|
t.Fatal("oversized entry should not be cached")
|
|
}
|
|
}
|
|
|
|
func TestBlobDiskCache_UpdateInPlace(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, err := newBlobDiskCache(1 << 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = cache.Close() }()
|
|
|
|
err = cache.Put("key1", []byte("v1"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = cache.Put("key1", []byte("version2"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, ok := cache.Get("key1")
|
|
if !ok {
|
|
t.Fatal("expected hit")
|
|
}
|
|
|
|
if string(got) != "version2" {
|
|
t.Fatalf("got %q, want %q", got, "version2")
|
|
}
|
|
|
|
if cache.Len() != 1 {
|
|
t.Fatalf("expected 1 entry, got %d", cache.Len())
|
|
}
|
|
|
|
if cache.Size() != int64(len("version2")) {
|
|
t.Fatalf("expected size %d, got %d", len("version2"), cache.Size())
|
|
}
|
|
}
|
|
|
|
func TestBlobDiskCache_ReadAt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, err := newBlobDiskCache(1 << 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = cache.Close() }()
|
|
|
|
data := make([]byte, 1024)
|
|
|
|
_, err = rand.Read(data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = cache.Put("blob1", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
chunk, err := cache.ReadAt("blob1", 100, 200)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !bytes.Equal(chunk, data[100:300]) {
|
|
t.Fatal("ReadAt returned wrong data")
|
|
}
|
|
|
|
_, err = cache.ReadAt("blob1", 900, 200)
|
|
if err == nil {
|
|
t.Fatal("expected error for out-of-bounds read")
|
|
}
|
|
|
|
_, err = cache.ReadAt("missing", 0, 10)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing key")
|
|
}
|
|
}
|
|
|
|
func TestBlobDiskCache_Close(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, err := newBlobDiskCache(1 << 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = cache.Put("key1", []byte("data"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = cache.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBlobDiskCache_LRUOrder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, err := newBlobDiskCache(200)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() { _ = cache.Close() }()
|
|
|
|
d := make([]byte, 100)
|
|
|
|
err = cache.Put("a", d)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = cache.Put("b", d)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Access "a" to make it most recently used
|
|
cache.Get("a")
|
|
|
|
// Adding "c" should evict "b" (LRU), not "a"
|
|
err = cache.Put("c", d)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !cache.Has("a") {
|
|
t.Fatal("expected 'a' to survive")
|
|
}
|
|
|
|
if !cache.Has("c") {
|
|
t.Fatal("expected 'c' to be present")
|
|
}
|
|
|
|
if cache.Has("b") {
|
|
t.Fatal("expected 'b' to be evicted")
|
|
}
|
|
}
|