1package daemon23import (4 "context"5 "errors"6 "testing"7 "time"8)910func TestMaintenanceDrainsAndRejectsNewOperations(t *testing.T) {11 var gate maintenanceGate12 _, finish, err := gate.beginOperation(context.Background())13 if err != nil {14 t.Fatal(err)15 }16 started := make(chan struct{})17 finished := make(chan error, 1)18 go func() {19 close(started)20 _, release, beginErr := gate.beginMaintenance(context.Background())21 if beginErr == nil {22 release()23 }24 finished <- beginErr25 }()26 <-started27 select {28 case <-finished:29 t.Fatal("maintenance completed before active operation drained")30 case <-time.After(20 * time.Millisecond):31 }32 if _, _, err := gate.beginOperation(context.Background()); !errors.Is(err, ErrMaintenance) {33 t.Fatalf("new operation error = %v", err)34 }35 finish()36 select {37 case err := <-finished:38 if err != nil {39 t.Fatal(err)40 }41 case <-time.After(time.Second):42 t.Fatal("maintenance did not acquire after drain")43 }44}4546func TestMaintenanceCancellationClearsAdmissionFlag(t *testing.T) {47 var gate maintenanceGate48 _, finish, err := gate.beginOperation(context.Background())49 if err != nil {50 t.Fatal(err)51 }52 ctx, cancel := context.WithCancel(context.Background())53 result := make(chan error, 1)54 go func() {55 _, _, beginErr := gate.beginMaintenance(ctx)56 result <- beginErr57 }()58 cancel()59 if err := <-result; !errors.Is(err, context.Canceled) {60 t.Fatalf("maintenance cancellation error = %v", err)61 }62 finish()63 _, release, err := gate.beginOperation(context.Background())64 if err != nil {65 t.Fatalf("operation admission remained blocked: %v", err)66 }67 release()68}6970func TestMaintenanceIsExclusiveAndNestedMarkersDoNotDeadlock(t *testing.T) {71 var gate maintenanceGate72 maintenanceCtx, release, err := gate.beginMaintenance(context.Background())73 if err != nil {74 t.Fatal(err)75 }76 defer release()77 if _, nestedRelease, err := gate.beginMaintenance(maintenanceCtx); err != nil {78 t.Fatal(err)79 } else {80 nestedRelease()81 }82 if _, _, err := gate.beginMaintenance(context.Background()); !errors.Is(err, ErrMaintenanceBusy) {83 t.Fatalf("concurrent maintenance error = %v", err)84 }85 if _, nestedRelease, err := gate.beginOperation(maintenanceCtx); err != nil {86 t.Fatal(err)87 } else {88 nestedRelease()89 }90}9192func TestMaintenanceOperationMarkerAllowsNestedCallbacks(t *testing.T) {93 var gate maintenanceGate94 ctx, release, err := gate.beginOperation(context.Background())95 if err != nil {96 t.Fatal(err)97 }98 defer release()99 nested, nestedRelease, err := gate.beginOperation(ctx)100 if err != nil {101 t.Fatal(err)102 }103 nestedRelease()104 if nested != ctx {105 t.Fatal("nested operation did not preserve context marker")106 }107}108109func TestClosingPermanentlyRejectsOperationsAndMaintenance(t *testing.T) {110 var gate maintenanceGate111 gate.markClosing()112 if _, _, err := gate.beginOperation(context.Background()); !errors.Is(err, ErrTenantClosing) {113 t.Fatalf("closing operation error = %v", err)114 }115 if _, _, err := gate.beginMaintenance(context.Background()); !errors.Is(err, ErrTenantClosing) {116 t.Fatalf("closing maintenance error = %v", err)117 }118}