Test that eviction ticks read nothing for reconciliation
The eviction loop runs the reconciliation pass on every periodic tick, so this test fails until the pass runs at startup and then once a day. Model: opus-5-5
This commit is contained in:
@@ -9,7 +9,9 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -918,6 +920,84 @@ func TestPeriodicReconciliationAdoptsFileThatAppearsAfterStartup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestEvictionTicksDoNotReconcile starts the eviction loop with an
|
||||
// interval of 10 ms. The startup pass reconciles before it removes the
|
||||
// expired negative cache rows, so once the expired row stored before the
|
||||
// start is gone, the startup reconciliation has made all its reads. The
|
||||
// test then waits for three periodic ticks, each seen when it removes an
|
||||
// expired row stored after the previous one went. The reconciliation
|
||||
// pass runs at startup and then once a day, so none of those ticks may
|
||||
// read a content table for reconciliation.
|
||||
func TestEvictionTicksDoNotReconcile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cache, _ := newEvictionTestCache(t, 1<<30)
|
||||
|
||||
var reconciliationReads atomic.Int64
|
||||
|
||||
cache.reconciliationReadTestHook = func(int) { reconciliationReads.Add(1) }
|
||||
|
||||
insertExpiredNegativeCacheRow(t, cache, "/startup.jpg")
|
||||
|
||||
cache.StartEviction(10 * time.Millisecond)
|
||||
defer func() { _ = cache.StopEviction(t.Context()) }()
|
||||
|
||||
waitForNegativeCacheRowRemoved(t, cache, "/startup.jpg")
|
||||
|
||||
startupReads := reconciliationReads.Load()
|
||||
t.Logf("the startup pass made %d reconciliation reads", startupReads)
|
||||
|
||||
if startupReads == 0 {
|
||||
t.Fatal("the startup pass made no reconciliation read")
|
||||
}
|
||||
|
||||
for tick := range 3 {
|
||||
path := "/tick-" + strconv.Itoa(tick) + ".jpg"
|
||||
|
||||
insertExpiredNegativeCacheRow(t, cache, path)
|
||||
waitForNegativeCacheRowRemoved(t, cache, path)
|
||||
}
|
||||
|
||||
tickReads := reconciliationReads.Load() - startupReads
|
||||
t.Logf("three eviction ticks made %d reconciliation reads", tickReads)
|
||||
|
||||
if tickReads != 0 {
|
||||
t.Errorf("three eviction ticks made %d reconciliation reads, want 0: "+
|
||||
"the reconciliation pass ran on an eviction tick", tickReads)
|
||||
}
|
||||
}
|
||||
|
||||
// insertExpiredNegativeCacheRow adds a negative_cache row for sourcePath
|
||||
// that expired an hour ago.
|
||||
func insertExpiredNegativeCacheRow(t *testing.T, cache *Cache, sourcePath string) {
|
||||
t.Helper()
|
||||
|
||||
_, err := cache.db.ExecContext(t.Context(), `
|
||||
INSERT INTO negative_cache
|
||||
(source_host, source_path, status_code, expires_at)
|
||||
VALUES (?, ?, 404, datetime('now', '-1 hour'))
|
||||
`, testHostExample, sourcePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert an expired negative cache row: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// waitForNegativeCacheRowRemoved waits up to five seconds for the
|
||||
// eviction loop to remove the negative_cache row for sourcePath.
|
||||
func waitForNegativeCacheRowRemoved(t *testing.T, cache *Cache, sourcePath string) {
|
||||
t.Helper()
|
||||
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
|
||||
for negativeCacheRowCount(t, cache, sourcePath) > 0 {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("the expired negative cache row for %s was not removed", sourcePath)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStopEvictionInterruptsPassInProgress holds the test database's
|
||||
// only connection, so the startup reconciliation pass waits for it, and
|
||||
// checks that StopEviction stops that pass instead of waiting for the
|
||||
|
||||
Reference in New Issue
Block a user