| use anyhow::{Context, Result}; |
| use clap::Parser; |
| pub use fuzztest_options::{ |
| ExecutionMode, FuzzFor, FuzzOptions, FuzzTestOptions, ReplayCorpusOptions, ReplayCrashOptions, |
| TimeBudgetType, |
| }; |
| use std::path::{Path, PathBuf}; |
| use std::process::Command; |
| |
| #[derive(Parser, Debug, Clone, Default)] |
| pub struct CargoFuzzTestOptions { |
| #[command(flatten)] |
| pub fuzztest_options: FuzzTestOptions, |
| |
| /// List all fuzz tests in the crate without running them. |
| #[arg(long)] |
| pub list: bool, |
| |
| /// Optional test target name to run (for example, `__fuzztest_mod__my_test::my_test`). |
| /// |
| /// If omitted, all generated fuzz tests in the binary are run. |
| #[arg()] |
| pub test_path: Option<String>, |
| |
| /// Optional path to the Centipede binary executable. |
| /// |
| /// When specified via CLI `--centipede-binary-path <path>`, this is forwarded to |
| /// the compiled test executable via the `FUZZTEST_CENTIPEDE_BINARY_PATH` environment variable. |
| #[arg(long)] |
| pub centipede_binary_path: Option<String>, |
| } |
| |
| impl CargoFuzzTestOptions { |
| fn check_centipede_binary_path_is_set(&self) -> Result<()> { |
| if self.centipede_binary_path.is_none() { |
| anyhow::bail!("`--centipede-binary-path` needs to be specified"); |
| } |
| Ok(()) |
| } |
| |
| fn check_corpus_db_is_set(&self) -> Result<()> { |
| if self.fuzztest_options.corpus_db.is_none() { |
| anyhow::bail!("`--corpus-db` needs to be specified"); |
| } |
| Ok(()) |
| } |
| |
| /// Returns the `ExecutionMode` derived from CLI options. |
| pub fn execution_mode(&self) -> Result<ExecutionMode> { |
| if self.list { |
| return Ok(ExecutionMode::ListFuzzTests); |
| } |
| |
| let mode = ExecutionMode::from_fuzztest_options(&self.fuzztest_options); |
| |
| let mode = match &mode { |
| ExecutionMode::Fuzz(_) |
| | ExecutionMode::ReplayAllCrashes |
| | ExecutionMode::ReplayCorpus(_) => { |
| self.check_centipede_binary_path_is_set()?; |
| mode |
| } |
| ExecutionMode::ReplayCrash(_) => { |
| self.check_centipede_binary_path_is_set()?; |
| self.check_corpus_db_is_set()?; |
| mode |
| } |
| ExecutionMode::SmokeTest => { |
| if self.test_path.is_some() { |
| self.check_centipede_binary_path_is_set()?; |
| ExecutionMode::Fuzz(FuzzOptions { |
| fuzz_for: self.fuzztest_options.fuzz_for.unwrap_or(FuzzFor::Indefinitely), |
| jobs: self.fuzztest_options.jobs, |
| }) |
| } else { |
| mode |
| } |
| } |
| _ => { |
| anyhow::bail!("mode not yet supported"); |
| } |
| }; |
| |
| Ok(mode) |
| } |
| |
| /// Returns the test filter pattern and whether exact matching should be enabled. |
| /// |
| /// If `test_path` is set, returns `(target, true)` for exact test execution. |
| /// Otherwise, returns `("__fuzztest_mod__", false)` to run all fuzz tests. |
| pub fn test_filter(&self) -> (&str, bool) { |
| match &self.test_path { |
| Some(test_path) => (test_path.as_str(), true), |
| None => ("__fuzztest_mod__", false), |
| } |
| } |
| } |
| |
| /// Queries Cargo via `cargo -vV` to discover the default host target triple (e.g. `x86_64-unknown-linux-gnu`). |
| pub fn get_host_target_triple() -> anyhow::Result<String> { |
| let cargo_bin = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); |
| let output = std::process::Command::new(&cargo_bin) |
| .arg("-vV") |
| .output() |
| .context("while attempting to run `cargo -vV` to determine host target triple")?; |
| |
| if !output.status.success() { |
| anyhow::bail!("cargo -vV exited with status: {:?}", output.status); |
| } |
| |
| let stdout_str = String::from_utf8(output.stdout) |
| .context("while attempting to parse stdout of cargo -vV as UTF-8")?; |
| parse_host_triple(&stdout_str) |
| } |
| |
| /// Parses the output of `cargo -vV` to extract the host target triple. |
| /// |
| /// Cargo's verbose version output contains lines of key-value metadata, including |
| /// a `host: <triple>` line representing the host build architecture. |
| fn parse_host_triple(stdout: &str) -> anyhow::Result<String> { |
| for line in stdout.lines() { |
| if let Some(triple) = line.strip_prefix("host: ") { |
| return Ok(triple.trim().to_string()); |
| } |
| } |
| anyhow::bail!("Failed to find 'host: ' line in cargo -vV output") |
| } |
| |
| /// Parses CLI options for the `cargo-fuzztest` binary. |
| /// |
| /// When invoked via `cargo fuzztest ...`, Cargo passes `"fuzztest"` as `argv[1]`. |
| /// We strip `"fuzztest"` if present so `clap` can parse options cleanly. |
| pub fn parse_cli_options() -> CargoFuzzTestOptions { |
| let mut args: Vec<std::ffi::OsString> = std::env::args_os().collect(); |
| if args.len() > 1 && args[1] == "fuzztest" { |
| args.remove(1); |
| } |
| CargoFuzzTestOptions::parse_from(args) |
| } |
| |
| pub struct FuzztestRunner { |
| pub host_triple: String, |
| pub options: CargoFuzzTestOptions, |
| } |
| |
| impl FuzztestRunner { |
| pub fn new(host_triple: String, options: CargoFuzzTestOptions) -> Self { |
| Self { host_triple, options } |
| } |
| |
| /// Constructs `cargo test --no-run --message-format=json --target <host_triple> --lib`. |
| pub fn build_compile_command(&self) -> Command { |
| let mut cmd = Command::new("cargo"); |
| cmd.args([ |
| "test", |
| "--no-run", |
| "--message-format=json", |
| "--target", |
| &self.host_triple, |
| "--lib", |
| ]); |
| |
| // set flags for sancov instrumentation |
| let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_default(); |
| rustflags.push_str( |
| " -Cpasses=sancov-module \ |
| -Cllvm-args=-sanitizer-coverage-level=4 \ |
| -Cllvm-args=-sanitizer-coverage-trace-pc-guard \ |
| -Cllvm-args=-sanitizer-coverage-pc-table \ |
| -Cllvm-args=-sanitizer-coverage-trace-compares", |
| ); |
| cmd.env("RUSTFLAGS", rustflags); |
| |
| cmd |
| } |
| |
| /// Parses the JSON stream emitted by `cargo test --message-format=json` to extract the compiled test executable. |
| pub fn parse_compiler_messages(json_output: &str) -> Result<PathBuf> { |
| let mut executables = Vec::new(); |
| |
| for line in json_output.lines() { |
| let trimmed = line.trim(); |
| if trimmed.is_empty() { |
| continue; |
| } |
| if let Ok(val) = serde_json::from_str::<serde_json::Value>(trimmed) { |
| let is_compiler_artifact = |
| val.get("reason").and_then(|r| r.as_str()) == Some("compiler-artifact"); |
| let is_test_profile = |
| val.get("profile").and_then(|p| p.get("test")).and_then(|t| t.as_bool()) |
| == Some(true); |
| |
| if is_compiler_artifact && is_test_profile { |
| if let Some(exe) = val.get("executable").and_then(|e| e.as_str()) { |
| executables.push(PathBuf::from(exe)); |
| } |
| } |
| } |
| } |
| |
| match executables.len() { |
| 1 => Ok(executables.remove(0)), |
| 0 => anyhow::bail!("no test executable found in Cargo compiler JSON output"), |
| // TODO(the-shank): support multiple test executables, for example from --lib and |
| // --bins targets. |
| _ => anyhow::bail!( |
| "multiple test executables found in Cargo compiler output: {:?}. Expected exactly one.", |
| executables |
| ), |
| } |
| } |
| |
| /// Construct the direct binary invocation command. |
| pub fn build_run_command(&self, test_binary: &Path) -> Result<Command> { |
| let mut cmd = Command::new(test_binary); |
| |
| match self.options.test_filter() { |
| (test_path, true) => { |
| cmd.arg(test_path); |
| cmd.arg("--exact"); |
| } |
| ("__fuzztest_mod__", false) => { |
| cmd.arg("__fuzztest_mod__"); |
| } |
| _ => { |
| unreachable!("no other combination is possible") |
| } |
| } |
| |
| let mode = self.options.execution_mode()?; |
| match mode { |
| ExecutionMode::ListFuzzTests => { |
| cmd.arg("--list"); |
| } |
| |
| ExecutionMode::Fuzz(fuzz_options) => { |
| let FuzzOptions { fuzz_for, jobs } = fuzz_options; |
| match fuzz_for { |
| FuzzFor::Indefinitely => { |
| cmd.env("FUZZTEST_FUZZ_FOR", "inf"); |
| } |
| FuzzFor::Duration(duration) => { |
| cmd.env("FUZZTEST_FUZZ_FOR", duration.to_string()); |
| } |
| } |
| if let Some(jobs) = jobs { |
| cmd.env("FUZZTEST_JOBS", jobs.to_string()); |
| } |
| } |
| |
| ExecutionMode::ReplayCrash(replay_options) => { |
| cmd.env("FUZZTEST_REPLAY_ID", replay_options.replay_id); |
| } |
| |
| ExecutionMode::ReplayAllCrashes => { |
| cmd.env("FUZZTEST_REPLAY_FINDINGS", "true"); |
| } |
| |
| ExecutionMode::ReplayCorpus(replay_corpus_options) => { |
| cmd.env( |
| "FUZZTEST_REPLAY_CORPUS_FOR", |
| replay_corpus_options.replay_corpus_for.to_string(), |
| ); |
| let time_budget_str = match replay_corpus_options.time_budget_type { |
| TimeBudgetType::PerTest => "per-test", |
| TimeBudgetType::Total => "total", |
| }; |
| cmd.env("FUZZTEST_TIME_BUDGET_TYPE", time_budget_str); |
| } |
| |
| ExecutionMode::SmokeTest => { |
| // nothing to be done |
| } |
| } |
| |
| if let Some(corpus_db) = &self.options.fuzztest_options.corpus_db { |
| cmd.env("FUZZTEST_CORPUS_DB", corpus_db); |
| } |
| |
| if let Some(workdir_root) = &self.options.fuzztest_options.workdir_root { |
| cmd.env("FUZZTEST_WORKDIR_ROOT", workdir_root); |
| } |
| |
| // If `--centipede-binary-path` was passed to `cargo-fuzztest`, forward it to the |
| // child test executable via `FUZZTEST_CENTIPEDE_BINARY_PATH` environment variable so the |
| // fuzzer runtime can locate and run Centipede during continuous fuzzing. |
| if let Some(centipede_binary_path) = &self.options.centipede_binary_path { |
| cmd.env("FUZZTEST_CENTIPEDE_BINARY_PATH", centipede_binary_path); |
| } |
| |
| if self.options.fuzztest_options.print_subprocess_log { |
| cmd.env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true"); |
| cmd.arg("--nocapture"); |
| } |
| |
| Ok(cmd) |
| } |
| |
| /// Runs the tool in two steps: |
| /// 1. build the test binary |
| /// 2. execute the target mode command on the compiled test binary |
| pub fn run(&self) -> Result<i32> { |
| // 1. build the test binary |
| let compile_output = self |
| .build_compile_command() |
| .output() |
| .context("while attempting to execute `cargo test --no-run` compilation command")?; |
| |
| if !compile_output.status.success() { |
| // print compiler's stderr to help the user diagnose compilation errors. |
| eprintln!("{}", String::from_utf8_lossy(&compile_output.stdout)); |
| eprintln!("{}", String::from_utf8_lossy(&compile_output.stderr)); |
| anyhow::bail!( |
| "cargo compilation failed with exit code: {:?}", |
| compile_output.status.code() |
| ); |
| } |
| |
| let json_stdout = String::from_utf8(compile_output.stdout) |
| .context("while attempting to parse compilation JSON output as UTF-8")?; |
| let test_binary = Self::parse_compiler_messages(&json_stdout)?; |
| |
| // 2. run the test binary |
| let mut run_cmd = self |
| .build_run_command(&test_binary) |
| .context("while attempting to construct test binary run command")?; |
| let status = |
| run_cmd.status().context("while attempting to execute compiled test binary")?; |
| |
| Ok(status.code().unwrap_or(1)) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use googletest::prelude::*; |
| |
| #[gtest] |
| fn test_parse_host_triple_valid() { |
| let stdout = "rustc 1.75.0 (82e5872f3 2023-12-21)\n\ |
| binary: rustc\n\ |
| commit-hash: 82e5872f3d693c9d7d4c2c56a8fb7f59d47c4058\n\ |
| commit-date: 2023-12-21\n\ |
| host: x86_64-unknown-linux-gnu\n\ |
| release: 1.75.0\n\ |
| LLVM version: 17.0.6\n"; |
| let result = parse_host_triple(stdout).expect("valid stdout parsing should succeed"); |
| assert_eq!(result, "x86_64-unknown-linux-gnu"); |
| } |
| |
| #[gtest] |
| fn test_parse_host_triple_missing_host() { |
| let stdout = "rustc 1.75.0 (82e5872f3 2023-12-21)\n\ |
| binary: rustc\n"; |
| let result = parse_host_triple(stdout); |
| assert!(result.is_err()); |
| assert!(result.unwrap_err().to_string().contains("Failed to find 'host: '")); |
| } |
| |
| #[gtest] |
| fn test_build_run_command_default() { |
| let options = CargoFuzzTestOptions::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 args: Vec<String> = cmd.get_args().map(|a| a.to_string_lossy().to_string()).collect(); |
| assert_eq!(args, vec!["__fuzztest_mod__"]); |
| } |
| |
| #[gtest] |
| fn test_build_run_command_list() { |
| let options = CargoFuzzTestOptions { list: true, ..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 args: Vec<String> = cmd.get_args().map(|a| a.to_string_lossy().to_string()).collect(); |
| assert_eq!(args, vec!["__fuzztest_mod__", "--list"]); |
| } |
| |
| #[gtest] |
| fn test_execution_mode_smoke_test() { |
| let options = CargoFuzzTestOptions { list: false, ..Default::default() }; |
| assert_eq!( |
| options.execution_mode().expect("valid execution mode"), |
| ExecutionMode::SmokeTest |
| ); |
| } |
| |
| #[gtest] |
| fn test_execution_mode_list_fuzz_tests() { |
| let options = CargoFuzzTestOptions { list: true, ..Default::default() }; |
| assert_eq!( |
| options.execution_mode().expect("valid execution mode"), |
| ExecutionMode::ListFuzzTests |
| ); |
| } |
| |
| #[gtest] |
| fn test_cli_option_parsing_list_flag() { |
| let parsed = CargoFuzzTestOptions::try_parse_from(["cargo-fuzztest", "--list"]) |
| .expect("--list argument should be valid CLI option"); |
| assert!(parsed.list); |
| assert_eq!( |
| parsed.execution_mode().expect("valid execution mode"), |
| ExecutionMode::ListFuzzTests |
| ); |
| } |
| |
| #[gtest] |
| fn test_cli_option_parsing_default() { |
| let parsed = CargoFuzzTestOptions::try_parse_from(["cargo-fuzztest"]) |
| .expect("empty CLI arguments should be valid"); |
| assert!(!parsed.list); |
| assert_eq!( |
| parsed.execution_mode().expect("valid execution mode"), |
| ExecutionMode::SmokeTest |
| ); |
| } |
| |
| #[gtest] |
| fn test_cli_option_parsing_replay_id_success() { |
| let parsed = CargoFuzzTestOptions::try_parse_from([ |
| "cargo-fuzztest", |
| "--replay-id", |
| "crash_12345", |
| "--corpus-db", |
| "/tmp/corpus_db", |
| "--centipede-binary-path", |
| "/custom/centipede", |
| ]) |
| .expect("valid replay options should parse successfully"); |
| |
| assert_eq!(parsed.fuzztest_options.replay_id.as_deref(), Some("crash_12345")); |
| assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db")); |
| |
| let mode = parsed.execution_mode().expect("valid execution mode"); |
| assert_eq!( |
| mode, |
| ExecutionMode::ReplayCrash(ReplayCrashOptions { replay_id: "crash_12345".to_string() }) |
| ); |
| } |
| |
| #[gtest] |
| fn test_execution_mode_replay_id_missing_corpus_db_errors() { |
| let options = CargoFuzzTestOptions { |
| fuzztest_options: FuzzTestOptions { |
| replay_id: Some("crash_12345".to_string()), |
| ..Default::default() |
| }, |
| centipede_binary_path: Some("/custom/centipede".to_string()), |
| ..Default::default() |
| }; |
| let err = options.execution_mode().expect_err("missing corpus-db should cause error"); |
| assert!(err.to_string().contains("`--corpus-db` needs to be specified")); |
| } |
| |
| #[gtest] |
| fn test_execution_mode_replay_id_missing_centipede_binary_path_errors() { |
| let options = CargoFuzzTestOptions { |
| fuzztest_options: FuzzTestOptions { |
| replay_id: Some("crash_12345".to_string()), |
| corpus_db: Some("/tmp/corpus_db".to_string()), |
| ..Default::default() |
| }, |
| ..Default::default() |
| }; |
| let err = |
| options.execution_mode().expect_err("missing centipede-binary-path should cause error"); |
| assert!(err.to_string().contains("`--centipede-binary-path` needs to be specified")); |
| } |
| |
| #[gtest] |
| fn test_build_run_command_replay_crash() { |
| let options = CargoFuzzTestOptions { |
| fuzztest_options: FuzzTestOptions { |
| replay_id: Some("crash_12345".to_string()), |
| corpus_db: Some("/tmp/corpus_db".to_string()), |
| ..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_REPLAY_ID".to_string(), Some("crash_12345".to_string())))); |
| assert!( |
| envs.contains(&("FUZZTEST_CORPUS_DB".to_string(), Some("/tmp/corpus_db".to_string()))) |
| ); |
| assert!(envs.contains(&( |
| "FUZZTEST_CENTIPEDE_BINARY_PATH".to_string(), |
| Some("/custom/centipede".to_string()) |
| ))); |
| } |
| |
| #[gtest] |
| fn test_cli_option_parsing_replay_findings_success() { |
| let parsed = CargoFuzzTestOptions::try_parse_from([ |
| "cargo-fuzztest", |
| "--replay-findings", |
| "--corpus-db", |
| "/tmp/corpus_db", |
| "--centipede-binary-path", |
| "/custom/centipede", |
| ]) |
| .expect("valid replay-findings options should parse successfully"); |
| |
| assert!(parsed.fuzztest_options.replay_findings); |
| assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db")); |
| |
| let mode = parsed.execution_mode().expect("valid execution mode"); |
| assert_eq!(mode, ExecutionMode::ReplayAllCrashes); |
| } |
| |
| #[gtest] |
| fn test_execution_mode_replay_findings_missing_centipede_binary_path_errors() { |
| let options = CargoFuzzTestOptions { |
| fuzztest_options: FuzzTestOptions { |
| replay_findings: true, |
| corpus_db: Some("/tmp/corpus_db".to_string()), |
| ..Default::default() |
| }, |
| ..Default::default() |
| }; |
| let err = |
| options.execution_mode().expect_err("missing centipede-binary-path should cause error"); |
| assert!(err.to_string().contains("`--centipede-binary-path` needs to be specified")); |
| } |
| |
| #[gtest] |
| fn test_build_run_command_replay_all_crashes() { |
| let options = CargoFuzzTestOptions { |
| fuzztest_options: FuzzTestOptions { |
| replay_findings: true, |
| corpus_db: Some("/tmp/corpus_db".to_string()), |
| ..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_REPLAY_FINDINGS".to_string(), Some("true".to_string())))); |
| assert!( |
| envs.contains(&("FUZZTEST_CORPUS_DB".to_string(), Some("/tmp/corpus_db".to_string()))) |
| ); |
| assert!(envs.contains(&( |
| "FUZZTEST_CENTIPEDE_BINARY_PATH".to_string(), |
| Some("/custom/centipede".to_string()) |
| ))); |
| } |
| |
| #[gtest] |
| fn test_cli_option_parsing_replay_corpus_for_success() { |
| let parsed = CargoFuzzTestOptions::try_parse_from([ |
| "cargo-fuzztest", |
| "--replay-corpus-for", |
| "10s", |
| "--corpus-db", |
| "/tmp/corpus_db", |
| "--centipede-binary-path", |
| "/custom/centipede", |
| ]) |
| .expect("valid replay-corpus-for options should parse successfully"); |
| |
| assert_eq!(parsed.fuzztest_options.replay_corpus_for, Some("10s".parse().unwrap())); |
| assert_eq!(parsed.fuzztest_options.time_budget_type, TimeBudgetType::PerTest); |
| assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db")); |
| |
| let mode = parsed.execution_mode().expect("valid execution mode"); |
| assert_eq!( |
| mode, |
| ExecutionMode::ReplayCorpus(ReplayCorpusOptions { |
| replay_corpus_for: "10s".parse().unwrap(), |
| time_budget_type: TimeBudgetType::PerTest, |
| }) |
| ); |
| } |
| |
| #[gtest] |
| fn test_cli_option_parsing_replay_corpus_for_with_time_budget_type() { |
| let parsed = CargoFuzzTestOptions::try_parse_from([ |
| "cargo-fuzztest", |
| "--replay-corpus-for", |
| "10s", |
| "--time-budget-type", |
| "total", |
| "--corpus-db", |
| "/tmp/corpus_db", |
| "--centipede-binary-path", |
| "/custom/centipede", |
| ]) |
| .expect("valid options with time-budget-type should parse successfully"); |
| |
| assert_eq!(parsed.fuzztest_options.replay_corpus_for, Some("10s".parse().unwrap())); |
| assert_eq!(parsed.fuzztest_options.time_budget_type, TimeBudgetType::Total); |
| |
| let mode = parsed.execution_mode().expect("valid execution mode"); |
| assert_eq!( |
| mode, |
| ExecutionMode::ReplayCorpus(ReplayCorpusOptions { |
| replay_corpus_for: "10s".parse().unwrap(), |
| time_budget_type: TimeBudgetType::Total, |
| }) |
| ); |
| } |
| |
| #[gtest] |
| fn test_execution_mode_replay_corpus_missing_centipede_binary_path_errors() { |
| let options = CargoFuzzTestOptions { |
| fuzztest_options: FuzzTestOptions { |
| replay_corpus_for: Some("10s".parse().unwrap()), |
| corpus_db: Some("/tmp/corpus_db".to_string()), |
| ..Default::default() |
| }, |
| ..Default::default() |
| }; |
| let err = |
| options.execution_mode().expect_err("missing centipede-binary-path should cause error"); |
| assert!(err.to_string().contains("`--centipede-binary-path` needs to be specified")); |
| } |
| |
| #[gtest] |
| fn test_build_run_command_replay_corpus() { |
| let options = CargoFuzzTestOptions { |
| fuzztest_options: FuzzTestOptions { |
| replay_corpus_for: Some("10s".parse().unwrap()), |
| time_budget_type: TimeBudgetType::Total, |
| corpus_db: Some("/tmp/corpus_db".to_string()), |
| ..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_REPLAY_CORPUS_FOR".to_string(), Some("10s".to_string())))); |
| assert!( |
| envs.contains(&("FUZZTEST_TIME_BUDGET_TYPE".to_string(), Some("total".to_string()))) |
| ); |
| assert!( |
| envs.contains(&("FUZZTEST_CORPUS_DB".to_string(), Some("/tmp/corpus_db".to_string()))) |
| ); |
| assert!(envs.contains(&( |
| "FUZZTEST_CENTIPEDE_BINARY_PATH".to_string(), |
| Some("/custom/centipede".to_string()) |
| ))); |
| } |
| } |