82 lines
2.4 KiB
Go
82 lines
2.4 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestRuntimeRollbackAndConcurrentClose(t *testing.T) {
|
|
r := NewRuntime(context.Background())
|
|
var order []int
|
|
failure, cleanup := errors.New("initialization failed"), errors.New("cleanup failed")
|
|
r.OnClose(func() error { order = append(order, 1); return cleanup })
|
|
r.OnClose(func() error { order = append(order, 2); panic("broken dependency") })
|
|
r.OnClose(func() error { order = append(order, 3); return nil })
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 20; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := r.Close(); !errors.Is(err, cleanup) {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if !reflect.DeepEqual(order, []int{3, 2, 1}) {
|
|
t.Fatal(order)
|
|
}
|
|
err := r.Finish(failure)
|
|
if !errors.Is(err, failure) || !errors.Is(err, cleanup) || !strings.Contains(err.Error(), "cleanup panic") {
|
|
t.Fatal(err)
|
|
}
|
|
if r.Context().Err() == nil {
|
|
t.Fatal("runtime context was not stopped")
|
|
}
|
|
}
|
|
|
|
func TestRuntimeRetainsMonitorFaultDuringCleanup(t *testing.T) {
|
|
r := NewRuntime(context.Background())
|
|
fault := errors.New("monitor failed")
|
|
r.OnClose(func() error { r.RequestShutdown(fault); return nil })
|
|
r.RequestShutdown(nil)
|
|
if err := r.Finish(nil); !errors.Is(err, fault) {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if recover() == nil {
|
|
t.Fatal("late ownership transfer accepted")
|
|
}
|
|
}()
|
|
r.OnClose(func() error { return nil })
|
|
}
|
|
|
|
func TestFinishRetainsDependenciesAfterIncompleteHTTPDrain(t *testing.T) {
|
|
r := NewRuntime(context.Background())
|
|
var order []string
|
|
r.OnStop("worker", func(context.Context) error { order = append(order, "stop"); return nil })
|
|
r.OnDrain("worker", func(context.Context) error { order = append(order, "drain"); return nil })
|
|
r.OnClose(func() error { t.Error("released dependency while HTTP work remains"); return nil })
|
|
fault := errors.New("HTTP shutdown timeout")
|
|
err := r.Finish(errors.Join(fault, ErrResourcesRetained))
|
|
if !errors.Is(err, fault) || !errors.Is(err, ErrResourcesRetained) {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(order, []string{"stop", "drain"}) {
|
|
t.Fatal(order)
|
|
}
|
|
if r.WorkContext().Err() == nil {
|
|
t.Fatal("background work was not canceled")
|
|
}
|
|
if err = r.Close(); !errors.Is(err, ErrResourcesRetained) {
|
|
t.Fatal("deferred Close bypassed retention", err)
|
|
}
|
|
if err = r.Finish(nil); !errors.Is(err, ErrResourcesRetained) {
|
|
t.Fatal("repeat Finish bypassed retention", err)
|
|
}
|
|
}
|