Files
orbit/examples/http-service/cmd/server/lifecycle_test.go

91 lines
2.3 KiB
Go

package main
import (
"context"
"errors"
"net"
"net/http"
"sync/atomic"
"testing"
"time"
)
func TestShutdownKeepsDependenciesUntilHTTPReturns(t *testing.T) {
entered := make(chan struct{})
release := make(chan struct{})
workerStopped := make(chan struct{})
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { close(entered); <-release; w.WriteHeader(200) })}
ready := new(atomic.Bool)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan error, 1)
go func() {
done <- serve(ctx, listener, server, ready, []component{{"worker", func(ctx context.Context) error { <-ctx.Done(); close(workerStopped); return nil }}}, component{"events", func(ctx context.Context) error {
<-ctx.Done()
select {
case <-workerStopped:
return nil
default:
return errors.New("consumer stopped before worker")
}
}}, time.Second)
}()
response := make(chan error, 1)
go func() {
r, e := http.Get("http://" + listener.Addr().String())
if e == nil {
r.Body.Close()
}
response <- e
}()
select {
case <-entered:
case <-time.After(time.Second):
t.Fatal("HTTP not entered")
}
cancel()
select {
case <-workerStopped:
t.Fatal("worker stopped during HTTP transaction")
case <-time.After(20 * time.Millisecond):
}
if ready.Load() {
t.Fatal("readiness remained true during shutdown")
}
close(release)
if err = <-response; err != nil {
t.Fatal(err)
}
select {
case err = <-done:
if err != nil {
t.Fatal(err)
}
case <-time.After(3 * time.Second):
t.Fatal("shutdown blocked")
}
}
func TestBackgroundFailureStopsServiceAndPeers(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
boom := errors.New("fatal worker")
stopped := make(chan struct{})
result := serve(context.Background(), listener, &http.Server{}, new(atomic.Bool), []component{{"worker", func(context.Context) error { return boom }}}, component{"consumer", func(ctx context.Context) error { <-ctx.Done(); close(stopped); return nil }}, time.Second)
if !errors.Is(result, boom) {
t.Fatal(result)
}
select {
case <-stopped:
default:
t.Fatal("consumer leaked")
}
}