check / check (push) Waiting to run
fx alone handles SIGINT and SIGTERM; the server's own handler, which only cancelled a context that fx's stop did not wait for, is gone. fx's Run exits with the shutdown's code: the one a shutdown request carries, 0 for a signal, 1 when the app fails to start or stop. A listen error asks fx to shut down with exit code 1. A Sentry DSN that cannot be used fails the server's start hook, so fx stops what had already started. The server's stop hook stops the HTTP server, then waits for the images still being processed, both within ShutdownTimeout; images still being processed after that are logged and fail the stop, so the exit code is 1. Model: opus-5-5
97 lines
2.4 KiB
Go
97 lines
2.4 KiB
Go
package server
|
|
|
|
import (
|
|
"log/slog"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"go.uber.org/fx"
|
|
"go.uber.org/fx/fxtest"
|
|
|
|
"sneak.berlin/go/pixa/internal/config"
|
|
"sneak.berlin/go/pixa/internal/globals"
|
|
"sneak.berlin/go/pixa/internal/logger"
|
|
)
|
|
|
|
// shutdownRecorder is an fx.Shutdowner that sends the options of each
|
|
// shutdown request on requests.
|
|
type shutdownRecorder struct {
|
|
requests chan []fx.ShutdownOption
|
|
}
|
|
|
|
func (r shutdownRecorder) Shutdown(opts ...fx.ShutdownOption) error {
|
|
r.requests <- opts
|
|
|
|
return nil
|
|
}
|
|
|
|
// TestSentryInitFailureFailsStartup checks that a Sentry DSN that cannot be
|
|
// used makes the server's start hook fail, so fx stops what has already
|
|
// started, instead of the process exiting from a goroutine.
|
|
func TestSentryInitFailureFailsStartup(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lc := fxtest.NewLifecycle(t)
|
|
|
|
log, err := logger.New(lc, logger.Params{Globals: &globals.Globals{}})
|
|
if err != nil {
|
|
t.Fatalf("logger.New() error = %v", err)
|
|
}
|
|
|
|
_, err = New(lc, Params{
|
|
Logger: log,
|
|
Globals: &globals.Globals{Appname: "pixad"},
|
|
Config: &config.Config{SentryDSN: "not-a-dsn"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
err = lc.Start(t.Context())
|
|
t.Logf("Start() error = %v", err)
|
|
|
|
if err == nil {
|
|
t.Fatal("Start() error = nil, want the Sentry initialization error")
|
|
}
|
|
}
|
|
|
|
// TestListenErrorRequestsShutdownWithExitCode1 occupies the server's port
|
|
// and checks that the listen error asks fx to shut down with exit code 1.
|
|
func TestListenErrorRequestsShutdownWithExitCode1(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
busy, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", ":0")
|
|
if err != nil {
|
|
t.Fatalf("Listen() error = %v", err)
|
|
}
|
|
|
|
t.Cleanup(func() { _ = busy.Close() })
|
|
|
|
addr, ok := busy.Addr().(*net.TCPAddr)
|
|
if !ok {
|
|
t.Fatalf("listener address %v is not a TCP address", busy.Addr())
|
|
}
|
|
|
|
requests := make(chan []fx.ShutdownOption, 1)
|
|
s := &Server{
|
|
log: slog.New(slog.DiscardHandler),
|
|
config: &config.Config{Port: addr.Port},
|
|
shutdowner: shutdownRecorder{requests: requests},
|
|
}
|
|
s.httpServer = s.newHTTPServer()
|
|
|
|
go s.serveUntilShutdown()
|
|
|
|
select {
|
|
case opts := <-requests:
|
|
t.Logf("shutdown options = %v", opts)
|
|
|
|
if len(opts) != 1 || opts[0] != fx.ExitCode(1) {
|
|
t.Errorf("shutdown options = %v, want [fx.ExitCode(1)]", opts)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("no shutdown was requested after the listen error")
|
|
}
|
|
}
|