Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 94 additions & 1 deletion rust/cargo_fuzztest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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,
})
);
}
Expand Down Expand Up @@ -637,6 +645,7 @@ mod tests {
replay_corpus_for: RunDuration::Indefinitely,
time_budget_type: TimeBudgetType::PerTest,
jobs: None,
continue_after_crash: false,
})
);
}
Expand All @@ -662,6 +671,7 @@ mod tests {
replay_corpus_for: RunDuration::Indefinitely,
time_budget_type: TimeBudgetType::PerTest,
jobs: None,
continue_after_crash: false,
})
);
}
Expand Down Expand Up @@ -691,6 +701,7 @@ mod tests {
replay_corpus_for: "10s".parse().unwrap(),
time_budget_type: TimeBudgetType::Total,
jobs: None,
continue_after_crash: false,
})
);
}
Expand Down Expand Up @@ -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<String>)> = 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<String>)> = 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()))));
}
}
29 changes: 29 additions & 0 deletions rust/cargo_fuzztest/tests/e2e_cli_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(&centipede_bin)
Expand Down Expand Up @@ -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(&centipede_bin)
Expand Down Expand Up @@ -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(&centipede_bin)
Expand Down Expand Up @@ -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(&centipede_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!"));
}
22 changes: 11 additions & 11 deletions rust/cargo_fuzztest/tests/runner_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = cmd.get_args().map(|s| s.to_string_lossy().to_string()).collect();
Expand All @@ -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<String>)> = cmd
Expand All @@ -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<String>)> = cmd
Expand All @@ -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<String>)> = cmd
Expand All @@ -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<String>)> = cmd
Expand All @@ -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<String>)> = cmd
Expand All @@ -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<String>)> = cmd
Expand Down Expand Up @@ -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<String>)> = cmd
Expand Down Expand Up @@ -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<String>)> = cmd
Expand Down Expand Up @@ -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<String>)> = cmd
Expand Down Expand Up @@ -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<String>)> = cmd
Expand Down
2 changes: 2 additions & 0 deletions rust/e2e_tests/replay_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
27 changes: 27 additions & 0 deletions rust/e2e_tests/standalone_mode_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Loading
Loading