@@ -911,21 +911,21 @@ final class ComponentLifecycleTests: XCTestCase {
911911 func testShutdownOnlyStarted( ) {
912912 class Item {
913913 let label : String
914- let sempahore : DispatchSemaphore
914+ let semaphore : DispatchSemaphore
915915 let failStart : Bool
916- let exptectedState : State
916+ let expectedState : State
917917 var state = State . idle
918918
919919 deinit {
920- XCTAssertEqual ( self . state, self . exptectedState , " \" \( self . label) \" should be \( self . exptectedState ) " )
921- self . sempahore . signal ( )
920+ XCTAssertEqual ( self . state, self . expectedState , " \" \( self . label) \" should be \( self . expectedState ) " )
921+ self . semaphore . signal ( )
922922 }
923923
924- init ( label: String , failStart: Bool , exptectedState : State , sempahore : DispatchSemaphore ) {
924+ init ( label: String , failStart: Bool , expectedState : State , semaphore : DispatchSemaphore ) {
925925 self . label = label
926926 self . failStart = failStart
927- self . exptectedState = exptectedState
928- self . sempahore = sempahore
927+ self . expectedState = expectedState
928+ self . semaphore = semaphore
929929 }
930930
931931 func start( ) throws {
@@ -951,11 +951,12 @@ final class ComponentLifecycleTests: XCTestCase {
951951 }
952952
953953 let count = Int . random ( in: 10 ..< 20 )
954- let sempahore = DispatchSemaphore ( value: count)
954+ let semaphore = DispatchSemaphore ( value: count)
955955 let lifecycle = ServiceLifecycle ( configuration: . init( shutdownSignal: nil ) )
956956
957957 for index in 0 ..< count {
958- let item = Item ( label: " \( index) " , failStart: index == count / 2 , exptectedState: index <= count / 2 ? . shutdown : . idle, sempahore: sempahore)
958+ let failStart = index == count / 2
959+ let item = Item ( label: " \( index) " , failStart: failStart, expectedState: failStart ? . error : index <= count / 2 ? . shutdown : . idle, semaphore: semaphore)
959960 lifecycle. register ( label: item. label, start: . sync( item. start) , shutdown: . sync( item. shutdown) )
960961 }
961962
@@ -965,25 +966,29 @@ final class ComponentLifecycleTests: XCTestCase {
965966 }
966967 lifecycle. wait ( )
967968
968- XCTAssertEqual ( . success, sempahore . wait ( timeout: . now( ) + 1 ) )
969+ XCTAssertEqual ( . success, semaphore . wait ( timeout: . now( ) + 1 ) )
969970 }
970971
971972 func testShutdownWhenStartFailedIfAsked( ) {
972973 class DestructionSensitive {
973974 let label : String
974975 let failStart : Bool
975- let sempahore : DispatchSemaphore
976+ let semaphore : DispatchSemaphore
976977 var state = State . idle
977978
978979 deinit {
979- XCTAssertEqual ( self . state, . shutdown, " \" \( self . label) \" should be shutdown " )
980- self . sempahore. signal ( )
980+ if failStart {
981+ XCTAssertEqual ( self . state, . error, " \" \( self . label) \" should be error " )
982+ } else {
983+ XCTAssertEqual ( self . state, . shutdown, " \" \( self . label) \" should be shutdown " )
984+ }
985+ self . semaphore. signal ( )
981986 }
982987
983- init ( label: String , failStart: Bool = false , sempahore : DispatchSemaphore ) {
988+ init ( label: String , failStart: Bool = false , semaphore : DispatchSemaphore ) {
984989 self . label = label
985990 self . failStart = failStart
986- self . sempahore = sempahore
991+ self . semaphore = semaphore
987992 }
988993
989994 func start( ) throws {
@@ -1008,25 +1013,25 @@ final class ComponentLifecycleTests: XCTestCase {
10081013 struct InitError : Error { }
10091014 }
10101015
1011- let sempahore = DispatchSemaphore ( value: 6 )
1016+ let semaphore = DispatchSemaphore ( value: 6 )
10121017 let lifecycle = ServiceLifecycle ( configuration: . init( shutdownSignal: nil ) )
10131018
1014- let item1 = DestructionSensitive ( label: " 1 " , sempahore : sempahore )
1019+ let item1 = DestructionSensitive ( label: " 1 " , semaphore : semaphore )
10151020 lifecycle. register ( label: item1. label, start: . sync( item1. start) , shutdown: . sync( item1. shutdown) )
10161021
1017- let item2 = DestructionSensitive ( label: " 2 " , sempahore : sempahore )
1022+ let item2 = DestructionSensitive ( label: " 2 " , semaphore : semaphore )
10181023 lifecycle. registerShutdown ( label: item2. label, . sync( item2. shutdown) )
10191024
1020- let item3 = DestructionSensitive ( label: " 3 " , failStart: true , sempahore : sempahore )
1025+ let item3 = DestructionSensitive ( label: " 3 " , failStart: true , semaphore : semaphore )
10211026 lifecycle. register ( label: item3. label, start: . sync( item3. start) , shutdown: . sync( item3. shutdown) )
10221027
1023- let item4 = DestructionSensitive ( label: " 4 " , sempahore : sempahore )
1028+ let item4 = DestructionSensitive ( label: " 4 " , semaphore : semaphore )
10241029 lifecycle. registerShutdown ( label: item4. label, . sync( item4. shutdown) )
10251030
1026- let item5 = DestructionSensitive ( label: " 5 " , sempahore : sempahore )
1031+ let item5 = DestructionSensitive ( label: " 5 " , semaphore : semaphore )
10271032 lifecycle. register ( label: item5. label, start: . none, shutdown: . sync( item5. shutdown) )
10281033
1029- let item6 = DestructionSensitive ( label: " 6 " , sempahore : sempahore )
1034+ let item6 = DestructionSensitive ( label: " 6 " , semaphore : semaphore )
10301035 lifecycle. register ( _LifecycleTask ( label: item6. label, shutdownIfNotStarted: true , start: . sync( item6. start) , shutdown: . sync( item6. shutdown) ) )
10311036
10321037 lifecycle. start { error in
@@ -1035,7 +1040,99 @@ final class ComponentLifecycleTests: XCTestCase {
10351040 }
10361041 lifecycle. wait ( )
10371042
1038- XCTAssertEqual ( . success, sempahore. wait ( timeout: . now( ) + 1 ) )
1043+ XCTAssertEqual ( . success, semaphore. wait ( timeout: . now( ) + 1 ) )
1044+ }
1045+
1046+ func testShutdownWhenStartFailsAndAsked( ) {
1047+ class BadItem : LifecycleTask {
1048+ let label : String = UUID ( ) . uuidString
1049+ var shutdown : Bool = false
1050+
1051+ func start( _ callback: ( Error ? ) -> Void ) {
1052+ callback ( TestError ( ) )
1053+ }
1054+
1055+ func shutdown( _ callback: ( Error ? ) -> Void ) {
1056+ self . shutdown = true
1057+ callback ( nil )
1058+ }
1059+ }
1060+
1061+ do {
1062+ let lifecycle = ComponentLifecycle ( label: " test " )
1063+
1064+ let item = BadItem ( )
1065+ lifecycle. register ( label: " test " , start: . async( item. start) , shutdown: . async( item. shutdown) , shutdownIfNotStarted: true )
1066+
1067+ XCTAssertThrowsError ( try lifecycle. startAndWait ( ) ) { error in
1068+ XCTAssert ( error is TestError , " expected error to match " )
1069+ }
1070+
1071+ XCTAssertTrue ( item. shutdown, " expected item to be shutdown " )
1072+ }
1073+
1074+ do {
1075+ let lifecycle = ComponentLifecycle ( label: " test " )
1076+
1077+ let item = BadItem ( )
1078+ lifecycle. register ( label: " test " , start: . async( item. start) , shutdown: . async( item. shutdown) , shutdownIfNotStarted: false )
1079+
1080+ XCTAssertThrowsError ( try lifecycle. startAndWait ( ) ) { error in
1081+ XCTAssert ( error is TestError , " expected error to match " )
1082+ }
1083+
1084+ XCTAssertFalse ( item. shutdown, " expected item to be not shutdown " )
1085+ }
1086+
1087+ do {
1088+ let lifecycle = ComponentLifecycle ( label: " test " )
1089+
1090+ let item1 = GoodItem ( )
1091+ lifecycle. register ( item1)
1092+
1093+ let item2 = BadItem ( )
1094+ lifecycle. register ( label: " test " , start: . async( item2. start) , shutdown: . async( item2. shutdown) , shutdownIfNotStarted: true )
1095+
1096+ let item3 = GoodItem ( )
1097+ lifecycle. register ( item3)
1098+
1099+ let item4 = GoodItem ( )
1100+ lifecycle. registerShutdown ( label: " test " , . async( item4. shutdown) )
1101+
1102+ XCTAssertThrowsError ( try lifecycle. startAndWait ( ) ) { error in
1103+ XCTAssert ( error is TestError , " expected error to match " )
1104+ }
1105+
1106+ XCTAssertEqual ( item1. state, . shutdown, " expected item to be shutdown " )
1107+ XCTAssertTrue ( item2. shutdown, " expected item to be shutdown " )
1108+ XCTAssertEqual ( item3. state, . idle, " expected item to be idle " )
1109+ XCTAssertEqual ( item4. state, . shutdown, " expected item to be shutdown " )
1110+ }
1111+
1112+ do {
1113+ let lifecycle = ComponentLifecycle ( label: " test " )
1114+
1115+ let item1 = GoodItem ( )
1116+ lifecycle. register ( item1)
1117+
1118+ let item2 = BadItem ( )
1119+ lifecycle. register ( label: " test " , start: . async( item2. start) , shutdown: . async( item2. shutdown) , shutdownIfNotStarted: false )
1120+
1121+ let item3 = GoodItem ( )
1122+ lifecycle. register ( item3)
1123+
1124+ let item4 = GoodItem ( )
1125+ lifecycle. registerShutdown ( label: " test " , . async( item4. shutdown) )
1126+
1127+ XCTAssertThrowsError ( try lifecycle. startAndWait ( ) ) { error in
1128+ XCTAssert ( error is TestError , " expected error to match " )
1129+ }
1130+
1131+ XCTAssertEqual ( item1. state, . shutdown, " expected item to be shutdown " )
1132+ XCTAssertFalse ( item2. shutdown, " expected item to be not shutdown " )
1133+ XCTAssertEqual ( item3. state, . idle, " expected item to be idle " )
1134+ XCTAssertEqual ( item4. state, . shutdown, " expected item to be shutdown " )
1135+ }
10391136 }
10401137
10411138 func testStatefulSync( ) {
@@ -1398,7 +1495,43 @@ final class ComponentLifecycleTests: XCTestCase {
13981495 let lifecycle = ComponentLifecycle ( label: " test " )
13991496
14001497 let item = Item ( )
1401- lifecycle. register ( label: " test " , start: . async( item. start) , shutdown: . async( item. shutdown) )
1498+ lifecycle. register ( label: " test " , start: . async( item. start) , shutdown: . async( item. shutdown) , shutdownIfNotStarted: false )
1499+
1500+ lifecycle. start { error in
1501+ XCTAssert ( error is TestError , " expected error to match " )
1502+ lifecycle. shutdown ( )
1503+ }
1504+ lifecycle. wait ( )
1505+ XCTAssertFalse ( item. isShutdown, " expected item to be shutdown " )
1506+ #endif
1507+ }
1508+
1509+ func testAsyncAwaitErrorOnStartShutdownRequested( ) throws {
1510+ #if compiler(<5.2)
1511+ return
1512+ #elseif compiler(<5.5)
1513+ throw XCTSkip ( )
1514+ #else
1515+ guard #available( macOS 12 . 0 , * ) else {
1516+ throw XCTSkip ( )
1517+ }
1518+
1519+ class Item {
1520+ var isShutdown : Bool = false
1521+
1522+ func start( ) async throws {
1523+ throw TestError ( )
1524+ }
1525+
1526+ func shutdown( ) async throws {
1527+ self . isShutdown = true // not thread safe but okay for this purpose
1528+ }
1529+ }
1530+
1531+ let lifecycle = ComponentLifecycle ( label: " test " )
1532+
1533+ let item = Item ( )
1534+ lifecycle. register ( label: " test " , start: . async( item. start) , shutdown: . async( item. shutdown) , shutdownIfNotStarted: true )
14021535
14031536 lifecycle. start { error in
14041537 XCTAssert ( error is TestError , " expected error to match " )
0 commit comments