diff --git a/rust/cargo_fuzztest/src/lib.rs b/rust/cargo_fuzztest/src/lib.rs index 353e26fd6..bdfa67fc7 100644 --- a/rust/cargo_fuzztest/src/lib.rs +++ b/rust/cargo_fuzztest/src/lib.rs @@ -100,6 +100,7 @@ impl CargoFuzzTestOptions { .fuzz_for .unwrap_or(RunDuration::Indefinitely), jobs: self.fuzztest_options.jobs, + continue_after_crash: self.fuzztest_options.continue_after_crash, }) } else { mode @@ -248,11 +249,14 @@ impl FuzztestRunner { } ExecutionMode::Fuzz(fuzz_options) => { - let FuzzOptions { fuzz_for, jobs } = fuzz_options; + let FuzzOptions { fuzz_for, jobs, continue_after_crash } = fuzz_options; cmd.env("FUZZTEST_FUZZ_FOR", fuzz_for.to_string()); if let Some(jobs) = jobs { cmd.env("FUZZTEST_JOBS", jobs.to_string()); } + if continue_after_crash { + cmd.env("FUZZTEST_CONTINUE_AFTER_CRASH", "true"); + } } ExecutionMode::ReplayCrash(replay_options) => { @@ -273,6 +277,9 @@ impl FuzztestRunner { TimeBudgetType::Total => "total", }; cmd.env("FUZZTEST_TIME_BUDGET_TYPE", time_budget_str); + if replay_corpus_options.continue_after_crash { + cmd.env("FUZZTEST_CONTINUE_AFTER_CRASH", "true"); + } } ExecutionMode::ListCrashIds(list_crash_ids_options) => { @@ -609,6 +616,7 @@ mod tests { replay_corpus_for: "10s".parse().expect("valid duration string"), time_budget_type: TimeBudgetType::PerTest, jobs: None, + continue_after_crash: false, }) ); } @@ -637,6 +645,7 @@ mod tests { replay_corpus_for: RunDuration::Indefinitely, time_budget_type: TimeBudgetType::PerTest, jobs: None, + continue_after_crash: false, }) ); } @@ -662,6 +671,7 @@ mod tests { replay_corpus_for: RunDuration::Indefinitely, time_budget_type: TimeBudgetType::PerTest, jobs: None, + continue_after_crash: false, }) ); } @@ -691,6 +701,7 @@ mod tests { replay_corpus_for: "10s".parse().unwrap(), time_budget_type: TimeBudgetType::Total, jobs: None, + continue_after_crash: false, }) ); } @@ -879,4 +890,86 @@ mod tests { Some("/custom/centipede".to_string()) ))); } + + #[gtest] + fn test_cli_option_parsing_continue_after_crash_flag() { + let parsed = CargoFuzzTestOptions::try_parse_from([ + "cargo-fuzztest", + "--fuzz-for", + "5s", + "--continue-after-crash", + "--centipede-binary-path", + "/custom/centipede", + ]) + .unwrap(); + + assert!(parsed.fuzztest_options.continue_after_crash); + let mode = parsed.execution_mode().unwrap(); + let expected_duration = "5s".parse().unwrap(); + assert_eq!( + mode, + ExecutionMode::Fuzz(FuzzOptions { + fuzz_for: RunDuration::Fixed(expected_duration), + jobs: None, + continue_after_crash: true, + }) + ); + } + + #[gtest] + fn test_build_run_command_fuzz_with_continue_after_crash() { + let options = CargoFuzzTestOptions { + fuzztest_options: FuzzTestOptions { + fuzz_for: Some("5s".parse().unwrap()), + continue_after_crash: true, + ..Default::default() + }, + centipede_binary_path: Some("/custom/centipede".to_string()), + ..Default::default() + }; + let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let cmd = + runner.build_run_command(Path::new("/tmp/test_bin")).expect("should build run command"); + + let envs: Vec<(String, Option)> = cmd + .get_envs() + .map(|(k, v)| { + (k.to_string_lossy().to_string(), v.map(|s| s.to_string_lossy().to_string())) + }) + .collect(); + + assert!( + envs.contains(&("FUZZTEST_CONTINUE_AFTER_CRASH".to_string(), Some("true".to_string()))) + ); + assert!(envs.contains(&("FUZZTEST_FUZZ_FOR".to_string(), Some("5s".to_string())))); + } + + #[gtest] + fn test_build_run_command_replay_corpus_with_continue_after_crash() { + let options = CargoFuzzTestOptions { + fuzztest_options: FuzzTestOptions { + replay_corpus_for: Some("10s".parse().expect("valid duration")), + corpus_db: Some("/tmp/corpus_db".into()), + continue_after_crash: true, + ..Default::default() + }, + centipede_binary_path: Some("/custom/centipede".to_string()), + ..Default::default() + }; + let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let cmd = + runner.build_run_command(Path::new("/tmp/test_bin")).expect("should build run command"); + + let envs: Vec<(String, Option)> = cmd + .get_envs() + .map(|(k, v)| { + (k.to_string_lossy().to_string(), v.map(|s| s.to_string_lossy().to_string())) + }) + .collect(); + + assert!( + envs.contains(&("FUZZTEST_CONTINUE_AFTER_CRASH".to_string(), Some("true".to_string()))) + ); + assert!(envs.contains(&("FUZZTEST_REPLAY_CORPUS_FOR".to_string(), Some("10s".to_string())))); + } } diff --git a/rust/cargo_fuzztest/tests/e2e_cli_test.rs b/rust/cargo_fuzztest/tests/e2e_cli_test.rs index 04ed647ab..b5a871739 100644 --- a/rust/cargo_fuzztest/tests/e2e_cli_test.rs +++ b/rust/cargo_fuzztest/tests/e2e_cli_test.rs @@ -165,6 +165,7 @@ fn test_cargo_fuzztest_e2e_replay_by_id() { let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); cmd.arg(test_target) .arg("--fuzz-for=5s") + .arg("--continue-after-crash") .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") .arg("--centipede-binary-path") .arg(¢ipede_bin) @@ -248,6 +249,7 @@ fn test_cargo_fuzztest_e2e_replay_all_crashes() { let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); cmd.arg(test_target) .arg("--fuzz-for=5s") + .arg("--continue-after-crash") .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") .arg("--centipede-binary-path") .arg(¢ipede_bin) @@ -412,6 +414,7 @@ fn test_cargo_fuzztest_e2e_list_crash_ids() { let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); cmd.arg(test_target) .arg("--fuzz-for=5s") + .arg("--continue-after-crash") .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") .arg("--centipede-binary-path") .arg(¢ipede_bin) @@ -448,3 +451,29 @@ fn test_cargo_fuzztest_e2e_list_crash_ids() { // good check here? expect_true!(!crash_ids.is_empty()); } + +#[gtest] +fn test_cargo_fuzztest_e2e_continue_after_crash() { + let sample_crate_path = get_sample_crate_path("another_sample_fuzz_crate"); + let test_target = "__fuzztest_mod__crashing_fuzztest_target::crashing_fuzztest_target"; + + let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); + let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH") + .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be set for the test"); + + let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); + cmd.arg(test_target) + .arg("--fuzz-for=3s") + .arg("--continue-after-crash") + .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") + .arg("--centipede-binary-path") + .arg(¢ipede_bin) + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true"); + + let output = cmd.output().expect("Failed to run cargo-fuzztest with continue-after-crash"); + let stderr_str = String::from_utf8_lossy(&output.stderr); + + expect_true!(output.status.success()); + expect_true!(stderr_str.contains("Property function ran but crashed.")); + expect_true!(stderr_str.contains("Crashing bug found!")); +} diff --git a/rust/cargo_fuzztest/tests/runner_test.rs b/rust/cargo_fuzztest/tests/runner_test.rs index a7c81c9c5..2a224519c 100644 --- a/rust/cargo_fuzztest/tests/runner_test.rs +++ b/rust/cargo_fuzztest/tests/runner_test.rs @@ -33,7 +33,7 @@ fn test_runner_build_run_command_with_target() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let args: Vec = cmd.get_args().map(|s| s.to_string_lossy().to_string()).collect(); @@ -52,7 +52,7 @@ fn test_runner_build_run_command_with_duration() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -72,7 +72,7 @@ fn test_runner_build_run_command_with_indefinitely() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -89,7 +89,7 @@ fn test_runner_build_run_command_with_centipede_binary_path() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -115,7 +115,7 @@ fn test_runner_build_run_command_with_jobs() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -131,7 +131,7 @@ fn test_runner_build_run_command_with_jobs_only() { let binary_path = get_sample_test_bin_path("sample_fuzz_crate"); let fuzztest_options = FuzzTestOptions { jobs: Some(4), ..Default::default() }; let options = CargoFuzzTestOptions { fuzztest_options, ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -155,7 +155,7 @@ fn test_runner_build_run_command_with_replay_id() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -217,7 +217,7 @@ fn test_runner_build_run_command_with_replay_findings() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -263,7 +263,7 @@ fn test_runner_build_run_command_with_replay_corpus() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -300,7 +300,7 @@ fn test_runner_build_run_command_with_replay_corpus_indefinitely() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd @@ -367,7 +367,7 @@ fn test_runner_build_run_command_with_list_crash_ids() { centipede_binary_path: Some("/custom/path/to/centipede".to_string()), ..Default::default() }; - let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options); + let runner = FuzztestRunner::new("sample-host-triple".to_string(), options); let cmd = runner.build_run_command(&binary_path).expect("valid run command"); let envs: Vec<(String, Option)> = cmd diff --git a/rust/e2e_tests/replay_test.rs b/rust/e2e_tests/replay_test.rs index d14d7722c..80c37ed89 100644 --- a/rust/e2e_tests/replay_test.rs +++ b/rust/e2e_tests/replay_test.rs @@ -48,6 +48,7 @@ fn replay_by_id_reproduces_panic(fixture: &EnvVars) { .arg(test_name) .arg("--exact") .env("FUZZTEST_FUZZ_FOR", "15s") + .env("FUZZTEST_CONTINUE_AFTER_CRASH", "true") .env("FUZZTEST_CORPUS_DB", &db_dir) .env("FUZZTEST_WORKDIR_ROOT", &workdir_root_dir) .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) @@ -127,6 +128,7 @@ fn replay_all_reproduces_all_failures(fixture: &EnvVars) { .arg(test_name) .arg("--exact") .env("FUZZTEST_FUZZ_FOR", "15s") + .env("FUZZTEST_CONTINUE_AFTER_CRASH", "true") .env("FUZZTEST_CORPUS_DB", &db_dir) .env("FUZZTEST_WORKDIR_ROOT", &workdir_root_dir) .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) diff --git a/rust/e2e_tests/standalone_mode_test.rs b/rust/e2e_tests/standalone_mode_test.rs index 53adb4818..f328388a0 100644 --- a/rust/e2e_tests/standalone_mode_test.rs +++ b/rust/e2e_tests/standalone_mode_test.rs @@ -95,12 +95,39 @@ fn standalone_mode_handles_worker_crash(fixture: &EnvVars) { let output = process.wait_with_output().expect("Should terminate"); let stderr = String::from_utf8_lossy(&output.stderr); + // By default continue_after_crash is false, so finding a crash causes test failure. + expect_false!(output.status.success()); expect_that!(stderr, matchers::contains_substring("Property function ran but crashed.")); expect_that!(stderr, matchers::contains_regex("Signature[ \t]*: Unwinding panic")); // Centipede prefixes logs from the crashing worker with "CRASH LOG: ". expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!")); } +#[gtest] +fn standalone_mode_continues_after_crash_when_enabled(fixture: &EnvVars) { + let test_name = "__fuzztest_mod__find_bug_fuzz_test::find_bug_fuzz_test"; + + let process = Command::new(&fixture.target_binary_path) + .arg(test_name) + .env("FUZZTEST_FUZZ_FOR", "15s") + .env("FUZZTEST_CONTINUE_AFTER_CRASH", "true") + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true") + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) + .env("RUST_TEST_NOCAPTURE", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output = process.wait_with_output().expect("Should terminate"); + let stderr = String::from_utf8_lossy(&output.stderr); + + // With continue_after_crash=true, the fuzz test runs for the full time limit and exits cleanly. + expect_true!(output.status.success()); + expect_that!(stderr, matchers::contains_substring("Property function ran but crashed.")); + expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!")); +} + #[gtest] fn standalone_mode_spawns_parallel_jobs(fixture: &EnvVars) { let target_binary_path = diff --git a/rust/options/src/lib.rs b/rust/options/src/lib.rs index 5abe89c4d..6f6eabc8d 100644 --- a/rust/options/src/lib.rs +++ b/rust/options/src/lib.rs @@ -122,6 +122,19 @@ pub struct FuzzTestOptions { /// regression, and crashing inputs for each test binary and fuzz test. #[arg(env = "FUZZTEST_CORPUS_DB", long, value_parser = parse_corpus_db_path)] pub corpus_db: Option, + + /// Controls the fuzzing and corpus replaying behavior when a crashing input is found. + /// + /// If set to false (default), the test execution stops upon finding the first crashing input, + /// and the test fails. If set to true, the execution logs any crash inputs found and continues + /// until reaching the time limit or manually stopped. + /// + /// Note that this does not affect crash replaying: + /// - Replaying a single crash (`--replay-id`) always stops immediately when a crash occurs. + /// - Replaying all crashes (`--replay-findings`) always continues replaying all remaining + /// crashing inputs regardless of this flag. + #[arg(env = "FUZZTEST_CONTINUE_AFTER_CRASH", long)] + pub continue_after_crash: bool, } /// Strongly-typed domain execution mode for test runs. @@ -172,6 +185,7 @@ impl ExecutionMode { replay_corpus_for, time_budget_type: options.time_budget_type, jobs: options.jobs, + continue_after_crash: options.continue_after_crash, }); } @@ -185,7 +199,11 @@ impl ExecutionMode { // Continuous fuzzing mode is selected if an explicit duration/budget (`fuzz_for`) is specified. if let Some(fuzz_for) = &options.fuzz_for { - return ExecutionMode::Fuzz(FuzzOptions { fuzz_for: *fuzz_for, jobs: options.jobs }); + return ExecutionMode::Fuzz(FuzzOptions { + fuzz_for: *fuzz_for, + jobs: options.jobs, + continue_after_crash: options.continue_after_crash, + }); } ExecutionMode::SmokeTest @@ -200,6 +218,8 @@ pub struct FuzzOptions { /// If `jobs` is `None`, we won't specify the number of jobs while invoking Centipede and it /// will use its own default value. pub jobs: Option, + + pub continue_after_crash: bool, } /// The duration or limit for fuzzing or replaying corpus. @@ -251,6 +271,7 @@ pub struct ReplayCorpusOptions { /// If `jobs` is `None`, we won't specify the number of jobs while invoking Centipede and it /// will use its own default value. pub jobs: Option, + pub continue_after_crash: bool, } /// Mode-specific options for listing crash IDs from the database. @@ -376,6 +397,7 @@ mod tests { eq(&ExecutionMode::Fuzz(FuzzOptions { fuzz_for: RunDuration::Fixed(expected_duration), jobs: Some(4), + continue_after_crash: false, })) ); @@ -405,6 +427,7 @@ mod tests { replay_corpus_for: expected_duration, time_budget_type: TimeBudgetType::PerTest, jobs: Some(4), + continue_after_crash: false, })) ); @@ -416,6 +439,63 @@ mod tests { } } + #[gtest] + fn test_continue_after_crash_with_fuzz_for_parsing_env() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_FUZZ_FOR", "5s"); + std::env::set_var("FUZZTEST_CONTINUE_AFTER_CRASH", "true"); + } + + let options = FuzzTestOptions::parse_from(std::iter::empty::()); + expect_true!(options.continue_after_crash); + let expected_duration = "5s".parse().expect("valid duration"); + expect_that!( + ExecutionMode::from_fuzztest_options(&options), + eq(&ExecutionMode::Fuzz(FuzzOptions { + fuzz_for: RunDuration::Fixed(expected_duration), + jobs: None, + continue_after_crash: true, + })) + ); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_FUZZ_FOR"); + std::env::remove_var("FUZZTEST_CONTINUE_AFTER_CRASH"); + } + } + + #[gtest] + fn test_continue_after_crash_with_replay_corpus_parsing_env() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_REPLAY_CORPUS_FOR", "10s"); + std::env::set_var("FUZZTEST_CORPUS_DB", "/tmp/corpus_db"); + std::env::set_var("FUZZTEST_CONTINUE_AFTER_CRASH", "true"); + } + + let options = FuzzTestOptions::parse_from(std::iter::empty::()); + expect_true!(options.continue_after_crash); + let expected_duration = "10s".parse().expect("valid duration string"); + expect_that!( + ExecutionMode::from_fuzztest_options(&options), + eq(&ExecutionMode::ReplayCorpus(ReplayCorpusOptions { + replay_corpus_for: expected_duration, + time_budget_type: TimeBudgetType::PerTest, + jobs: None, + continue_after_crash: true, + })) + ); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_REPLAY_CORPUS_FOR"); + std::env::remove_var("FUZZTEST_CORPUS_DB"); + std::env::remove_var("FUZZTEST_CONTINUE_AFTER_CRASH"); + } + } + #[gtest] fn test_replay_findings_requires_corpus_db() { // SAFETY: Testing environment parsing in single-threaded context. diff --git a/rust/src/options.rs b/rust/src/options.rs index b5c61d205..9f4971827 100644 --- a/rust/src/options.rs +++ b/rust/src/options.rs @@ -229,6 +229,9 @@ impl CentipedeArgs { if let Some(jobs) = &fuzz_opts.jobs { add_arg(format!("--j={jobs}"))?; } + if !fuzz_opts.continue_after_crash { + add_arg("--exit_on_crash".to_string())?; + } } ExecutionMode::ReplayCrash(replay_opts) => { add_arg("--replay_crash".to_string())?; @@ -270,6 +273,9 @@ impl CentipedeArgs { if let Some(jobs) = &replay_corpus_opts.jobs { add_arg(format!("--j={jobs}"))?; } + if !replay_corpus_opts.continue_after_crash { + add_arg("--exit_on_crash".to_string())?; + } } ExecutionMode::ListCrashIds(list_opts) => { add_arg("--list_crash_ids=true".to_string())?; @@ -876,4 +882,66 @@ mod tests { expect_true!(args_str.iter().any(|s| s.starts_with("--workdir="))); expect_true!(args_str.contains(&"--test_name=my_mod.my_test")); } + + #[gtest] + fn test_determine_execution_action_fuzz_continue_after_crash_false() { + let options = FuzzTestOptions { + fuzz_for: Some("5s".parse().unwrap()), + continue_after_crash: false, + ..Default::default() + }; + let action = determine_execution_action_internal(&options, "my_mod::my_test"); + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect(); + expect_true!(args_str.contains(&"--exit_on_crash")); + } + + #[gtest] + fn test_determine_execution_action_fuzz_continue_after_crash_true() { + let options = FuzzTestOptions { + fuzz_for: Some("5s".parse().unwrap()), + continue_after_crash: true, + ..Default::default() + }; + let action = determine_execution_action_internal(&options, "my_mod::my_test"); + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect(); + expect_false!(args_str.contains(&"--exit_on_crash")); + } + + #[gtest] + fn test_determine_execution_action_replay_corpus_continue_after_crash_false() { + let options = FuzzTestOptions { + replay_corpus_for: Some("5s".parse().unwrap()), + continue_after_crash: false, + corpus_db: Some("/tmp/corpus_db".into()), + ..Default::default() + }; + let action = determine_execution_action_internal(&options, "my_mod::my_test"); + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect(); + expect_true!(args_str.contains(&"--exit_on_crash")); + } + + #[gtest] + fn test_determine_execution_action_replay_corpus_continue_after_crash_true() { + let options = FuzzTestOptions { + replay_corpus_for: Some("5s".parse().unwrap()), + continue_after_crash: true, + corpus_db: Some("/tmp/corpus_db".into()), + ..Default::default() + }; + let action = determine_execution_action_internal(&options, "my_mod::my_test"); + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect(); + expect_false!(args_str.contains(&"--exit_on_crash")); + } }