Refactor FuzzTestOptions into a standalone sub-crate Refactor FuzzTestOptions, and common configuration types out of the main fuzztest crate into a new dedicated fuzztest_options sub-crate, so that it can be shared between fuzztest and cargo-fuzztest tool. Changes: - Extract FuzzTestOptions, TimeBudgetType, FuzzOptions, FuzzFor, ReplayCrashOptions, and ReplayCorpusOptions into third_party/googlefuzztest/rust/options. - Re-export options types from fuzztest_options in fuzztest::options for backwards compatibility. - Add ExecutionModeExt extension trait in fuzztest::options for runtime action preparation. PiperOrigin-RevId: 964070742
diff --git a/Cargo.lock b/Cargo.lock index 033c775..3d7faa8 100644 --- a/Cargo.lock +++ b/Cargo.lock
@@ -244,6 +244,7 @@ "coverage", "engine-ffi", "fuzztest-macro", + "fuzztest-options", "googletest", "humantime", "inventory", @@ -269,6 +270,16 @@ ] [[package]] +name = "fuzztest-options" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "googletest", + "humantime", +] + +[[package]] name = "getrandom" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/rust/BUILD b/rust/BUILD index 49c0df7..ef68440 100644 --- a/rust/BUILD +++ b/rust/BUILD
@@ -18,6 +18,7 @@ deps = [ "@com_google_fuzztest//rust/coverage", "@com_google_fuzztest//rust/engine", + "@com_google_fuzztest//rust/options:fuzztest_options", "@crate_index//:anyhow", # v1 "@crate_index//:clap", # v4 "@crate_index//:humantime", # v2
diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 31ce402..8a414e0 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml
@@ -13,6 +13,7 @@ coverage = { path = "coverage" } engine = { path = "engine", package = "engine-ffi" } fuzztest-macro = { path = "fuzztest_macro" } +fuzztest-options = { path = "options" } humantime.workspace = true inventory.workspace = true num-traits.workspace = true @@ -23,7 +24,7 @@ tempfile.workspace = true [dev-dependencies] -googletest = "0.14.3" +googletest.workspace = true [dev-dependencies.trybuild] version = "1.0.103"
diff --git a/rust/e2e_tests/replay_test.rs b/rust/e2e_tests/replay_test.rs index ba512c8..ca65490 100644 --- a/rust/e2e_tests/replay_test.rs +++ b/rust/e2e_tests/replay_test.rs
@@ -25,26 +25,27 @@ 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"); + let db_dir = fixture.tmp_dir_path.join("replay_by_id_reproduces_panic").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"); + let workdir_root_dir = + fixture.tmp_dir_path.join("replay_by_id_reproduces_panic").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", "60s") + .env("FUZZTEST_FUZZ_FOR", "15s") .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 crash_ids_file = + fixture.tmp_dir_path.join("replay_by_id_reproduces_panic").join("crash_ids.txt"); let list_args = [ format!("--binary={}", target_binary_path.display()), format!("--fuzztest_binary_identifier={}", binary_id), @@ -102,23 +103,23 @@ 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"); + let db_dir = + fixture.tmp_dir_path.join("replay_all_reproduces_all_failures").join("corpus_db_all"); fs::create_dir_all(&db_dir).expect("Failed to create db directory"); - let workdir_root_dir = fixture.tmp_dir_path.join("workdir_root_all"); + let workdir_root_dir = + fixture.tmp_dir_path.join("replay_all_reproduces_all_failures").join("workdir_root_all"); fs::create_dir_all(&workdir_root_dir).expect("Failed to workdir_root_all 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", "60s") + .env("FUZZTEST_FUZZ_FOR", "15s") .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");
diff --git a/rust/e2e_tests/standalone_mode_test.rs b/rust/e2e_tests/standalone_mode_test.rs index 98cede1..20ba005 100644 --- a/rust/e2e_tests/standalone_mode_test.rs +++ b/rust/e2e_tests/standalone_mode_test.rs
@@ -130,8 +130,14 @@ let target_binary_path = fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin"); - let corpus_db = fixture.tmp_dir_path.join("corpus_db_per_test"); - let workdir_root = fixture.tmp_dir_path.join("workdir_root_per_test"); + let corpus_db = fixture + .tmp_dir_path + .join("standalone_mode_replay_corpus_per_test_budget") + .join("corpus_db_per_test"); + let workdir_root = fixture + .tmp_dir_path + .join("standalone_mode_replay_corpus_per_test_budget") + .join("workdir_root_per_test"); // Centipede appends the binary identifier (relative path) to the corpus database path. let identifier = target_binary_path.to_str().unwrap(); @@ -215,8 +221,14 @@ let target_binary_path = fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin"); - let corpus_db = fixture.tmp_dir_path.join("corpus_db_total"); - let workdir_root = fixture.tmp_dir_path.join("workdir_root_total"); + let corpus_db = fixture + .tmp_dir_path + .join("standalone_mode_replay_corpus_per_test_budget") + .join("corpus_db_total"); + let workdir_root = fixture + .tmp_dir_path + .join("standalone_mode_replay_corpus_per_test_budget") + .join("workdir_root_total"); // Centipede appends the binary identifier (relative path) to the corpus database path. let identifier = target_binary_path.to_str().unwrap();
diff --git a/rust/e2e_tests/testdata/replay_fuzz_tests.rs b/rust/e2e_tests/testdata/replay_fuzz_tests.rs index 7d664da..b4a1027 100644 --- a/rust/e2e_tests/testdata/replay_fuzz_tests.rs +++ b/rust/e2e_tests/testdata/replay_fuzz_tests.rs
@@ -10,16 +10,12 @@ } #[fuzztest(a = Arbitrary::<i32>::default())] -fn find_two_bugs_fuzz_test(a: i32) { - println!("PROPERTY_FUNCTION_EXECUTED"); - if a < -1 { +fn find_two_bugs_fuzz_test(a: i32) { println!("PROPERTY_FUNCTION_EXECUTED"); + if a == 10 { panic!("Bug 1 found!"); - } else if a > 1 { + } else if a == 20 { println!("Bug 2 found!"); - // Safety: We are intentionally causing a segfault to test that the engine would catch it. - unsafe { - std::ptr::null_mut::<i32>().write_volatile(42); - } + std::process::exit(99); } }
diff --git a/rust/options/BUILD b/rust/options/BUILD new file mode 100644 index 0000000..b85746a --- /dev/null +++ b/rust/options/BUILD
@@ -0,0 +1,30 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +licenses(["notice"]) + +exports_files(["BUILD"]) + +rust_library( + name = "fuzztest_options", + srcs = glob([ + "src/**/*.rs", + ]), + edition = "2024", + visibility = ["@com_google_fuzztest//rust:__subpackages__"], + deps = [ + "@crate_index//:anyhow", # v1 + "@crate_index//:clap", # v4 + "@crate_index//:humantime", # v2 + ], +) + +rust_test( + name = "fuzztest_options_test", + # Avoid interference when setting/resetting environment variables in tests. + args = ["--test-threads=1"], + crate = ":fuzztest_options", + edition = "2024", + deps = [ + "@crate_index//:googletest", + ], +)
diff --git a/rust/options/Cargo.toml b/rust/options/Cargo.toml new file mode 100644 index 0000000..2fa2ff4 --- /dev/null +++ b/rust/options/Cargo.toml
@@ -0,0 +1,12 @@ +[package] +name = "fuzztest-options" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap.workspace = true +humantime.workspace = true +anyhow.workspace = true + +[dev-dependencies] +googletest.workspace = true
diff --git a/rust/options/src/lib.rs b/rust/options/src/lib.rs new file mode 100644 index 0000000..1e72d34 --- /dev/null +++ b/rust/options/src/lib.rs
@@ -0,0 +1,207 @@ +// This module provides the core command-line flag and environment variable options +// structure (`FuzzTestOptions`) and domain execution modes (`ExecutionMode`). + +use clap::{Parser, ValueEnum}; +use humantime::Duration; + +/// Time budget calculation type for replay corpus mode. +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum TimeBudgetType { + #[default] + PerTest, + Total, +} + +/// Parses a fuzzing duration string from `FUZZTEST_FUZZ_FOR`. +/// +/// Matches `"inf"` or `"infinity"` to [`FuzzFor::Indefinitely`]. All other values +/// are parsed as standard human-readable durations (for example, `"5s"` or `"10m"`). +fn parse_fuzz_for(s: &str) -> anyhow::Result<FuzzFor> { + let s_lower = s.trim().to_lowercase(); + if s_lower == "inf" || s_lower == "infinity" { + Ok(FuzzFor::Indefinitely) + } else { + let duration = s.parse()?; + Ok(FuzzFor::Duration(duration)) + } +} + +/// Command-line and environment variable options parsed for the FuzzTest harness. +#[derive(Parser, Debug, Clone, Default)] +pub struct FuzzTestOptions { + /// The working root directory. + #[arg(env = "FUZZTEST_WORKDIR_ROOT", long)] + pub workdir_root: Option<String>, + + /// The duration for which each test should be fuzzed. + /// + /// Accepts a human-readable duration (e.g., `5s`, `10m`, `1h`) or `inf` / `infinity` + /// to fuzz indefinitely until a crash is found or it is stopped manually. + #[arg(env = "FUZZTEST_FUZZ_FOR", long, value_parser = parse_fuzz_for)] + pub fuzz_for: Option<FuzzFor>, + + /// If true, subprocess logs are printed after every batch. Note that crash logs are always + /// printed regardless of this flag's value. + #[arg(env = "FUZZTEST_PRINT_SUBPROCESS_LOG", long)] + pub print_subprocess_log: bool, + + /// Number of parallel jobs to run. + #[arg(env = "FUZZTEST_JOBS", long)] + pub jobs: Option<usize>, + + /// The crash ID to be replayed from the corpus database. + /// + /// If set, `corpus_db` must also be specified. This mode retrieves the crashing input + /// associated with the given ID from the database and executes the property function. + #[arg(env = "FUZZTEST_REPLAY_ID", long, requires = "corpus_db")] + pub replay_id: Option<String>, + + /// Replay all crashing inputs from the corpus database. + #[arg(env = "FUZZTEST_REPLAY_FINDINGS", long)] + pub replay_findings: bool, + + /// Replay the corpus for a specified duration. + #[arg(env = "FUZZTEST_REPLAY_CORPUS_FOR", long)] + pub replay_corpus_for: Option<Duration>, + + /// Time budget calculation type for replay corpus mode. + #[arg(env = "FUZZTEST_TIME_BUDGET_TYPE", long, value_enum, default_value_t = TimeBudgetType::PerTest)] + pub time_budget_type: TimeBudgetType, + + /// The path to the corpus database. + /// + /// If set to non-empty, updates/queries the corpus database that contains coverage, + /// regression, and crashing inputs for each test binary and fuzz test. + #[arg(env = "FUZZTEST_CORPUS_DB", long)] + pub corpus_db: Option<String>, +} + +/// Strongly-typed domain execution mode for test runs. +/// +/// This enum represents the parsed high-level user intent derived from `FuzzTestOptions`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExecutionMode { + /// Smoke test execution (regular unit test fallback). + SmokeTest, + + /// Fuzzing mode. + Fuzz(FuzzOptions), + + /// Replay a specific crash ID from the corpus database. + ReplayCrash(ReplayCrashOptions), + + /// Replay all crashing inputs stored in the corpus database. + ReplayAllCrashes, + + /// Replay corpus inputs for a specified duration. + ReplayCorpus(ReplayCorpusOptions), +} + +impl ExecutionMode { + /// Evaluates the raw options and maps them to a strongly-typed domain `ExecutionMode`. + /// + /// This decouples raw environment/CLI option parsing from execution mode validation. + pub fn from_fuzztest_options(options: &FuzzTestOptions) -> ExecutionMode { + if let Some(replay_corpus_for) = options.replay_corpus_for { + return ExecutionMode::ReplayCorpus(ReplayCorpusOptions { + replay_corpus_for, + time_budget_type: options.time_budget_type, + }); + } + + if options.replay_findings { + return ExecutionMode::ReplayAllCrashes; + } + + if let Some(replay_id) = &options.replay_id { + return ExecutionMode::ReplayCrash(ReplayCrashOptions { replay_id: replay_id.clone() }); + } + + if let Some(fuzz_for) = options.fuzz_for { + return ExecutionMode::Fuzz(FuzzOptions { fuzz_for, jobs: options.jobs.clone() }); + } + + ExecutionMode::SmokeTest + } +} + +/// Mode-specific options for continuous fuzzing. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FuzzOptions { + pub fuzz_for: FuzzFor, + + /// 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<usize>, +} + +/// The duration or limit for fuzzing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FuzzFor { + /// Fuzz indefinitely until it is manually stopped or a crash is found. + Indefinitely, + + /// Fuzz for a specific duration. + Duration(Duration), +} + +/// Mode-specific options for replaying a specific crashing input from the corpus database. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReplayCrashOptions { + pub replay_id: String, +} + +/// Mode-specific options for replaying corpus for a duration. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReplayCorpusOptions { + pub replay_corpus_for: Duration, + pub time_budget_type: TimeBudgetType, +} + +#[cfg(test)] +mod tests { + use super::*; + use googletest::prelude::*; + use std::ffi::OsString; + + #[gtest] + fn test_replay_id_requires_corpus_db() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_REPLAY_ID", "my_crash_123"); + std::env::remove_var("FUZZTEST_CORPUS_DB"); + } + + let result = FuzzTestOptions::try_parse_from(std::iter::empty::<OsString>()); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_REPLAY_ID"); + } + + let err = result.expect_err("parsing should fail when corpus_db is missing"); + expect_that!(err.kind(), eq(clap::error::ErrorKind::MissingRequiredArgument)); + } + + #[gtest] + fn test_replay_id_with_corpus_db_succeeds() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_REPLAY_ID", "my_crash_123"); + std::env::set_var("FUZZTEST_CORPUS_DB", "/tmp/corpus_db"); + } + + let result = FuzzTestOptions::try_parse_from(std::iter::empty::<OsString>()); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_REPLAY_ID"); + std::env::remove_var("FUZZTEST_CORPUS_DB"); + } + + let options = + result.expect("parsing should succeed when both replay_id and corpus_db are present"); + expect_that!(options.replay_id.as_deref(), eq(Some("my_crash_123"))); + expect_that!(options.corpus_db.as_deref(), eq(Some("/tmp/corpus_db"))); + } +}
diff --git a/rust/src/options.rs b/rust/src/options.rs index c9e46d8..210a535 100644 --- a/rust/src/options.rs +++ b/rust/src/options.rs
@@ -1,113 +1,17 @@ use crate::internal::FuzzTestRegistration; use ::engine::engine_ffi; use anyhow::Context; -use clap::{Parser, ValueEnum}; -use humantime::Duration; +use clap::Parser; use std::ffi::CString; use std::ffi::OsString; use std::path::Path; use std::sync::OnceLock; use tempfile::{NamedTempFile, TempDir}; -/// Time budget calculation type for replay corpus mode. -#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum TimeBudgetType { - #[default] - PerTest, - Total, -} - -/// Parses a fuzzing duration string from `FUZZTEST_FUZZ_FOR`. -/// -/// Matches `"inf"` or `"infinity"` to [`FuzzFor::Indefinitely`]. All other values -/// are parsed as standard human-readable durations (for example, `"5s"` or `"10m"`). -fn parse_fuzz_for(s: &str) -> anyhow::Result<FuzzFor> { - let s_lower = s.trim().to_lowercase(); - if s_lower == "inf" || s_lower == "infinity" { - Ok(FuzzFor::Indefinitely) - } else { - let duration = s.parse()?; - Ok(FuzzFor::Duration(duration)) - } -} - -/// Command-line and environment variable options parsed for the FuzzTest harness. -#[derive(Parser, Debug, Clone, Default)] -pub struct FuzzTestOptions { - /// The working root directory. - #[arg(env = "FUZZTEST_WORKDIR_ROOT", long)] - pub workdir_root: Option<String>, - - /// The duration for which each test should be fuzzed. - /// - /// Accepts a human-readable duration (e.g., `5s`, `10m`, `1h`) or `inf` / `infinite` - /// to fuzz indefinitely until a crash is found or it is stopped manually. - #[arg(env = "FUZZTEST_FUZZ_FOR", long, value_parser = parse_fuzz_for)] - pub fuzz_for: Option<FuzzFor>, - - /// If true, subprocess logs are printed after every batch. Note that crash logs are always printed - /// regardless of this flag's value. - #[arg(env = "FUZZTEST_PRINT_SUBPROCESS_LOG", long)] - pub print_subprocess_log: bool, - - /// Number of parallel jobs to run. - #[arg(env = "FUZZTEST_JOBS", long)] - pub jobs: Option<usize>, - - /// The crash ID to be replayed from the corpus database. - /// - /// If set, `corpus_db` must also be specified. This mode retrieves the crashing input - /// associated with the given ID from the database and executes the property function. - #[arg(env = "FUZZTEST_REPLAY_ID", long, requires = "corpus_db")] - pub replay_id: Option<String>, - - /// Replay all crashing inputs from the corpus database. - #[arg(env = "FUZZTEST_REPLAY_FINDINGS", long)] - pub replay_findings: bool, - - /// Replay the corpus for a specified duration. - #[arg(env = "FUZZTEST_REPLAY_CORPUS_FOR", long)] - pub replay_corpus_for: Option<Duration>, - - /// Time budget calculation type for replay corpus mode. - #[arg(env = "FUZZTEST_TIME_BUDGET_TYPE", long, value_enum, default_value_t = TimeBudgetType::PerTest)] - pub time_budget_type: TimeBudgetType, - - /// The path to the corpus database. - /// - /// If set to non-empty, updates/queries the corpus database that contains coverage, - /// regression, and crashing inputs for each test binary and fuzz test. - #[arg(env = "FUZZTEST_CORPUS_DB", long)] - pub corpus_db: Option<String>, -} - -impl FuzzTestOptions { - /// Evaluates the raw options and maps them to a strongly-typed domain `ExecutionMode`. - /// - /// This decouples raw environment/CLI option parsing from execution mode validation. - pub fn execution_mode(&self) -> ExecutionMode { - if let Some(replay_corpus_for) = self.replay_corpus_for { - return ExecutionMode::ReplayCorpus(ReplayCorpusOptions { - replay_corpus_for, - time_budget_type: self.time_budget_type, - }); - } - - if self.replay_findings { - return ExecutionMode::ReplayAllCrashes; - } - - if let Some(replay_id) = &self.replay_id { - return ExecutionMode::ReplayCrash(ReplayCrashOptions { replay_id: replay_id.clone() }); - } - - if let Some(fuzz_for) = self.fuzz_for { - return ExecutionMode::Fuzz(FuzzOptions { fuzz_for, jobs: self.jobs.clone() }); - } - - ExecutionMode::SmokeTest - } -} +pub use fuzztest_options::{ + ExecutionMode, FuzzFor, FuzzOptions, FuzzTestOptions, ReplayCorpusOptions, ReplayCrashOptions, + TimeBudgetType, +}; /// Returns a lazily-initialized static reference to the global `FuzzTestOptions`. pub fn get_fuzztest_options() -> &'static FuzzTestOptions { @@ -119,64 +23,20 @@ OPTIONS.get_or_init(|| FuzzTestOptions::parse_from(std::iter::empty::<OsString>())) } -/// Strongly-typed domain execution mode for test runs. -/// -/// This enum represents the parsed high-level user intent derived from `FuzzTestOptions`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ExecutionMode { - /// Smoke test execution (regular unit test fallback). - SmokeTest, - - /// Fuzzing mode. - Fuzz(FuzzOptions), - - /// Replay a specific crash ID from the corpus database. - ReplayCrash(ReplayCrashOptions), - - /// Replay all crashing inputs stored in the corpus database. - ReplayAllCrashes, - - /// Replay corpus inputs for a specified duration. - ReplayCorpus(ReplayCorpusOptions), -} - -/// Mode-specific options for continuous fuzzing. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct FuzzOptions { - pub fuzz_for: FuzzFor, - - /// 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<usize>, -} - -/// The duration or limit for fuzzing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FuzzFor { - /// Fuzz indefinitely until it is manually stopped or a crash is found. - Indefinitely, - - /// Fuzz for a specific duration. - Duration(Duration), -} - -/// Mode-specific options for replaying a specific crashing input from the corpus database. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ReplayCrashOptions { - pub replay_id: String, -} - -/// Mode-specific options for replaying corpus for a duration. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ReplayCorpusOptions { - pub replay_corpus_for: Duration, - pub time_budget_type: TimeBudgetType, -} - -impl ExecutionMode { +trait ExecutionModeExt { /// Prepares the concrete `ExecutionAction` payload for execution by building any required /// `CentipedeArgs` or temporary runtime assets. - pub fn prepare_action( + fn prepare_action( + &self, + options: &FuzzTestOptions, + current_test_name: &str, + ) -> anyhow::Result<ExecutionAction>; +} + +impl ExecutionModeExt for ExecutionMode { + /// Prepares the concrete `ExecutionAction` payload for execution by building any required + /// `CentipedeArgs` or temporary runtime assets. + fn prepare_action( &self, options: &FuzzTestOptions, current_test_name: &str, @@ -288,6 +148,9 @@ add_arg(format!("--fuzztest_binary_identifier={binary_id}"))?; add_arg("--populate_binary_info=false".to_string())?; + // TODO(the-shank): provide a way to override this. + // allow more crashes to be reported when running with FuzzTest (default is 5) + add_arg("--max_num_crash_reports=20".to_string())?; add_arg("--fork_server=false".to_string())?; add_arg(format!("--print_runner_log={}", options.print_subprocess_log))?; @@ -426,8 +289,7 @@ options: &FuzzTestOptions, current_test_name: &str, ) -> ExecutionAction { - options - .execution_mode() + ExecutionMode::from_fuzztest_options(options) .prepare_action(options, current_test_name) .expect("failed to prepare execution action from fuzztest options") } @@ -486,7 +348,10 @@ let options = FuzzTestOptions::parse_from(std::iter::empty::<OsString>()); expect_true!(options.fuzz_for.is_some()); - expect_that!(options.execution_mode(), matches_pattern!(ExecutionMode::Fuzz(_))); + expect_that!( + ExecutionMode::from_fuzztest_options(&options), + matches_pattern!(ExecutionMode::Fuzz(_)) + ); // SAFETY: Cleaning up environment variables. unsafe { @@ -504,7 +369,7 @@ let options = FuzzTestOptions::parse_from(std::iter::empty::<OsString>()); expect_that!(options.fuzz_for, eq(Some(FuzzFor::Indefinitely))); - let mode = options.execution_mode(); + let mode = ExecutionMode::from_fuzztest_options(&options); let ExecutionMode::Fuzz(fuzz_opts) = mode else { panic!("Expected ExecutionMode::Fuzz"); };