check / check (push) Waiting to run
Removes output_content and request_cache, which nothing writes, and every column that was written but never read: the host, path, query, content_type and fetched_at of source_content, the content_type and created_at of variant_content, and the status_code, error_message and fetched_at of negative_cache. StoreSource no longer takes the fetch result and LookupSource returns only the source key. StoreVariant no longer takes a content type and StoreNegative no longer takes a status code or message, so extractStatusCode and the status constants, which only fed status_code, are gone. Reconciliation adopts a variant file without opening it, as its row no longer records a content type. Tests that set or checked a removed column follow. Model: opus-5-5
188 lines
5.3 KiB
Go
188 lines
5.3 KiB
Go
package imgcache
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Static errors returned by the stub free-space probes below.
|
|
var (
|
|
errTestStatfsFailed = errors.New("statfs failed")
|
|
errTestProbeNotExpected = errors.New("probe must not be called")
|
|
)
|
|
|
|
// TestComputeDefaultMaxBytesCountsWhatTheCacheHolds verifies that the
|
|
// default limit is 75% of the free space plus what the cache already
|
|
// holds, so a cache filled to its limit keeps that limit across a
|
|
// restart instead of shrinking to 75% of the space left free.
|
|
func TestComputeDefaultMaxBytesCountsWhatTheCacheHolds(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, _ := newEvictionTestCache(t, 1<<30)
|
|
|
|
// Empty cache, 4 GiB free -> 3 GiB default.
|
|
got, err := cache.computeDefaultMaxBytes(t.Context(),
|
|
func(string) (uint64, error) { return 4294967296, nil })
|
|
if err != nil {
|
|
t.Fatalf("computeDefaultMaxBytes returned error: %v", err)
|
|
}
|
|
|
|
t.Logf("default for an empty cache with 4 GiB free: %d", got)
|
|
|
|
if got != 3221225472 {
|
|
t.Errorf("default for an empty cache = %d, want 3221225472 (75%% of 4 GiB)",
|
|
got)
|
|
}
|
|
|
|
// The cache now holds those 3 GiB, which leaves 1 GiB free.
|
|
_, err = cache.db.ExecContext(t.Context(),
|
|
`INSERT INTO variant_content (cache_key, size_bytes) VALUES (?, ?)`,
|
|
string(testVariantKeyOne), 3221225472,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("failed to insert variant accounting row: %v", err)
|
|
}
|
|
|
|
got, err = cache.computeDefaultMaxBytes(t.Context(),
|
|
func(string) (uint64, error) { return 1073741824, nil })
|
|
if err != nil {
|
|
t.Fatalf("computeDefaultMaxBytes returned error: %v", err)
|
|
}
|
|
|
|
t.Logf("default for a cache holding 3 GiB with 1 GiB free: %d", got)
|
|
|
|
if got != 3221225472 {
|
|
t.Errorf("default for a cache holding 3 GiB with 1 GiB free = %d, "+
|
|
"want 3221225472 (75%% of 1 GiB + 3 GiB)", got)
|
|
}
|
|
}
|
|
|
|
// TestComputeDefaultMaxBytesAppliesFloor verifies that when 75% of the
|
|
// free space plus what the cache holds is below 500 MiB, the default
|
|
// is floored at DefaultCacheMaxBytesFloor.
|
|
func TestComputeDefaultMaxBytesAppliesFloor(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
freeBytes uint64
|
|
}{
|
|
{name: "100 MiB free", freeBytes: 104857600},
|
|
{name: "zero free", freeBytes: 0},
|
|
{name: "just below floor threshold", freeBytes: 699050665},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, _ := newEvictionTestCache(t, 1<<30)
|
|
|
|
got, err := cache.computeDefaultMaxBytes(t.Context(),
|
|
func(string) (uint64, error) { return tc.freeBytes, nil })
|
|
if err != nil {
|
|
t.Fatalf("computeDefaultMaxBytes returned error: %v", err)
|
|
}
|
|
|
|
if got != DefaultCacheMaxBytesFloor {
|
|
t.Errorf("computeDefaultMaxBytes = %d, want floor %d",
|
|
got, DefaultCacheMaxBytesFloor)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestComputeDefaultMaxBytesPropagatesProbeError verifies that a
|
|
// failing free-space probe produces an error naming cache_max_bytes,
|
|
// instead of a silently wrong default.
|
|
func TestComputeDefaultMaxBytesPropagatesProbeError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache, _ := newEvictionTestCache(t, 1<<30)
|
|
|
|
_, err := cache.computeDefaultMaxBytes(t.Context(),
|
|
func(string) (uint64, error) { return 0, errTestStatfsFailed })
|
|
if err == nil {
|
|
t.Fatal("probe failure must produce an error, got nil")
|
|
}
|
|
|
|
t.Logf("got expected error: %v", err)
|
|
|
|
if !strings.Contains(err.Error(), "cache_max_bytes") {
|
|
t.Errorf("error %q does not name cache_max_bytes", err.Error())
|
|
}
|
|
}
|
|
|
|
// TestNewCacheComputesDefaultMaxBytesWhenAsked verifies that with
|
|
// UseDefaultMaxBytes set, the cache's limit becomes the computed
|
|
// default, and that the probe is pointed at <state_dir>/cache/, which
|
|
// must be created first so statfs measures the right filesystem.
|
|
func TestNewCacheComputesDefaultMaxBytesWhenAsked(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
stateDir := t.TempDir()
|
|
wantCacheDir := filepath.Join(stateDir, "cache")
|
|
|
|
var probedPath string
|
|
|
|
// 4 GiB free -> 3 GiB default.
|
|
probe := func(path string) (uint64, error) {
|
|
probedPath = path
|
|
|
|
info, err := os.Stat(path)
|
|
if err != nil || !info.IsDir() {
|
|
t.Errorf("cache directory %q was not created before probing: info=%v err=%v",
|
|
path, info, err)
|
|
}
|
|
|
|
return 4294967296, nil
|
|
}
|
|
|
|
cache, err := newCache(evictionTestDB(t), CacheConfig{
|
|
StateDir: stateDir,
|
|
UseDefaultMaxBytes: true,
|
|
}, probe)
|
|
if err != nil {
|
|
t.Fatalf("newCache returned error: %v", err)
|
|
}
|
|
|
|
if cache.config.MaxBytes != 3221225472 {
|
|
t.Errorf("MaxBytes = %d, want computed default 3221225472",
|
|
cache.config.MaxBytes)
|
|
}
|
|
|
|
if probedPath != wantCacheDir {
|
|
t.Errorf("free space probed at %q, want cache directory %q",
|
|
probedPath, wantCacheDir)
|
|
}
|
|
}
|
|
|
|
// TestNewCacheKeepsExplicitMaxBytes verifies that without
|
|
// UseDefaultMaxBytes the cache keeps MaxBytes exactly as given and
|
|
// never consults the free-space probe.
|
|
func TestNewCacheKeepsExplicitMaxBytes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
probe := func(string) (uint64, error) {
|
|
t.Error("free-space probe must not be consulted for explicit values")
|
|
|
|
return 0, errTestProbeNotExpected
|
|
}
|
|
|
|
cache, err := newCache(evictionTestDB(t), CacheConfig{
|
|
StateDir: t.TempDir(),
|
|
MaxBytes: 1024,
|
|
}, probe)
|
|
if err != nil {
|
|
t.Fatalf("newCache returned error: %v", err)
|
|
}
|
|
|
|
if cache.config.MaxBytes != 1024 {
|
|
t.Errorf("MaxBytes = %d, want explicit 1024 (no floor, no recompute)",
|
|
cache.config.MaxBytes)
|
|
}
|
|
}
|