@@ -1049,4 +1049,256 @@ mod test {
1049
1049
}
1050
1050
}
1051
1051
}
1052
+
1053
+ /// An operation that can be performed during a [`QueueDequeueTest`].
1054
+ #[ derive( Clone , Copy , Debug ) ]
1055
+ enum QueueOp {
1056
+ Enqueue ( RequestKind ) ,
1057
+ Dequeue ,
1058
+ }
1059
+
1060
+ fn queue_op_strategy ( ) -> impl Strategy < Value = QueueOp > {
1061
+ prop_oneof ! [
1062
+ request_strategy( ) . prop_map( QueueOp :: Enqueue ) ,
1063
+ Just ( QueueOp :: Dequeue )
1064
+ ]
1065
+ }
1066
+
1067
+ /// A helper that queues and dequeues requests in a proptest-generated
1068
+ /// order and that sends fake completion notifications back to the request
1069
+ /// queue.
1070
+ struct QueueDequeueTest {
1071
+ /// The external request queue under test.
1072
+ queue : ExternalRequestQueue ,
1073
+
1074
+ /// The set of state change requests that the helper expects to see from
1075
+ /// the external queue.
1076
+ expected_state : VecDeque < RequestKind > ,
1077
+
1078
+ /// The set of component change requests that the helper expects to see
1079
+ /// from the external queue.
1080
+ expected_component : VecDeque < RequestKind > ,
1081
+
1082
+ /// True if the helper has queued a request to start its fake VM.
1083
+ start_requested : bool ,
1084
+
1085
+ /// True if the helper has successfully started its fake VM.
1086
+ started : bool ,
1087
+
1088
+ /// True if the helper has queued a request to stop its fake VM.
1089
+ stop_requested : bool ,
1090
+
1091
+ /// True if the helper has an outstanding request to reboot its fake VM.
1092
+ reboot_requested : bool ,
1093
+
1094
+ /// True if the helper has an outstanding request to migrate its fake
1095
+ /// VM.
1096
+ migrate_out_requested : bool ,
1097
+
1098
+ /// True if the fake VM is halted (for any reason).
1099
+ halted : bool ,
1100
+ }
1101
+
1102
+ impl QueueDequeueTest {
1103
+ fn new ( ) -> Self {
1104
+ Self {
1105
+ queue : ExternalRequestQueue :: new (
1106
+ test_logger ( ) ,
1107
+ InstanceAutoStart :: No ,
1108
+ ) ,
1109
+ expected_state : Default :: default ( ) ,
1110
+ expected_component : Default :: default ( ) ,
1111
+ start_requested : false ,
1112
+ started : false ,
1113
+ stop_requested : false ,
1114
+ reboot_requested : false ,
1115
+ migrate_out_requested : false ,
1116
+ halted : false ,
1117
+ }
1118
+ }
1119
+
1120
+ fn run ( & mut self , ops : Vec < QueueOp > ) {
1121
+ for op in ops {
1122
+ match op {
1123
+ QueueOp :: Enqueue ( request) => self . queue_request ( request) ,
1124
+ QueueOp :: Dequeue => {
1125
+ self . dequeue_request ( ) ;
1126
+ if self . halted {
1127
+ return ;
1128
+ }
1129
+ }
1130
+ }
1131
+ }
1132
+ }
1133
+
1134
+ /// Submits the supplied `request` to the external request queue,
1135
+ /// determines the expected result of that submission based on the
1136
+ /// helper's current flags, and asserts that the result matches the
1137
+ /// helper's expectation. If the helper expects the request to be
1138
+ /// queued, it pushes an entry to its internal expected-change queues.
1139
+ fn queue_request ( & mut self , request : RequestKind ) {
1140
+ let result = self . queue . try_queue ( request. into ( ) ) ;
1141
+ match request {
1142
+ RequestKind :: Start { .. } => {
1143
+ if self . halted || self . stop_requested {
1144
+ assert ! ( result. is_err( ) ) ;
1145
+ return ;
1146
+ }
1147
+
1148
+ assert ! ( result. is_ok( ) ) ;
1149
+ if !self . start_requested {
1150
+ self . start_requested = true ;
1151
+ self . expected_state . push_back ( request) ;
1152
+ }
1153
+ }
1154
+ RequestKind :: Stop => {
1155
+ if self . halted || self . stop_requested {
1156
+ assert ! ( result. is_ok( ) ) ;
1157
+ return ;
1158
+ }
1159
+
1160
+ if self . migrate_out_requested {
1161
+ assert ! ( result. is_err( ) ) ;
1162
+ return ;
1163
+ }
1164
+
1165
+ self . stop_requested = true ;
1166
+ self . expected_state . push_back ( request) ;
1167
+ }
1168
+ RequestKind :: Reboot => {
1169
+ if !self . started
1170
+ || self . halted
1171
+ || self . stop_requested
1172
+ || self . migrate_out_requested
1173
+ {
1174
+ assert ! ( result. is_err( ) ) ;
1175
+ return ;
1176
+ }
1177
+
1178
+ assert ! ( result. is_ok( ) ) ;
1179
+ if !self . reboot_requested {
1180
+ self . reboot_requested = true ;
1181
+ self . expected_state . push_back ( request) ;
1182
+ }
1183
+ }
1184
+ RequestKind :: Migrate { .. } => {
1185
+ if ( !self . started && !self . start_requested )
1186
+ || self . halted
1187
+ || self . stop_requested
1188
+ || self . migrate_out_requested
1189
+ {
1190
+ assert ! ( result. is_err( ) ) ;
1191
+ return ;
1192
+ }
1193
+
1194
+ assert ! ( result. is_ok( ) ) ;
1195
+ self . expected_state . push_back ( request) ;
1196
+ self . migrate_out_requested = true ;
1197
+ }
1198
+ RequestKind :: ReconfigureCrucible => {
1199
+ if self . halted {
1200
+ assert ! ( result. is_err( ) ) ;
1201
+ return ;
1202
+ }
1203
+
1204
+ assert ! ( result. is_ok( ) ) ;
1205
+ self . expected_component . push_back ( request) ;
1206
+ }
1207
+ }
1208
+ }
1209
+
1210
+ /// Pops a request from the helper's external queue and verifies that it
1211
+ /// matches the first request on the helper's expected-change queue. If
1212
+ /// the requests do match, sends a completion notification to the
1213
+ /// external queue.
1214
+ fn dequeue_request ( & mut self ) {
1215
+ let ( dequeued, expected) = match (
1216
+ self . queue . pop_front ( ) ,
1217
+ self . expected_state
1218
+ . pop_front ( )
1219
+ . or_else ( || self . expected_component . pop_front ( ) ) ,
1220
+ ) {
1221
+ ( None , None ) => return ,
1222
+ ( Some ( d) , None ) => {
1223
+ panic ! ( "dequeued request {d:?} but expected nothing" )
1224
+ }
1225
+ ( None , Some ( e) ) => {
1226
+ panic ! ( "expected request {e:?} but dequeued nothing" )
1227
+ }
1228
+ ( Some ( d) , Some ( e) ) => ( d, e) ,
1229
+ } ;
1230
+
1231
+ match ( dequeued, expected) {
1232
+ (
1233
+ ExternalRequest :: State ( StateChangeRequest :: Start ) ,
1234
+ RequestKind :: Start { will_succeed } ,
1235
+ ) => {
1236
+ self . queue . notify_request_completed (
1237
+ CompletedRequest :: Start { succeeded : will_succeed } ,
1238
+ ) ;
1239
+ if will_succeed {
1240
+ self . started = true ;
1241
+ } else {
1242
+ self . halted = true ;
1243
+ }
1244
+ }
1245
+ (
1246
+ ExternalRequest :: State ( StateChangeRequest :: Stop ) ,
1247
+ RequestKind :: Stop ,
1248
+ ) => {
1249
+ self . queue . notify_request_completed ( CompletedRequest :: Stop ) ;
1250
+ self . halted = true ;
1251
+ }
1252
+ (
1253
+ ExternalRequest :: State ( StateChangeRequest :: Reboot ) ,
1254
+ RequestKind :: Reboot ,
1255
+ ) => {
1256
+ self . queue
1257
+ . notify_request_completed ( CompletedRequest :: Reboot ) ;
1258
+ self . reboot_requested = false ;
1259
+ }
1260
+ (
1261
+ ExternalRequest :: State (
1262
+ StateChangeRequest :: MigrateAsSource { .. } ,
1263
+ ) ,
1264
+ RequestKind :: Migrate { will_succeed } ,
1265
+ ) => {
1266
+ self . queue . notify_request_completed (
1267
+ CompletedRequest :: MigrationOut {
1268
+ succeeded : will_succeed,
1269
+ } ,
1270
+ ) ;
1271
+ self . migrate_out_requested = false ;
1272
+ if will_succeed {
1273
+ self . halted = true ;
1274
+ }
1275
+ }
1276
+ (
1277
+ ExternalRequest :: Component (
1278
+ ComponentChangeRequest :: ReconfigureCrucibleVolume {
1279
+ ..
1280
+ } ,
1281
+ ) ,
1282
+ RequestKind :: ReconfigureCrucible ,
1283
+ ) => { }
1284
+ ( d, e) => panic ! (
1285
+ "dequeued request {d:?} but expected to dequeue {e:?}\n \
1286
+ remaining queue: {:#?}\n \
1287
+ remaining expected (state): {:#?}\n \
1288
+ remaining expected (components): {:#?}",
1289
+ self . queue, self . expected_state, self . expected_component
1290
+ ) ,
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ proptest ! {
1296
+ #[ test]
1297
+ fn request_queue_dequeue(
1298
+ ops in prop:: collection:: vec( queue_op_strategy( ) , 0 ..100 )
1299
+ ) {
1300
+ let mut test = QueueDequeueTest :: new( ) ;
1301
+ test. run( ops) ;
1302
+ }
1303
+ }
1052
1304
}
0 commit comments