package cli import ( "context" "log/slog" "net" "net/http" "sync" "testing" "time" ) // recordHandler captures log records for assertions. type recordHandler struct { mu sync.Mutex records []slog.Record } func (h *recordHandler) Enabled(_ context.Context, _ slog.Level) bool { return true } func (h *recordHandler) Handle(_ context.Context, r slog.Record) error { h.mu.Lock() h.records = append(h.records, r) h.mu.Unlock() return nil } func (h *recordHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h } func (h *recordHandler) WithGroup(_ string) slog.Handler { return h } func (h *recordHandler) hasWarn(msg string) bool { h.mu.Lock() defer h.mu.Unlock() for _, r := range h.records { if r.Level == slog.LevelWarn && r.Message == msg { return true } } return false } // TestServe_ShutdownError_IsLogged verifies that a shutdown timeout error is // logged as a warning rather than silently dropped. func TestServe_ShutdownError_IsLogged(t *testing.T) { // Start a real listener so we have an address. ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } // Handler that hangs so the active connection prevents clean shutdown. hang := make(chan struct{}) mux := http.NewServeMux() mux.HandleFunc("/hang", func(w http.ResponseWriter, r *http.Request) { <-hang }) srv := &http.Server{Handler: mux} // Serve in background. go srv.Serve(ln) //nolint:errcheck // Open a connection and start a hanging request so the server has an // active connection when we call Shutdown. addr := ln.Addr().String() connReady := make(chan struct{}) go func() { req, _ := http.NewRequest(http.MethodGet, "http://"+addr+"/hang", nil) close(connReady) http.DefaultClient.Do(req) //nolint:errcheck }() <-connReady // Give the goroutine a moment to establish the request. time.Sleep(20 * time.Millisecond) // Shutdown with an already-expired deadline so it times out immediately. expiredCtx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second)) defer cancel() h := &recordHandler{} logger := slog.New(h) // This is the exact logic from serve.go's shutdown goroutine. if err := srv.Shutdown(expiredCtx); err != nil { logger.Warn("shutdown error", "err", err) } // Unblock the hanging handler. close(hang) if !h.hasWarn("shutdown error") { t.Error("expected shutdown error to be logged as Warn, but it was not") } }