@@ -911,21 +911,21 @@ final class ComponentLifecycleTests: XCTestCase {
911
911
func testShutdownOnlyStarted( ) {
912
912
class Item {
913
913
let label : String
914
- let sempahore : DispatchSemaphore
914
+ let semaphore : DispatchSemaphore
915
915
let failStart : Bool
916
- let exptectedState : State
916
+ let expectedState : State
917
917
var state = State . idle
918
918
919
919
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 ( )
922
922
}
923
923
924
- init ( label: String , failStart: Bool , exptectedState : State , sempahore : DispatchSemaphore ) {
924
+ init ( label: String , failStart: Bool , expectedState : State , semaphore : DispatchSemaphore ) {
925
925
self . label = label
926
926
self . failStart = failStart
927
- self . exptectedState = exptectedState
928
- self . sempahore = sempahore
927
+ self . expectedState = expectedState
928
+ self . semaphore = semaphore
929
929
}
930
930
931
931
func start( ) throws {
@@ -951,11 +951,12 @@ final class ComponentLifecycleTests: XCTestCase {
951
951
}
952
952
953
953
let count = Int . random ( in: 10 ..< 20 )
954
- let sempahore = DispatchSemaphore ( value: count)
954
+ let semaphore = DispatchSemaphore ( value: count)
955
955
let lifecycle = ServiceLifecycle ( configuration: . init( shutdownSignal: nil ) )
956
956
957
957
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)
959
960
lifecycle. register ( label: item. label, start: . sync( item. start) , shutdown: . sync( item. shutdown) )
960
961
}
961
962
@@ -965,25 +966,29 @@ final class ComponentLifecycleTests: XCTestCase {
965
966
}
966
967
lifecycle. wait ( )
967
968
968
- XCTAssertEqual ( . success, sempahore . wait ( timeout: . now( ) + 1 ) )
969
+ XCTAssertEqual ( . success, semaphore . wait ( timeout: . now( ) + 1 ) )
969
970
}
970
971
971
972
func testShutdownWhenStartFailedIfAsked( ) {
972
973
class DestructionSensitive {
973
974
let label : String
974
975
let failStart : Bool
975
- let sempahore : DispatchSemaphore
976
+ let semaphore : DispatchSemaphore
976
977
var state = State . idle
977
978
978
979
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 ( )
981
986
}
982
987
983
- init ( label: String , failStart: Bool = false , sempahore : DispatchSemaphore ) {
988
+ init ( label: String , failStart: Bool = false , semaphore : DispatchSemaphore ) {
984
989
self . label = label
985
990
self . failStart = failStart
986
- self . sempahore = sempahore
991
+ self . semaphore = semaphore
987
992
}
988
993
989
994
func start( ) throws {
@@ -1008,25 +1013,25 @@ final class ComponentLifecycleTests: XCTestCase {
1008
1013
struct InitError : Error { }
1009
1014
}
1010
1015
1011
- let sempahore = DispatchSemaphore ( value: 6 )
1016
+ let semaphore = DispatchSemaphore ( value: 6 )
1012
1017
let lifecycle = ServiceLifecycle ( configuration: . init( shutdownSignal: nil ) )
1013
1018
1014
- let item1 = DestructionSensitive ( label: " 1 " , sempahore : sempahore )
1019
+ let item1 = DestructionSensitive ( label: " 1 " , semaphore : semaphore )
1015
1020
lifecycle. register ( label: item1. label, start: . sync( item1. start) , shutdown: . sync( item1. shutdown) )
1016
1021
1017
- let item2 = DestructionSensitive ( label: " 2 " , sempahore : sempahore )
1022
+ let item2 = DestructionSensitive ( label: " 2 " , semaphore : semaphore )
1018
1023
lifecycle. registerShutdown ( label: item2. label, . sync( item2. shutdown) )
1019
1024
1020
- let item3 = DestructionSensitive ( label: " 3 " , failStart: true , sempahore : sempahore )
1025
+ let item3 = DestructionSensitive ( label: " 3 " , failStart: true , semaphore : semaphore )
1021
1026
lifecycle. register ( label: item3. label, start: . sync( item3. start) , shutdown: . sync( item3. shutdown) )
1022
1027
1023
- let item4 = DestructionSensitive ( label: " 4 " , sempahore : sempahore )
1028
+ let item4 = DestructionSensitive ( label: " 4 " , semaphore : semaphore )
1024
1029
lifecycle. registerShutdown ( label: item4. label, . sync( item4. shutdown) )
1025
1030
1026
- let item5 = DestructionSensitive ( label: " 5 " , sempahore : sempahore )
1031
+ let item5 = DestructionSensitive ( label: " 5 " , semaphore : semaphore )
1027
1032
lifecycle. register ( label: item5. label, start: . none, shutdown: . sync( item5. shutdown) )
1028
1033
1029
- let item6 = DestructionSensitive ( label: " 6 " , sempahore : sempahore )
1034
+ let item6 = DestructionSensitive ( label: " 6 " , semaphore : semaphore )
1030
1035
lifecycle. register ( _LifecycleTask ( label: item6. label, shutdownIfNotStarted: true , start: . sync( item6. start) , shutdown: . sync( item6. shutdown) ) )
1031
1036
1032
1037
lifecycle. start { error in
@@ -1035,7 +1040,99 @@ final class ComponentLifecycleTests: XCTestCase {
1035
1040
}
1036
1041
lifecycle. wait ( )
1037
1042
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
+ }
1039
1136
}
1040
1137
1041
1138
func testStatefulSync( ) {
@@ -1398,7 +1495,43 @@ final class ComponentLifecycleTests: XCTestCase {
1398
1495
let lifecycle = ComponentLifecycle ( label: " test " )
1399
1496
1400
1497
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 )
1402
1535
1403
1536
lifecycle. start { error in
1404
1537
XCTAssert ( error is TestError , " expected error to match " )
0 commit comments