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) } }