From 2777d3f784aab70e927d1d8958aa5fdb621f5d12 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Sat, 3 Oct 2026 15:53:34 +0000 Subject: [PATCH] Test that a shared miss keeps its deadline and reports panics (closes #65) A request with a deadline whose fetch waits on its context, as the fetcher does while it waits for a free connection to the host, must return by that deadline. A panic while a variant is being made must be reported through the Sentry hub on the request's context, where the Sentry middleware puts one when sentry_dsn is set. The panic test fails on next, where the panic leaves Get. Model: opus-5-5 --- .../concurrent_misses_internal_test.go | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/internal/imgcache/concurrent_misses_internal_test.go b/internal/imgcache/concurrent_misses_internal_test.go index 53b2db5..ad0e1b2 100644 --- a/internal/imgcache/concurrent_misses_internal_test.go +++ b/internal/imgcache/concurrent_misses_internal_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/getsentry/sentry-go" "sneak.berlin/go/pixa/internal/httpfetcher" "sneak.berlin/go/pixa/internal/magic" ) @@ -408,6 +409,32 @@ func TestService_Get_EndedRequestFetchesNothing(t *testing.T) { } } +// TestService_Get_ReturnsByItsDeadline gives a request a deadline and holds +// its fetch until the fetch's context ends, as the fetcher does while it waits +// for a free connection to the host. The request must return by its deadline +// with the deadline's error. +func TestService_Get_ReturnsByItsDeadline(t *testing.T) { + t.Parallel() + + svc, fixtures, _ := setupHeldFetchService(t) + + const timeout = 200 * time.Millisecond + + ctx, cancel := context.WithTimeout(t.Context(), timeout) + defer cancel() + + results := startGet(ctx, svc, photoVariant(fixtures, 85, FitCover)) + + select { + case got := <-results: + if !errors.Is(got.err, context.DeadlineExceeded) { + t.Errorf("Get() error = %v, want %v", got.err, context.DeadlineExceeded) + } + case <-time.After(timeout + time.Second): + t.Fatal("request did not return by its deadline") + } +} + // panickingFetcher panics on every fetch. type panickingFetcher struct{} @@ -442,3 +469,41 @@ func TestService_Get_PanicBecomesAnError(t *testing.T) { t.Errorf("Get() error = %v, want one naming the panic", err) } } + +// TestService_Get_PanicIsReportedToSentry checks that a panic while a variant +// is being made is reported through the Sentry hub on the request's context, +// where the Sentry middleware puts one when sentry_dsn is set. +func TestService_Get_PanicIsReportedToSentry(t *testing.T) { + t.Parallel() + + svc, fixtures := SetupTestService(t) + svc.fetcher = panickingFetcher{} + + transport := &sentry.MockTransport{} + + client, err := sentry.NewClient(sentry.ClientOptions{ + Dsn: "https://abc123@sentry.example.com/42", + Transport: transport, + }) + if err != nil { + t.Fatal(err) + } + + ctx := sentry.SetHubOnContext(t.Context(), + sentry.NewHub(client, sentry.NewScope())) + + func() { + defer func() { + if recovered := recover(); recovered != nil { + t.Fatalf("Get() panicked: %v", recovered) + } + }() + + _, _ = svc.Get(ctx, photoVariant(fixtures, 85, FitCover)) + }() + + events := transport.Events() + if len(events) != 1 || events[0].Message != "upstream fetcher panicked" { + t.Errorf("Sentry events = %+v, want one for the panic", events) + } +}