1
1
use anyhow:: { anyhow, Context } ;
2
2
use clap:: { CommandFactory , Parser , Subcommand } ;
3
3
use slog:: { Drain , Level , Logger } ;
4
- use slog_scope:: { error, info, warn } ;
4
+ use slog_scope:: { error, info} ;
5
5
use std:: {
6
- fs,
6
+ fmt , fs,
7
7
path:: { Path , PathBuf } ,
8
+ process:: { ExitCode , Termination } ,
8
9
sync:: Arc ,
9
10
time:: Duration ,
10
11
} ;
12
+ use thiserror:: Error ;
11
13
use tokio:: {
12
14
signal:: unix:: { signal, SignalKind } ,
13
15
sync:: Mutex ,
@@ -17,7 +19,8 @@ use tokio::{
17
19
use mithril_common:: StdResult ;
18
20
use mithril_doc:: GenerateDocCommands ;
19
21
use mithril_end_to_end:: {
20
- Devnet , DevnetBootstrapArgs , MithrilInfrastructure , MithrilInfrastructureConfig , RunOnly , Spec ,
22
+ Devnet , DevnetBootstrapArgs , MithrilInfrastructure , MithrilInfrastructureConfig ,
23
+ RetryableDevnetError , RunOnly , Spec ,
21
24
} ;
22
25
23
26
/// Tests args
@@ -152,8 +155,16 @@ enum EndToEndCommands {
152
155
GenerateDoc ( GenerateDocCommands ) ,
153
156
}
154
157
155
- #[ tokio:: main]
156
- async fn main ( ) -> StdResult < ( ) > {
158
+ fn main ( ) -> AppResult {
159
+ tokio:: runtime:: Builder :: new_multi_thread ( )
160
+ . enable_all ( )
161
+ . build ( )
162
+ . unwrap ( )
163
+ . block_on ( async { main_exec ( ) . await } )
164
+ . into ( )
165
+ }
166
+
167
+ async fn main_exec ( ) -> StdResult < ( ) > {
157
168
let args = Args :: parse ( ) ;
158
169
let _guard = slog_scope:: set_global_logger ( build_logger ( & args) ) ;
159
170
@@ -198,9 +209,69 @@ async fn main() -> StdResult<()> {
198
209
199
210
app_stopper. stop ( ) . await ;
200
211
join_set. shutdown ( ) . await ;
212
+
201
213
res
202
214
}
203
215
216
+ #[ derive( Debug ) ]
217
+ enum AppResult {
218
+ Success ( ) ,
219
+ UnretryableError ( anyhow:: Error ) ,
220
+ RetryableError ( anyhow:: Error ) ,
221
+ Cancelled ( anyhow:: Error ) ,
222
+ }
223
+
224
+ impl AppResult {
225
+ fn exit_code ( & self ) -> ExitCode {
226
+ match self {
227
+ AppResult :: Success ( ) => ExitCode :: SUCCESS ,
228
+ AppResult :: UnretryableError ( _) | AppResult :: Cancelled ( _) => ExitCode :: FAILURE ,
229
+ AppResult :: RetryableError ( _) => ExitCode :: from ( 2 ) ,
230
+ }
231
+ }
232
+ }
233
+
234
+ impl fmt:: Display for AppResult {
235
+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
236
+ match self {
237
+ AppResult :: Success ( ) => write ! ( f, "Success" ) ,
238
+ AppResult :: UnretryableError ( error) => write ! ( f, "Error(Unretryable): {error:?}" ) ,
239
+ AppResult :: RetryableError ( error) => write ! ( f, "Error(Retryable): {error:?}" ) ,
240
+ AppResult :: Cancelled ( error) => write ! ( f, "Cancelled: {error:?}" ) ,
241
+ }
242
+ }
243
+ }
244
+
245
+ impl Termination for AppResult {
246
+ fn report ( self ) -> ExitCode {
247
+ let exit_code = self . exit_code ( ) ;
248
+ println ! ( " " ) ;
249
+ println ! ( "{:-^100}" , "" ) ;
250
+ println ! ( "Mithril End to End test outcome:" ) ;
251
+ println ! ( "{:-^100}" , "" ) ;
252
+ println ! ( "{self}" ) ;
253
+
254
+ exit_code
255
+ }
256
+ }
257
+
258
+ impl From < StdResult < ( ) > > for AppResult {
259
+ fn from ( result : StdResult < ( ) > ) -> Self {
260
+ match result {
261
+ Ok ( ( ) ) => AppResult :: Success ( ) ,
262
+ Err ( error) => {
263
+ if error. is :: < RetryableDevnetError > ( ) {
264
+ AppResult :: RetryableError ( error)
265
+ } else if error. is :: < SignalError > ( ) {
266
+ AppResult :: Cancelled ( error)
267
+ } else {
268
+ AppResult :: UnretryableError ( error)
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+
204
275
struct App {
205
276
devnet : Arc < Mutex < Option < Devnet > > > ,
206
277
infrastructure : Arc < Mutex < Option < MithrilInfrastructure > > > ,
@@ -338,31 +409,73 @@ fn create_workdir_if_not_exist_clean_otherwise(work_dir: &Path) {
338
409
fs:: create_dir ( work_dir) . expect ( "Work dir creation failure" ) ;
339
410
}
340
411
412
+ #[ derive( Error , Debug , PartialEq , Eq ) ]
413
+ #[ error( "Signal received: `{0}`" ) ]
414
+ pub struct SignalError ( pub String ) ;
415
+
341
416
fn with_gracefull_shutdown ( join_set : & mut JoinSet < StdResult < ( ) > > ) {
342
417
join_set. spawn ( async move {
343
418
let mut sigterm = signal ( SignalKind :: terminate ( ) ) . expect ( "Failed to create SIGTERM signal" ) ;
344
- sigterm
345
- . recv ( )
346
- . await
347
- . ok_or ( anyhow ! ( "Failed to receive SIGTERM" ) )
348
- . inspect ( |( ) | warn ! ( "Received SIGTERM" ) )
419
+ sigterm. recv ( ) . await ;
420
+
421
+ Err ( anyhow ! ( SignalError ( "SIGTERM" . to_string( ) ) ) )
349
422
} ) ;
350
423
351
424
join_set. spawn ( async move {
352
425
let mut sigterm = signal ( SignalKind :: interrupt ( ) ) . expect ( "Failed to create SIGINT signal" ) ;
353
- sigterm
354
- . recv ( )
355
- . await
356
- . ok_or ( anyhow ! ( "Failed to receive SIGINT" ) )
357
- . inspect ( |( ) | warn ! ( "Received SIGINT" ) )
426
+ sigterm. recv ( ) . await ;
427
+
428
+ Err ( anyhow ! ( SignalError ( "SIGINT" . to_string( ) ) ) )
358
429
} ) ;
359
430
360
431
join_set. spawn ( async move {
361
432
let mut sigterm = signal ( SignalKind :: quit ( ) ) . expect ( "Failed to create SIGQUIT signal" ) ;
362
- sigterm
363
- . recv ( )
364
- . await
365
- . ok_or ( anyhow ! ( "Failed to receive SIGQUIT" ) )
366
- . inspect ( |( ) | warn ! ( "Received SIGQUIT" ) )
433
+ sigterm. recv ( ) . await ;
434
+
435
+ Err ( anyhow ! ( SignalError ( "SIGQUIT" . to_string( ) ) ) )
367
436
} ) ;
368
437
}
438
+
439
+ #[ cfg( test) ]
440
+ mod tests {
441
+ use super :: * ;
442
+
443
+ #[ test]
444
+ fn app_result_exit_code ( ) {
445
+ let expected_exit_code = ExitCode :: SUCCESS ;
446
+ let exit_code = AppResult :: Success ( ) . exit_code ( ) ;
447
+ assert_eq ! ( expected_exit_code, exit_code) ;
448
+
449
+ let expected_exit_code = ExitCode :: FAILURE ;
450
+ let exit_code = AppResult :: UnretryableError ( anyhow:: anyhow!( "an error" ) ) . exit_code ( ) ;
451
+ assert_eq ! ( expected_exit_code, exit_code) ;
452
+
453
+ let expected_exit_code = ExitCode :: from ( 2 ) ;
454
+ let exit_code = AppResult :: RetryableError ( anyhow:: anyhow!( "an error" ) ) . exit_code ( ) ;
455
+ assert_eq ! ( expected_exit_code, exit_code) ;
456
+
457
+ let expected_exit_code = ExitCode :: FAILURE ;
458
+ let exit_code = AppResult :: Cancelled ( anyhow:: anyhow!( "an error" ) ) . exit_code ( ) ;
459
+ assert_eq ! ( expected_exit_code, exit_code) ;
460
+ }
461
+
462
+ #[ test]
463
+ fn app_result_conversion ( ) {
464
+ assert ! ( matches!( AppResult :: from( Ok ( ( ) ) ) , AppResult :: Success ( ) ) ) ;
465
+
466
+ assert ! ( matches!(
467
+ AppResult :: from( Err ( anyhow!( RetryableDevnetError ( "an error" . to_string( ) ) ) ) ) ,
468
+ AppResult :: RetryableError ( _)
469
+ ) ) ;
470
+
471
+ assert ! ( matches!(
472
+ AppResult :: from( Err ( anyhow!( "an error" ) ) ) ,
473
+ AppResult :: UnretryableError ( _)
474
+ ) ) ;
475
+
476
+ assert ! ( matches!(
477
+ AppResult :: from( Err ( anyhow!( SignalError ( "an error" . to_string( ) ) ) ) ) ,
478
+ AppResult :: Cancelled ( _)
479
+ ) ) ;
480
+ }
481
+ }
0 commit comments