From 057c06132653ee149eb916cd6e1b49b752cd4e08 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Sun, 23 Aug 2026 01:41:02 +0100 Subject: [PATCH] test(providers): exercise fire, not ResponseHeaderTimeout, in the leak test TestWatchdogBody_NoBlockingGoroutineLeak_AcrossManyRequests drove its timed-out iterations against a server that never wrote headers. That hits Transport.ResponseHeaderTimeout inside client.Do and returns before a response ever exists, so watchdogBody.fire never runs and the test could not have caught a leak in fire itself. The mutation-verification pass (manually adding a permanently blocked goroutine to fire, then reverting) confirmed this: the test passed even with that goroutine present. Fixing the stalling server to write headers first, mirroring the mid-body-stall tests above it, makes fire run on every iteration. Re-running the same mutation now fails the test as intended. --- timeout_test.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/timeout_test.go b/timeout_test.go index e4db7e2..c3ffdd3 100644 --- a/timeout_test.go +++ b/timeout_test.go @@ -577,7 +577,18 @@ func TestWatchdogBody_NoBlockingGoroutineLeak_AcrossManyRequests(t *testing.T) { })) defer fastSrv.Close() - hangSrv, release := newStallingServer(nil) + // prewrite sends headers before stalling: a "never sends headers" hang + // is caught by Transport.ResponseHeaderTimeout, in a *http.Client.Do + // error, and never even constructs a watchdogBody — this test is + // specifically about the WATCHDOG's own goroutine, so its hang must + // stall AFTER headers, the same shape as the mid-body-stall tests + // above, to actually invoke fire() on every one of the iterations + // below. + hangSrv, release := newStallingServer(func(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.(http.Flusher).Flush() + }) runtime.GC() baseline := runtime.NumGoroutine()