use anyhow::Error;
use clap::Subcommand;
use fehler::throws;
use heck::ToSnakeCase;
use trident_client::___private::Commander;
use trident_client::___private::ExitCodeMode;
use trident_client::___private::ProjectType;
use trident_client::___private::TestGenerator;
use crate::command::check_fuzz_test_exists;
use crate::command::check_fuzz_test_not_exists;
use crate::command::check_trident_uninitialized;
use crate::command::ensure_running_from_trident_tests;
use crate::command::get_project_root_for_fuzz;
use crate::command::is_anchor_project;
use crate::command::validate_program_name_usage;
#[derive(Subcommand)]
#[allow(non_camel_case_types)]
pub(crate) enum FuzzCommand {
#[command(about = "Generate new Fuzz Test template.")]
Add {
#[arg(
short,
long,
required = false,
help = "Specify the name of the program for which the fuzz test will be generated.",
value_name = "FILE"
)]
program_name: Option<String>,
#[arg(
short,
long,
required = false,
help = "Name of the fuzz test to add.",
value_name = "NAME"
)]
test_name: Option<String>,
#[arg(
short,
long,
required = false,
help = "Skip building the program before adding new fuzz test."
)]
skip_build: bool,
#[arg(
long,
required = false,
num_args = 1..,
help = "Path(s) to IDL file(s). Specify multiple files separated by spaces. When provided, default target/idl/ directory is ignored.",
value_name = "FILE"
)]
idl_paths: Vec<String>,
},
Run {
#[arg(
required = true,
help = "Name of the desired fuzz template to execute (for example fuzz_0)."
)]
target: String,
#[arg(
short = 'e',
long = "exit-code",
required = false,
value_name = "MODE",
help = "Exit with non-zero code on failures. Modes: 'all' (any failure), 'invariants' (only custom invariant/assert failures)."
)]
exit_code: Option<ExitCodeMode>,
#[arg(
required = false,
help = "Master seed used for fuzzing, if not provided it will be generated randomly."
)]
seed: Option<String>,
},
Debug {
#[arg(
required = true,
help = "Name of the desired fuzz template to execute (for example fuzz_0)."
)]
target: String,
#[arg(
required = true,
help = "Master seed of the desired fuzz template to execute."
)]
seed: String,
},
Refresh {
#[arg(
required = true,
help = "Name of the fuzz test to refresh (for example fuzz_0)."
)]
target: String,
#[arg(
short,
long,
required = false,
help = "Specify the name of the program for which the fuzz test will be refreshed.",
value_name = "FILE"
)]
program_name: Option<String>,
#[arg(
short,
long,
required = false,
help = "Skip building the program before refreshing the types file."
)]
skip_build: bool,
#[arg(
long,
required = false,
num_args = 1..,
help = "Path(s) to IDL file(s). Specify multiple files separated by spaces. When provided, default target/idl/ directory is ignored.",
value_name = "FILE"
)]
idl_paths: Vec<String>,
},
}
#[throws]
pub(crate) async fn fuzz(subcmd: FuzzCommand) {
let idl_paths = match &subcmd {
FuzzCommand::Add { idl_paths, .. } => idl_paths.clone(),
FuzzCommand::Refresh { idl_paths, .. } => idl_paths.clone(),
FuzzCommand::Run { .. } | FuzzCommand::Debug { .. } => Vec::new(),
};
let root = get_project_root_for_fuzz(&idl_paths)?;
check_trident_uninitialized(&root)?;
match subcmd {
FuzzCommand::Run {
target,
exit_code,
seed,
} => {
ensure_running_from_trident_tests(&root)?;
let commander = Commander::new(&root);
commander.run(target, exit_code, seed).await?;
}
FuzzCommand::Debug { target, seed } => {
ensure_running_from_trident_tests(&root)?;
let commander = Commander::new(&root);
commander.run_debug(target, seed).await?;
}
FuzzCommand::Add {
program_name,
test_name,
skip_build,
idl_paths,
} => {
let test_name_snake = test_name.map(|name| name.to_snake_case());
let is_anchor = is_anchor_project()?;
let project_type = if is_anchor {
ProjectType::Anchor
} else {
ProjectType::Vanilla
};
validate_program_name_usage(is_anchor, &program_name)?;
let mut generator = TestGenerator::new_with_root(&root, skip_build, project_type)?;
if let Some(test_name) = &test_name_snake {
check_fuzz_test_exists(&root, test_name)?;
}
generator
.add_fuzz_test(program_name, test_name_snake, idl_paths)
.await?;
}
FuzzCommand::Refresh {
target,
program_name,
skip_build,
idl_paths,
} => {
check_fuzz_test_not_exists(&root, &target)?;
let is_anchor = is_anchor_project()?;
let project_type = if is_anchor {
ProjectType::Anchor
} else {
ProjectType::Vanilla
};
validate_program_name_usage(is_anchor, &program_name)?;
let mut generator = TestGenerator::new_with_root(&root, skip_build, project_type)?;
generator
.refresh_fuzz_test(target, program_name, idl_paths)
.await?;
}
};
}