mirror of
https://github.com/hanzo-fi/go-libs.git
synced 2026-10-30 20:53:44 +00:00
153 lines
4.1 KiB
Go
153 lines
4.1 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
logging "github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/transport/serverport"
|
|
)
|
|
|
|
// safeBuffer is a goroutine-safe io.Writer used to capture log output emitted
|
|
// from the Serve goroutine.
|
|
type safeBuffer struct {
|
|
mu sync.Mutex
|
|
buf bytes.Buffer
|
|
}
|
|
|
|
func (s *safeBuffer) Write(p []byte) (int, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.buf.Write(p)
|
|
}
|
|
|
|
func (s *safeBuffer) String() string {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.buf.String()
|
|
}
|
|
|
|
// TestStartServerServeErrorDoesNotPanic is a regression test for EN-1160: an
|
|
// unexpected Serve error (anything other than http.ErrServerClosed) used to
|
|
// panic inside an unmonitored goroutine, killing the whole process and
|
|
// bypassing fx OnStop hooks. It must be logged instead.
|
|
func TestStartServerServeErrorDoesNotPanic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
buf := &safeBuffer{}
|
|
ctx := logging.ContextWithLogger(context.Background(), logging.NewDefaultLogger(buf, false, false, false))
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
|
|
server := serverport.NewServer(serverPortDiscr, serverport.WithListener(listener))
|
|
|
|
stop, err := startServer(ctx, server, http.NewServeMux())
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
_ = stop(context.Background())
|
|
})
|
|
|
|
// Close the listener out from under the server: Serve returns a
|
|
// non-ErrServerClosed error. A panic in the Serve goroutine would crash
|
|
// the test binary; instead the error must be logged.
|
|
require.NoError(t, listener.Close())
|
|
|
|
require.Eventually(t, func() bool {
|
|
return strings.Contains(buf.String(), "failed to serve")
|
|
}, 5*time.Second, 10*time.Millisecond)
|
|
}
|
|
|
|
// TestStartServerGracefulShutdownDoesNotLogError checks that a graceful
|
|
// shutdown (Serve returning http.ErrServerClosed) is not reported as an error.
|
|
func TestStartServerGracefulShutdownDoesNotLogError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
buf := &safeBuffer{}
|
|
ctx := logging.ContextWithLogger(context.Background(), logging.NewDefaultLogger(buf, false, false, false))
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
|
|
server := serverport.NewServer(serverPortDiscr, serverport.WithListener(listener))
|
|
|
|
stop, err := startServer(ctx, server, http.NewServeMux())
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, stop(context.Background()))
|
|
|
|
// Give the Serve goroutine time to observe http.ErrServerClosed.
|
|
time.Sleep(100 * time.Millisecond)
|
|
require.NotContains(t, buf.String(), "failed to serve")
|
|
}
|
|
|
|
func TestStartServerSetsDefaultTimeouts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
|
|
server := serverport.NewServer(serverPortDiscr, serverport.WithListener(listener))
|
|
var configured struct {
|
|
readHeaderTimeout time.Duration
|
|
readTimeout time.Duration
|
|
writeTimeout time.Duration
|
|
idleTimeout time.Duration
|
|
}
|
|
|
|
stop, err := startServer(
|
|
logging.TestingContext(),
|
|
server,
|
|
http.NewServeMux(),
|
|
func(server *http.Server) {
|
|
configured.readHeaderTimeout = server.ReadHeaderTimeout
|
|
configured.readTimeout = server.ReadTimeout
|
|
configured.writeTimeout = server.WriteTimeout
|
|
configured.idleTimeout = server.IdleTimeout
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
_ = stop(context.Background())
|
|
})
|
|
|
|
require.Equal(t, 10*time.Second, configured.readHeaderTimeout)
|
|
require.Zero(t, configured.readTimeout)
|
|
require.Zero(t, configured.writeTimeout)
|
|
require.Equal(t, 120*time.Second, configured.idleTimeout)
|
|
}
|
|
|
|
func TestStartServerAllowsReadTimeoutOverride(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
|
|
server := serverport.NewServer(serverPortDiscr, serverport.WithListener(listener))
|
|
var configured time.Duration
|
|
|
|
stop, err := startServer(
|
|
logging.TestingContext(),
|
|
server,
|
|
http.NewServeMux(),
|
|
func(server *http.Server) {
|
|
server.ReadTimeout = time.Minute
|
|
configured = server.ReadTimeout
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
_ = stop(context.Background())
|
|
})
|
|
|
|
require.Equal(t, time.Minute, configured)
|
|
}
|