check / check (push) Canceled after 0s
Tests that waited a few seconds for something to happen now wait 15 seconds. The wait ends as soon as the condition holds, so a passing run takes no longer. TestStreamNotDroppedWhileLinesArrive, and the tests whose requests pass a 2- or 4-second limit in the server, run on a fake clock, so a stall on the host cannot use up the limit. The checkpoint tests give the writing connection a 30-second busy timeout, so their limit can be 15 seconds and a checkpoint that waits for a read still fails them. Model: opus-5-5
93 lines
2.2 KiB
Go
93 lines
2.2 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
|
)
|
|
|
|
// TestStopClosesTheConnectionOfARequestStillRunning serves a request whose
|
|
// handler does not return until the test ends, and stops the server with a
|
|
// deadline that passes while the request runs. The client must then see its
|
|
// connection closed. Before the fix Stop returned at the deadline and left the
|
|
// connection open until the handler returned.
|
|
func TestStopClosesTheConnectionOfARequestStillRunning(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
stopDeadline = 100 * time.Millisecond
|
|
// waitTimeout is how long the test waits for the request to reach
|
|
// the handler, and then for the client to see its connection closed.
|
|
waitTimeout = 15 * time.Second
|
|
// readHeaderTimeout bounds the test server's read of the request
|
|
// header, as every http.Server must.
|
|
readHeaderTimeout = waitTimeout
|
|
)
|
|
|
|
release := make(chan struct{})
|
|
defer close(release)
|
|
|
|
started := make(chan struct{})
|
|
|
|
var listenConfig net.ListenConfig
|
|
|
|
listener, err := listenConfig.Listen(t.Context(), "tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("failed to open the test listener: %v", err)
|
|
}
|
|
|
|
s := &Server{logger: logger.New(), srv: &http.Server{
|
|
Handler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
|
close(started)
|
|
<-release
|
|
}),
|
|
ReadHeaderTimeout: readHeaderTimeout,
|
|
}}
|
|
|
|
go func() { _ = s.srv.Serve(listener) }()
|
|
|
|
answered := make(chan error, 1)
|
|
|
|
go func() {
|
|
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet,
|
|
"http://"+listener.Addr().String()+"/", nil)
|
|
if err != nil {
|
|
answered <- err
|
|
|
|
return
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err == nil {
|
|
_ = resp.Body.Close()
|
|
}
|
|
|
|
answered <- err
|
|
}()
|
|
|
|
select {
|
|
case <-started:
|
|
case <-time.After(waitTimeout):
|
|
t.Fatal("the request did not reach the handler")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(t.Context(), stopDeadline)
|
|
defer cancel()
|
|
|
|
_ = s.Stop(ctx)
|
|
|
|
select {
|
|
case err := <-answered:
|
|
if err == nil {
|
|
t.Error("the request still running at the deadline was answered")
|
|
}
|
|
case <-time.After(waitTimeout):
|
|
t.Fatal("the connection of the request still running at the " +
|
|
"deadline was open after Stop returned")
|
|
}
|
|
}
|