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 = 2 * time.Second // readHeaderTimeout bounds the test server's read of the request // header, as every http.Server must. readHeaderTimeout = time.Second ) 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") } }