check / check (push) Canceled after 0s
The new package internal/imageproxy holds Proxy, which was imgcache.Service, with its Config, ImageResponse, CacheStatus and errors. NewProxy makes and registers the counters of upstream fetches and transforms. internal/imgcache holds the cache only, with its hit and miss counters, and imgcache.CacheConfig is imgcache.Config. The cache reads and writes each kind it keeps with one pair of methods: Variant and PutVariant, Source and PutSource, Failure and PutFailure. Source replaces LookupSource followed by GetSourceContent. imgcache.go and module.go are gone. The proxy's tests moved with it. They reach the cache through its database, its state directory and its metrics registry, and run its reconciliation pass with a second cache, as pixad does at startup. Model: opus-5-5
79 lines
2.3 KiB
Go
79 lines
2.3 KiB
Go
package imageproxy
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/pixa/internal/imagerequest"
|
|
)
|
|
|
|
// pendingAccessTimesWait is how long a test waits for the database's
|
|
// connection, for a cache hit to be answered, for an access time to reach
|
|
// the database, or for a request to return past its deadline.
|
|
const pendingAccessTimesWait = 5 * time.Second
|
|
|
|
// holdDatabase takes the one connection of db, so that every other query
|
|
// waits for it, and returns the func that frees it.
|
|
func holdDatabase(t *testing.T, db *sql.DB) func() {
|
|
t.Helper()
|
|
|
|
ctx, cancel := context.WithTimeout(t.Context(), pendingAccessTimesWait)
|
|
defer cancel()
|
|
|
|
conn, err := db.Conn(ctx)
|
|
if err != nil {
|
|
t.Fatalf("failed to take the database connection: %v", err)
|
|
}
|
|
|
|
return func() { _ = conn.Close() }
|
|
}
|
|
|
|
// TestProxy_Get_ReturnsByItsDeadlineWhileTheDatabaseIsBusy holds the
|
|
// database's one connection while a request whose fetch is held reaches its
|
|
// deadline. The request must return with the deadline's error while the
|
|
// connection is still held, as it waits for no database write past its
|
|
// deadline.
|
|
func TestProxy_Get_ReturnsByItsDeadlineWhileTheDatabaseIsBusy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svc, fixtures, fetcher := setupHeldFetchProxy(t)
|
|
|
|
svc.cache.StartPendingAccessTimeWrites()
|
|
defer func() { _ = svc.cache.StopPendingAccessTimeWrites(t.Context()) }()
|
|
|
|
// Room for the request to reach its fetch on a busy host
|
|
const timeout = 2 * time.Second
|
|
|
|
ctx, cancel := context.WithTimeout(t.Context(), timeout)
|
|
defer cancel()
|
|
|
|
deadline, _ := ctx.Deadline()
|
|
|
|
results := startGet(ctx, svc, photoVariant(fixtures, 85, imagerequest.FitCover))
|
|
|
|
// The request has made its database reads by the time it fetches.
|
|
select {
|
|
case <-fetcher.started:
|
|
case <-time.After(timeout):
|
|
t.Fatal("request did not reach its fetch by its deadline")
|
|
}
|
|
|
|
releaseDatabase := holdDatabase(t, svc.db)
|
|
defer releaseDatabase()
|
|
|
|
select {
|
|
case got := <-results:
|
|
t.Logf("Get() returned %v after its deadline, error = %v",
|
|
time.Since(deadline), got.err)
|
|
|
|
if !errors.Is(got.err, context.DeadlineExceeded) {
|
|
t.Errorf("Get() error = %v, want %v", got.err, context.DeadlineExceeded)
|
|
}
|
|
case <-time.After(time.Until(deadline) + pendingAccessTimesWait):
|
|
t.Fatal("request did not return while the database was busy")
|
|
}
|
|
}
|