From 7967d6396f7606f0e18f077e2febb01893a7852b Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 5 Nov 2026 12:55:08 +0000 Subject: [PATCH] rproxy tests give a loaded host time before their limits run out (closes #185) Four rproxy tests set a time limit of a second or less, and each needed a step done before that limit ran out. On a loaded host the limit ran out first, rproxy answered as SPEC.md says it should, and the test failed. Each of these limits is now 3 seconds: - TestAnAppTooSlowToFinishItsAnswerIsCutOff, which now also checks that the visitor got the app's 200 and the start of its answer; - TestAVisitorTooSlowToTakeTheAnswerIsCutOff; - TestAVisitorThatStopsSendingGetsAnAnswerRProxyGivesByTheDeadline; - TestAReportServeWAFLeavesUnansweredIsDroppedAtTheTimeLimit. Judgement call: tests whose limits already give 2 seconds or more are left as they are. Model: opus-5-5 --- internal/rproxy/limits_test.go | 34 +++++++++++++++++++++++++++------- internal/rproxy/rproxy_test.go | 4 +++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/internal/rproxy/limits_test.go b/internal/rproxy/limits_test.go index 638a085..b8df0f8 100644 --- a/internal/rproxy/limits_test.go +++ b/internal/rproxy/limits_test.go @@ -1,7 +1,9 @@ package rproxy_test import ( + "bufio" "bytes" + "errors" "io" "log/slog" "net" @@ -602,10 +604,12 @@ func TestAnAppTooSlowToAnswerIsAnswered504(t *testing.T) { func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) { t.Parallel() + const start = "the start" + release := make(chan struct{}) servewaf := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { - _, _ = io.WriteString(w, "the start") + _, _ = io.WriteString(w, start) _ = http.NewResponseController(w).Flush() <-release @@ -613,17 +617,29 @@ func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) { t.Cleanup(func() { close(release) }) + // The time limit is long enough for a loaded host to pass on the start of the + // answer before it runs out. addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ - maxResponseTime: "300", + maxResponseTime: "3000", }) conn := dial(t, addr) send(t, conn, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n") - // dial's deadline ends this read with an error if the connection stays open. - _, err := io.ReadAll(conn) + res, err := http.ReadResponse(bufio.NewReader(conn), nil) if err != nil { - t.Errorf("read: %v, want the connection closed", err) + t.Fatalf("read the answer: %v", err) + } + + // The body breaks off when rproxy closes the connection. If the connection + // stays open, dial's deadline ends this read with a timeout instead. + body, err := io.ReadAll(res.Body) + _ = res.Body.Close() + + if res.StatusCode != http.StatusOK || string(body) != start || + !errors.Is(err, io.ErrUnexpectedEOF) { + t.Errorf("answered %d %q (%v), want the app's 200 %q, then the connection closed", + res.StatusCode, body, err, start) } if report := servewaf.report(t); report.Status != http.StatusOK { @@ -651,8 +667,10 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) { } } }) + // The time limit is long enough for a loaded host to start passing on the + // answer before it runs out. addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ - maxResponseTime: "500", + maxResponseTime: "3000", }) // The visitor asks, and never reads the answer. @@ -793,8 +811,10 @@ func TestAVisitorThatStopsSendingGetsAnAnswerRProxyGivesByTheDeadline(t *testing servewaf := startServeWAF(t, deny) app := startApp(t, greet) + // The time limit is long enough for a loaded host to read the first part of + // the body before it runs out. addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ - maxRequestTime: "500", + maxRequestTime: "3000", }) // Go's server waits for a little of the body left before it sends an answer, diff --git a/internal/rproxy/rproxy_test.go b/internal/rproxy/rproxy_test.go index 2e65e00..79711e2 100644 --- a/internal/rproxy/rproxy_test.go +++ b/internal/rproxy/rproxy_test.go @@ -1353,8 +1353,10 @@ func TestAReportServeWAFLeavesUnansweredIsDroppedAtTheTimeLimit(t *testing.T) { }) app := startApp(t, greet) + // The time limit is long enough for a loaded host to answer the question before + // it runs out. addr, out := startRProxy(t, backend, app.URL, map[string]string{ - timeoutSetting: "1000", + timeoutSetting: "3000", }) t.Cleanup(func() { close(release) })