| use googletest::prelude::*; | |
| use std::env; | |
| use std::fs; | |
| use std::path::{Path, PathBuf}; | |
| use std::process::Command; | |
| use tempfile::TempDir; | |
| fn setup_cargo_fuzztest_command(project_dir: &Path, temp_target_dir: &Path) -> Command { | |
| // Locate the cargo-fuzztest CLI binary compiled by Cargo for this integration test | |
| let binary_path = PathBuf::from(env!("CARGO_BIN_EXE_cargo-fuzztest")); | |
| assert!(binary_path.exists(), "cargo-fuzztest binary must exist"); | |
| // Prepend the directory containing cargo-fuzztest to PATH so `cargo fuzztest` resolves to it | |
| let orig_path = env::var("PATH").unwrap_or_default(); | |
| let bin_dir = binary_path.parent().expect("Binary path should have parent directory"); | |
| let new_path = format!("{}:{}", bin_dir.display(), orig_path); | |
| let cargo_bin = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); | |
| let mut cmd = Command::new(cargo_bin); | |
| cmd.arg("fuzztest"); | |
| cmd.current_dir(project_dir); | |
| cmd.env("CARGO_TARGET_DIR", temp_target_dir); | |
| cmd.env("PATH", new_path); | |
| cmd | |
| } | |
| fn get_sample_crate_path(sample_crate_name: &str) -> PathBuf { | |
| let manifest_dir = env!("CARGO_MANIFEST_DIR"); | |
| let sample_crate_path = Path::new(manifest_dir).join("test_crates").join(sample_crate_name); | |
| assert!(sample_crate_path.exists()); | |
| sample_crate_path | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_smoke_test() { | |
| // Resolve the absolute path of the sample crate | |
| let sample_crate_path = get_sample_crate_path("sample_fuzz_crate"); | |
| // Create a temporary directory for Cargo build outputs | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| // Invoke: `cargo fuzztest` inside the resolved sample crate directory. | |
| let output = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()) | |
| .output() | |
| .expect("Failed to run cargo-fuzztest command"); | |
| // Verify that both fuzztest targets in the crate were run in smoke-test mode | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| expect_true!(stdout_str.contains("sample_fuzztest_target")); | |
| expect_true!(stdout_str.contains("another_sample_fuzztest_target")); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_list() { | |
| // Resolve the absolute path of the sample crate | |
| let sample_crate_path = get_sample_crate_path("sample_fuzz_crate"); | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| // Invoke: `cargo-fuzztest --list` inside the sample crate directory | |
| let output = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()) | |
| .arg("--list") | |
| .output() | |
| .expect("Failed to run cargo-fuzztest --list command"); | |
| // Verify execution completed successfully | |
| expect_true!(output.status.success()); | |
| // Verify that full test paths were listed in stdout | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| expect_true!( | |
| stdout_str.contains("__fuzztest_mod__sample_fuzztest_target::sample_fuzztest_target") | |
| ); | |
| expect_true!(stdout_str.contains( | |
| "__fuzztest_mod__another_sample_fuzztest_target::another_sample_fuzztest_target" | |
| )); | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| expect_true!(stdout_str.contains("sample_fuzztest_target")); | |
| expect_true!(stdout_str.contains("another_sample_fuzztest_target")); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_specific_target() { | |
| // Resolve the absolute path of the sample crate | |
| let sample_crate_path = get_sample_crate_path("sample_fuzz_crate"); | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be set for the test"); | |
| cmd.arg("__fuzztest_mod__sample_fuzztest_target::sample_fuzztest_target") | |
| .arg("--fuzz-for=2s") | |
| .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .arg("--centipede-binary-path") | |
| .arg(centipede_bin); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest command"); | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| expect_true!(stdout_str.contains("sample_fuzztest_target")); | |
| expect_false!(stdout_str.contains("another_sample_fuzztest_target")); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_parallel_jobs() { | |
| let sample_crate_path = get_sample_crate_path("sample_fuzz_crate"); | |
| 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("__fuzztest_mod__jobs_test::jobs_test") | |
| .arg("--jobs") | |
| .arg("4") | |
| .arg("--fuzz-for") | |
| .arg("5s") | |
| .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .arg("--centipede-binary-path") | |
| .arg(centipede_bin) | |
| .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true"); | |
| let output = cmd.output().expect("running local cargo-fuzztest sub-process should complete"); | |
| expect_true!(output.status.success()); | |
| let stderr_str = String::from_utf8_lossy(&output.stderr); | |
| use std::collections::HashSet; | |
| let mut pids = HashSet::new(); | |
| for line in stderr_str.lines() { | |
| if let Some(pos) = line.find("LOG: JOBS_TEST_PID: ") { | |
| let pid_str = &line[pos + "LOG: JOBS_TEST_PID: ".len()..]; | |
| if let Ok(pid) = pid_str.parse::<u32>() { | |
| pids.insert(pid); | |
| } | |
| } | |
| } | |
| expect_that!(pids.len(), eq(4)); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_replay_by_id() { | |
| let sample_crate_path = get_sample_crate_path("another_sample_fuzz_crate"); | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| fs::create_dir_all(&temp_target_dir).expect("Failed to corpus db directory"); | |
| let temp_db_dir = TempDir::new().expect("Failed to create temporary corpus db directory"); | |
| fs::create_dir_all(&temp_db_dir).expect("Failed to corpus db directory"); | |
| let workdir_root_dir = | |
| TempDir::new().expect("Failed to create temporary workdir_root directory"); | |
| fs::create_dir_all(&workdir_root_dir).expect("Failed to workdir_root directory"); | |
| let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be set for the test"); | |
| let test_target = "__fuzztest_mod__crashing_fuzztest_target::crashing_fuzztest_target"; | |
| // 1. Run Centipede via cargo-fuzztest to fuzz the target and populate the corpus database. | |
| 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) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--workdir-root") | |
| .arg(workdir_root_dir.path()); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest to fuzz target"); | |
| assert!(output.status.success()); | |
| // 2. Get list of crash ids via cargo-fuzztest --list-crash-ids | |
| let list_temp_dir = TempDir::new().expect("Failed to create temporary list directory"); | |
| let crash_ids_file = list_temp_dir.path().join("crash_ids.txt"); | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg(test_target) | |
| .arg("--list-crash-ids") | |
| .arg("--list-crash-ids-file") | |
| .arg(&crash_ids_file) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest to list crash IDs"); | |
| assert!(output.status.success()); | |
| 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_true!(!crash_ids.is_empty()); | |
| let crash_id = crash_ids[0]; | |
| // 3. Run cargo-fuzztest CLI with --replay-id <crash_id> to verify it replays the crash. | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg(test_target) | |
| .arg("--replay-id") | |
| .arg(crash_id) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--workdir-root") | |
| .arg(workdir_root_dir.path()) | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest in replay mode"); | |
| let stderr_str = String::from_utf8_lossy(&output.stderr); | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| expect_false!(output.status.success()); | |
| expect_true!( | |
| stderr_str.contains("FuzzTest controller reported failure") | |
| || stderr_str.contains("Crashing bug found!") | |
| || stdout_str.contains("FuzzTest controller reported failure") | |
| ); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_replay_all_crashes() { | |
| let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be provided"); | |
| let sample_crate_path = get_sample_crate_path("another_sample_fuzz_crate"); | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| fs::create_dir_all(&temp_target_dir).expect("Failed to create target directory"); | |
| let temp_db_dir = TempDir::new().expect("Failed to create temporary corpus db directory"); | |
| fs::create_dir_all(&temp_db_dir).expect("Failed to create corpus db directory"); | |
| let workdir_root_dir = | |
| TempDir::new().expect("Failed to create temporary workdir_root directory"); | |
| fs::create_dir_all(&workdir_root_dir).expect("Failed to create workdir_root directory"); | |
| let test_target = "__fuzztest_mod__crashing_fuzztest_target::crashing_fuzztest_target"; | |
| // 1. Run Centipede via cargo-fuzztest to fuzz the target and populate the corpus database. | |
| 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) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--workdir-root") | |
| .arg(workdir_root_dir.path()); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest to fuzz target"); | |
| assert!(output.status.success()); | |
| // 2. Run cargo-fuzztest CLI with --replay-findings to verify it replays all crashes from corpus db. | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg(test_target) | |
| .arg("--replay-findings") | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest in replay-findings mode"); | |
| let stderr_str = String::from_utf8_lossy(&output.stderr); | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| expect_true!(output.status.success()); | |
| expect_true!( | |
| stderr_str.contains("Crashing bug found!") || stdout_str.contains("Crashing bug found!") | |
| ); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_replay_corpus() { | |
| let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be provided"); | |
| let sample_crate_path = get_sample_crate_path("sample_fuzz_crate"); | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| fs::create_dir_all(&temp_target_dir).expect("Failed to create target directory"); | |
| let temp_db_dir = TempDir::new().expect("Failed to create temporary corpus db directory"); | |
| fs::create_dir_all(&temp_db_dir).expect("Failed to create corpus db directory"); | |
| let workdir_root_dir = | |
| TempDir::new().expect("Failed to create temporary workdir_root directory"); | |
| fs::create_dir_all(&workdir_root_dir).expect("Failed to create workdir_root directory"); | |
| // 1. Run Centipede via cargo-fuzztest to fuzz and populate the corpus database. | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg("__fuzztest_mod__sample_fuzztest_target::sample_fuzztest_target") | |
| .arg("--fuzz-for=3s") | |
| .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--workdir-root") | |
| .arg(workdir_root_dir.path()); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest to fuzz target"); | |
| assert!(output.status.success()); | |
| // 2. Run cargo-fuzztest CLI with --replay-corpus-for to verify it replays the corpus. | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg("__fuzztest_mod__sample_fuzztest_target::sample_fuzztest_target") | |
| .arg("--replay-corpus-for=2s") | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest in replay-corpus mode"); | |
| let stderr_str = String::from_utf8_lossy(&output.stderr); | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| expect_true!(output.status.success()); | |
| expect_true!( | |
| stderr_str.contains( | |
| "Replaying __fuzztest_mod__sample_fuzztest_target.sample_fuzztest_target for 2s" | |
| ) || stdout_str.contains( | |
| "Replaying __fuzztest_mod__sample_fuzztest_target.sample_fuzztest_target for 2s" | |
| ) | |
| ); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_replay_corpus_total_budget() { | |
| let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be provided"); | |
| let sample_crate_path = get_sample_crate_path("sample_fuzz_crate"); | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| fs::create_dir_all(&temp_target_dir).expect("Failed to create target directory"); | |
| let temp_db_dir = TempDir::new().expect("Failed to create temporary corpus db directory"); | |
| fs::create_dir_all(&temp_db_dir).expect("Failed to create corpus db directory"); | |
| let workdir_root_dir = | |
| TempDir::new().expect("Failed to create temporary workdir_root directory"); | |
| fs::create_dir_all(&workdir_root_dir).expect("Failed to create workdir_root directory"); | |
| // 1. Run Centipede via cargo-fuzztest to fuzz and populate the corpus database. | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg("__fuzztest_mod__sample_fuzztest_target::sample_fuzztest_target") | |
| .arg("--fuzz-for=3s") | |
| .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--workdir-root") | |
| .arg(workdir_root_dir.path()); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest to fuzz target"); | |
| assert!(output.status.success()); | |
| // 2. Run cargo-fuzztest CLI with --replay-corpus-for and --time-budget-type total. | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg("__fuzztest_mod__sample_fuzztest_target::sample_fuzztest_target") | |
| .arg("--replay-corpus-for=4.5s") | |
| .arg("--time-budget-type=total") | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest in replay-corpus mode"); | |
| let stderr_str = String::from_utf8_lossy(&output.stderr); | |
| let stdout_str = String::from_utf8_lossy(&output.stdout); | |
| eprintln!("tmp:: stderr:\n{stderr_str}"); | |
| eprintln!("tmp:: stdout:\n{stdout_str}"); | |
| expect_true!(output.status.success()); | |
| expect_true!( | |
| stderr_str.contains( | |
| "Replaying __fuzztest_mod__sample_fuzztest_target.sample_fuzztest_target for 1.5s" | |
| ) || stdout_str.contains( | |
| "Replaying __fuzztest_mod__sample_fuzztest_target.sample_fuzztest_target for 1.5s" | |
| ) | |
| ); | |
| } | |
| #[gtest] | |
| fn test_cargo_fuzztest_e2e_list_crash_ids() { | |
| let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH") | |
| .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be provided"); | |
| let sample_crate_path = get_sample_crate_path("another_sample_fuzz_crate"); | |
| let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory"); | |
| fs::create_dir_all(&temp_target_dir).expect("Failed to create target directory"); | |
| let temp_db_dir = TempDir::new().expect("Failed to create temporary corpus db directory"); | |
| fs::create_dir_all(&temp_db_dir).expect("Failed to create corpus db directory"); | |
| let workdir_root_dir = | |
| TempDir::new().expect("Failed to create temporary workdir_root directory"); | |
| fs::create_dir_all(&workdir_root_dir).expect("Failed to create workdir_root directory"); | |
| let test_target = "__fuzztest_mod__crashing_fuzztest_target::crashing_fuzztest_target"; | |
| // 1. Run Centipede via cargo-fuzztest to fuzz the target and populate the corpus database. | |
| 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) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--workdir-root") | |
| .arg(workdir_root_dir.path()); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest to fuzz target"); | |
| assert!(output.status.success()); | |
| // 2. Run cargo-fuzztest CLI with --list-crash-ids and --list-crash-ids-file to list crashes from corpus db. | |
| let list_temp_dir = TempDir::new().expect("Failed to create temporary list directory"); | |
| let crash_ids_file = list_temp_dir.path().join("crash_ids.txt"); | |
| let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path()); | |
| cmd.arg(test_target) | |
| .arg("--list-crash-ids") | |
| .arg("--list-crash-ids-file") | |
| .arg(&crash_ids_file) | |
| .arg("--corpus-db") | |
| .arg(temp_db_dir.path()) | |
| .arg("--centipede-binary-path") | |
| .arg(¢ipede_bin); | |
| let output = cmd.output().expect("Failed to run cargo-fuzztest in list-crash-ids mode"); | |
| assert!(output.status.success()); | |
| 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(); | |
| // DISCUSS: We probably should be checking for something more specific, but what would be a | |
| // 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!")); | |
| } |