91 lines
2.3 KiB
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")
|
|
}
|
|
}
|