blob: c8a5a03a8206d597525123d0bc7f787c4bbec3ca [file]
use googletest::matchers;
use googletest::prelude::*;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use test_utils::EnvVars;
fn get_target_binary_path(fixture: &EnvVars) -> PathBuf {
std::env::var_os("CARGO_BIN_EXE_replay_fuzz_tests_bin").map(PathBuf::from).unwrap_or_else(
|| {
fixture
.target_binary_path
.parent()
.expect("target_binary_path must have a parent")
.join("replay_fuzz_tests_bin")
},
)
}
#[gtest]
fn replay_by_id_reproduces_panic(fixture: &EnvVars) {
let test_name = "__fuzztest_mod__find_two_bugs_fuzz_test::find_two_bugs_fuzz_test";
let normalized_test_name = test_name.replace("::", ".");
let target_binary_path = get_target_binary_path(fixture);
let target_binary_str = target_binary_path.to_str().expect("valid path string");
let binary_id = target_binary_str.strip_prefix('/').unwrap_or(target_binary_str);
let db_dir = fixture.tmp_dir_path.join("corpus_db");
fs::create_dir_all(&db_dir).expect("Failed to create db directory");
let workdir_root_dir = fixture.tmp_dir_path.join("workdir_root");
fs::create_dir_all(&workdir_root_dir).expect("Failed to workdir_root directory");
// 1. Run Centipede to fuzz the target and populate the corpus database.
Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.env("FUZZTEST_FUZZ_FOR", "10s")
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_WORKDIR_ROOT", &workdir_root_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true")
.status()
.expect("Failed to spawn binary");
// 2. Retrieve the crash IDs from the corpus database using Centipede's --list_crash_ids flag.
let crash_ids_file = fixture.tmp_dir_path.join("crash_ids.txt");
let list_args = [
format!("--binary={}", target_binary_path.display()),
format!("--fuzztest_binary_identifier={}", binary_id),
format!("--test_name={}", normalized_test_name),
format!("--fuzztest_corpus_database={}", db_dir.display()),
"--list_crash_ids=1".to_string(),
format!("--list_crash_ids_file={}", crash_ids_file.display()),
];
let list_args_refs: Vec<&str> = list_args.iter().map(|s| s.as_str()).collect();
test_utils::run_centipede_with_args_expect_termination(fixture, &list_args_refs);
let crash_ids_contents =
fs::read_to_string(&crash_ids_file).expect("Failed to read crash IDs file");
let crash_ids: Vec<&str> =
crash_ids_contents.lines().filter(|line| !line.trim().is_empty()).collect();
expect_that!(crash_ids.len(), eq(2));
// 3. Verify that replaying each crash ID reproduces a panic or segfault.
let mut reproduced_panic = false;
let mut reproduced_segfault = false;
for crash_id in &crash_ids {
let process = Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.arg("--nocapture")
.env("FUZZTEST_REPLAY_ID", crash_id)
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.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);
expect_that!(output.status.success(), eq(false));
expect_that!(stderr, matchers::contains_substring("FuzzTest controller reported failure"));
if stderr.contains("Bug 1 found!") {
reproduced_panic = true;
} else if stderr.contains("Bug 2 found!") {
reproduced_segfault = true;
}
}
expect_that!(reproduced_panic, eq(true));
expect_that!(reproduced_segfault, eq(true));
}
#[gtest]
fn replay_all_reproduces_all_failures(fixture: &EnvVars) {
let test_name = "__fuzztest_mod__find_two_bugs_fuzz_test::find_two_bugs_fuzz_test";
let normalized_test_name = test_name.replace("::", ".");
let target_binary_path = get_target_binary_path(fixture);
let target_binary_str = target_binary_path.to_str().expect("valid path string");
let binary_id = target_binary_str.strip_prefix('/').unwrap_or(target_binary_str);
let db_dir = fixture.tmp_dir_path.join("corpus_db_all");
fs::create_dir_all(&db_dir).expect("Failed to create db directory");
// 1. Run Centipede to fuzz the target and populate the corpus database.
let centipede_args = [
format!("--binary={}", target_binary_path.display()),
format!("--fuzztest_binary_identifier={}", binary_id),
format!("--test_name={}", normalized_test_name),
format!("--fuzztest_corpus_database={}", db_dir.display()),
"--fuzztest_time_limit_per_test=5s".to_string(),
"--exit_on_crash=0".to_string(),
];
let centipede_args_refs: Vec<&str> = centipede_args.iter().map(|s| s.as_str()).collect();
test_utils::run_centipede_with_args_expect_termination(fixture, &centipede_args_refs);
// 2. Replay all findings using FUZZTEST_REPLAY_FINDINGS=true.
let process = Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.env("FUZZTEST_REPLAY_FINDINGS", "true")
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.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);
expect_that!(output.status.success(), eq(true));
expect_that!(stderr, matchers::contains_substring("Bug 1 found!"));
expect_that!(stderr, matchers::contains_substring("Bug 2 found!"));
}