1717
1818use anyhow:: { Context , Result } ;
1919use clap:: Parser ;
20- pub use fuzztest_options:: { ExecutionMode , FuzzFor , FuzzOptions , FuzzTestOptions } ;
20+ pub use fuzztest_options:: {
21+ ExecutionMode , FuzzFor , FuzzOptions , FuzzTestOptions , ReplayCrashOptions ,
22+ } ;
2123use std:: env;
2224use std:: ffi:: OsString ;
2325use std:: path:: { Path , PathBuf } ;
@@ -54,7 +56,15 @@ impl CargoFuzzTestOptions {
5456 fn check_centipede_binary_path_is_set ( & self ) -> Result < ( ) > {
5557 anyhow:: ensure!(
5658 self . centipede_binary_path. is_some( ) ,
57- "fuzzing mode requires `--centipede-binary-path` to be specified"
59+ "`--centipede-binary-path` needs to be specified"
60+ ) ;
61+ Ok ( ( ) )
62+ }
63+
64+ fn check_corpus_db_is_set ( & self ) -> Result < ( ) > {
65+ anyhow:: ensure!(
66+ self . fuzztest_options. corpus_db. is_some( ) ,
67+ "`--corpus-db` needs to be specified"
5868 ) ;
5969 Ok ( ( ) )
6070 }
@@ -72,14 +82,18 @@ impl CargoFuzzTestOptions {
7282 self . check_centipede_binary_path_is_set ( ) ?;
7383 mode
7484 }
85+ ExecutionMode :: ReplayCrash ( _) => {
86+ self . check_centipede_binary_path_is_set ( ) ?;
87+ self . check_corpus_db_is_set ( ) ?;
88+ mode
89+ }
7590 ExecutionMode :: SmokeTest => {
7691 if self . test_path . is_some ( ) {
7792 self . check_centipede_binary_path_is_set ( ) ?;
78- return Ok ( ExecutionMode :: Fuzz ( FuzzOptions {
79- fuzz_for : FuzzFor :: Indefinitely ,
80- // TODO(the-shank): support parallel jobs
81- jobs : None ,
82- } ) ) ;
93+ ExecutionMode :: Fuzz ( FuzzOptions {
94+ fuzz_for : self . fuzztest_options . fuzz_for . unwrap_or ( FuzzFor :: Indefinitely ) ,
95+ jobs : self . fuzztest_options . jobs ,
96+ } )
8397 } else {
8498 mode
8599 }
@@ -227,7 +241,7 @@ impl FuzztestRunner {
227241 }
228242
229243 ExecutionMode :: Fuzz ( fuzz_options) => {
230- let FuzzOptions { fuzz_for, jobs : _ } = fuzz_options;
244+ let FuzzOptions { fuzz_for, jobs } = fuzz_options;
231245 match fuzz_for {
232246 FuzzFor :: Indefinitely => {
233247 cmd. env ( "FUZZTEST_FUZZ_FOR" , "inf" ) ;
@@ -236,7 +250,13 @@ impl FuzztestRunner {
236250 cmd. env ( "FUZZTEST_FUZZ_FOR" , duration. to_string ( ) ) ;
237251 }
238252 }
239- // TODO(the-shank): support parallel jobs
253+ if let Some ( jobs) = jobs {
254+ cmd. env ( "FUZZTEST_JOBS" , jobs. to_string ( ) ) ;
255+ }
256+ }
257+
258+ ExecutionMode :: ReplayCrash ( replay_options) => {
259+ cmd. env ( "FUZZTEST_REPLAY_ID" , replay_options. replay_id ) ;
240260 }
241261
242262 ExecutionMode :: SmokeTest => {
@@ -247,13 +267,26 @@ impl FuzztestRunner {
247267 }
248268 }
249269
270+ if let Some ( corpus_db) = & self . options . fuzztest_options . corpus_db {
271+ cmd. env ( "FUZZTEST_CORPUS_DB" , corpus_db) ;
272+ }
273+
274+ if let Some ( workdir_root) = & self . options . fuzztest_options . workdir_root {
275+ cmd. env ( "FUZZTEST_WORKDIR_ROOT" , workdir_root) ;
276+ }
277+
250278 // If `--centipede-binary-path` was passed to `cargo-fuzztest`, forward it to the
251279 // child test executable via `FUZZTEST_CENTIPEDE_BINARY_PATH` environment variable so the
252280 // fuzzer runtime can locate and run Centipede during continuous fuzzing.
253281 if let Some ( centipede_binary_path) = & self . options . centipede_binary_path {
254282 cmd. env ( "FUZZTEST_CENTIPEDE_BINARY_PATH" , centipede_binary_path) ;
255283 }
256284
285+ if self . options . fuzztest_options . print_subprocess_log {
286+ cmd. env ( "FUZZTEST_PRINT_SUBPROCESS_LOG" , "true" ) ;
287+ cmd. arg ( "--nocapture" ) ;
288+ }
289+
257290 Ok ( cmd)
258291 }
259292
@@ -377,4 +410,88 @@ mod tests {
377410 ExecutionMode :: SmokeTest
378411 ) ;
379412 }
413+
414+ #[ gtest]
415+ fn test_cli_option_parsing_replay_id_success ( ) {
416+ let parsed = CargoFuzzTestOptions :: try_parse_from ( [
417+ "cargo-fuzztest" ,
418+ "--replay-id" ,
419+ "crash_12345" ,
420+ "--corpus-db" ,
421+ "/tmp/corpus_db" ,
422+ "--centipede-binary-path" ,
423+ "/custom/centipede" ,
424+ ] )
425+ . expect ( "valid replay options should parse successfully" ) ;
426+
427+ assert_eq ! ( parsed. fuzztest_options. replay_id. as_deref( ) , Some ( "crash_12345" ) ) ;
428+ assert_eq ! ( parsed. fuzztest_options. corpus_db. as_deref( ) , Some ( "/tmp/corpus_db" ) ) ;
429+
430+ let mode = parsed. execution_mode ( ) . expect ( "valid execution mode" ) ;
431+ assert_eq ! (
432+ mode,
433+ ExecutionMode :: ReplayCrash ( ReplayCrashOptions { replay_id: "crash_12345" . to_string( ) } )
434+ ) ;
435+ }
436+
437+ #[ gtest]
438+ fn test_execution_mode_replay_id_missing_corpus_db_errors ( ) {
439+ let options = CargoFuzzTestOptions {
440+ fuzztest_options : FuzzTestOptions {
441+ replay_id : Some ( "crash_12345" . to_string ( ) ) ,
442+ ..Default :: default ( )
443+ } ,
444+ centipede_binary_path : Some ( "/custom/centipede" . to_string ( ) ) ,
445+ ..Default :: default ( )
446+ } ;
447+ let err = options. execution_mode ( ) . expect_err ( "missing corpus-db should cause error" ) ;
448+ assert ! ( err. to_string( ) . contains( "`--corpus-db` needs to be specified" ) ) ;
449+ }
450+
451+ #[ gtest]
452+ fn test_execution_mode_replay_id_missing_centipede_binary_path_errors ( ) {
453+ let options = CargoFuzzTestOptions {
454+ fuzztest_options : FuzzTestOptions {
455+ replay_id : Some ( "crash_12345" . to_string ( ) ) ,
456+ corpus_db : Some ( "/tmp/corpus_db" . to_string ( ) ) ,
457+ ..Default :: default ( )
458+ } ,
459+ ..Default :: default ( )
460+ } ;
461+ let err =
462+ options. execution_mode ( ) . expect_err ( "missing centipede-binary-path should cause error" ) ;
463+ assert ! ( err. to_string( ) . contains( "`--centipede-binary-path` needs to be specified" ) ) ;
464+ }
465+
466+ #[ gtest]
467+ fn test_build_run_command_replay_crash ( ) {
468+ let options = CargoFuzzTestOptions {
469+ fuzztest_options : FuzzTestOptions {
470+ replay_id : Some ( "crash_12345" . to_string ( ) ) ,
471+ corpus_db : Some ( "/tmp/corpus_db" . to_string ( ) ) ,
472+ ..Default :: default ( )
473+ } ,
474+ centipede_binary_path : Some ( "/custom/centipede" . to_string ( ) ) ,
475+ ..Default :: default ( )
476+ } ;
477+ let runner = FuzztestRunner :: new ( "x86_64-unknown-linux-gnu" . to_string ( ) , options) ;
478+ let cmd =
479+ runner. build_run_command ( Path :: new ( "/tmp/test_bin" ) ) . expect ( "should build run command" ) ;
480+
481+ let envs: Vec < ( String , Option < String > ) > = cmd
482+ . get_envs ( )
483+ . map ( |( k, v) | {
484+ ( k. to_string_lossy ( ) . to_string ( ) , v. map ( |s| s. to_string_lossy ( ) . to_string ( ) ) )
485+ } )
486+ . collect ( ) ;
487+
488+ assert ! ( envs. contains( & ( "FUZZTEST_REPLAY_ID" . to_string( ) , Some ( "crash_12345" . to_string( ) ) ) ) ) ;
489+ assert ! (
490+ envs. contains( & ( "FUZZTEST_CORPUS_DB" . to_string( ) , Some ( "/tmp/corpus_db" . to_string( ) ) ) )
491+ ) ;
492+ assert ! ( envs. contains( & (
493+ "FUZZTEST_CENTIPEDE_BINARY_PATH" . to_string( ) ,
494+ Some ( "/custom/centipede" . to_string( ) )
495+ ) ) ) ;
496+ }
380497}
0 commit comments