Description
Proposal #54136 (implemented in CL 436890 which is part of Go 1.20) added the "http".ResponseController
type, which allows manipulating per-request timeouts. This is especially useful for programs managing long-running HTTP connections such as Mercure.
However, testing HTTP handlers leveraging per-request timeouts is currently cumbersome (even if doable) because "net/http/httptest".ResponseRecorder
isn't compatible yet with "http".ResponseController
.
To make it fully compatible with response controllers and improve the testing experience, I propose the following additions to its public API:
type ResponseRecorder struct {
// ...
// ReadDeadline is the last read deadline that has been set using
// "net/http".ResponseController
ReadDeadline time.Time
// WriteDeadline is the last write deadline that has been set using
// "net/http".ResponseController
WriteDeadline time.Time
}
// SetReadDeadline allows using "net/http".ResponseController.SetReadDeadline()
// with the recorder.
//
// The deadline is recorded but is not enforced.
// To prevent flaky tests reads made after the deadline will work
// as if no deadline was set.
//
// To retrieve the deadline, use rw.ReadDeadline.
func (rw *ResponseRecorder) SetReadDeadline(deadline time.Time) error
// SetWriteDeadline allows using "net/http".ResponseController.SetWriteDeadline()
// with the recorder.
//
// The deadline is recorded but is not enforced.
// To prevent flaky tests writes made after the deadline will work
// as if no deadline was set.
//
// To retrieve the deadline, use rw.WriteDeadline.
func (rw *ResponseRecorder) SetWriteDeadline(deadline time.Time) error
All new methods are part of the contract that response types must honor to be usable with "HTTP".ResponseController
.
As discussed below, deadlines are recorded but not enforced to prevent flaky tests.
Proposal implementation: #60231
Activity
net/http/httptest: add support for http.ResponseController to
net/http/httptest: add support for http.ResponseController to
net/http/httptest: add support for http.ResponseController to
net/http/httptest: add support for ResponseController to ResponseReco…
gopherbot commentedon May 16, 2023
Change https://go.dev/cl/495295 mentions this issue:
net/http/httptest: add support for http.ResponseController to ResponseRecorder
neild commentedon May 16, 2023
I don't think
ResponseRecorder
should be trying to apply a read or write deadline.It can't apply a real read deadline, since it has no good way to interrupt reads from the request body. In CL 495295, setting a read deadline causes
req.Body.Read
calls made after the deadline has passed to return an error, but doesn't do anything to interrupt in-progress calls.Setting a write deadline can make write calls after the deadline has passed fail. Since
ResponseRecorder
writes to abytes.Buffer
, interrupting in-progress writes is not a concern.Making reads and writes fail in this fashion seems out of scope for
ResponseRecorder
(which just records responses).This also seems like an encouragement to write flaky tests. For example, in the example:
This may or may not write the response string to the recorder. If three seconds pass between the call to
SetWriteDeadline
and theWriteString
call, nothing is written. This might seem unlikely, but much test flakiness stems from exactly this sort of case when a slow test machine pauses for an unexpectedly long time between operations.If you do want to write tests that rely on a record
ResponseController
which supports deadlines, it's simple to composeResponseRecorder
with a custom controller that implementsSetReadDeadline
andSetWriteDeadline
:dunglas commentedon May 16, 2023
Thanks for the quick and detailed feedback @neild.
My main goal was to be able to easily assert that a deadline has been set (or changed) by the handler under test. I thought it was nice to have the full contract needed by
"http".ResponseController
implemented and to return an error "as in prod" if reached, but indeed this can encourage writing flaky tests.It's definitely doable to implement a custom type similar to what is in this PR (it's already what we do for Mercure), but the developer experience is usually better when it's not necessary to write custom code to test native features.
Do you think that there is value in providing the methods to set deadlines but not enforce them (to prevent flaky tests), or should I close this proposal and the related CL?
neild commentedon May 16, 2023
Recording deadlines and not enforcing them would fit within
ResponseRecorder
's remit. I worry a bit that it'd lead to confusion from users who expect the deadline to be enforced, butResponseRecorder
doesn't really lend itself to use outside of tests so that's probably not a significant concern.So I'd be fine with adding record-only deadline methods.
dunglas commentedon May 17, 2023
I updated the proposal and the related patch accordingly.
dunglas commentedon May 18, 2023
As deadlines can be updated during the HTTP handler execution, would it be interesting to record all calls to
Set*
methods?The API would then be:
30 remaining items