use {
chrono::Utc,
clap::ValueEnum,
mollusk_svm::{
result::{Compare, Config, InstructionResult},
Mollusk,
},
mollusk_svm_bencher::{get_solana_version, result::MolluskComputeUnitBenchResult},
std::path::PathBuf,
};
#[derive(Clone, Debug, Default, ValueEnum)]
pub enum ProtoLayout {
#[default]
Mollusk,
Firedancer,
}
pub struct CusReport {
pub path: String,
pub table_header: String,
}
impl CusReport {
pub fn new(path: String, table_header: Option<String>) -> Self {
let table_header = table_header.unwrap_or_else(|| Utc::now().to_string());
Self { path, table_header }
}
}
pub struct RunResult<'a> {
pub pass: bool,
pub bench_result: Option<MolluskComputeUnitBenchResult<'a>>,
}
pub struct Runner {
checks: Vec<Compare>,
cus_report: Option<CusReport>,
inputs_only: bool,
program_logs: bool,
proto: ProtoLayout,
verbose: bool,
}
impl Runner {
pub const fn new(
checks: Vec<Compare>,
cus_report: Option<CusReport>,
inputs_only: bool,
program_logs: bool,
proto: ProtoLayout,
verbose: bool,
) -> Self {
Self {
checks,
cus_report,
inputs_only,
program_logs,
proto,
verbose,
}
}
fn run_fixture(
&self,
mollusk: &mut Mollusk,
fixture_path: &str,
) -> (InstructionResult, InstructionResult) {
match self.proto {
ProtoLayout::Mollusk => {
let fixture = mollusk_svm_fuzz_fixture::Fixture::load_from_blob_file(fixture_path);
let result = mollusk.process_fixture(&fixture);
let effects = (&fixture.output).into();
(result, effects)
}
ProtoLayout::Firedancer => {
let fixture =
mollusk_svm_fuzz_fixture_firedancer::Fixture::load_from_blob_file(fixture_path);
let result = mollusk.process_firedancer_fixture(&fixture);
let (_, effects) = mollusk_svm::fuzz::firedancer::load_firedancer_fixture(&fixture);
(result, effects)
}
}
}
fn run<'a>(
&self,
ground: Option<&mut Mollusk>,
target: &mut Mollusk,
fixture_path: &'a str,
) -> Result<RunResult<'a>, Box<dyn std::error::Error>> {
if !self.program_logs {
solana_logger::setup_with("");
}
let mut pass = true;
if self.verbose {
println!("----------------------------------------");
}
let ground_result = ground.map(|ground| {
if self.verbose {
println!("[GROUND]: FIX: {}", fixture_path);
}
if self.program_logs {
println!("[GROUND]: Program logs:");
println!();
}
let (ground_result, effects) = self.run_fixture(ground, fixture_path);
if self.program_logs {
println!();
}
if self.verbose {
println!("[GROUND]: Result:");
println!();
println!("{:?}", &ground_result);
println!();
}
if !self.inputs_only {
if self.verbose {
println!("[GROUND]: Comparing against fixture effects...");
println!();
}
pass &= ground_result.compare_with_config(
&effects,
&self.checks,
&Config {
panic: false,
verbose: self.verbose,
},
);
}
ground_result
});
if self.verbose {
println!("[TARGET]: FIX: {}", &fixture_path);
}
if self.program_logs {
println!("[TARGET]: Program logs:");
println!();
}
let (target_result, effects) = self.run_fixture(target, fixture_path);
let bench_result = if self.cus_report.is_some() {
Some(MolluskComputeUnitBenchResult::new(
parse_fixture_name(fixture_path),
target_result.clone(),
))
} else {
None
};
if self.program_logs {
println!();
}
if self.verbose {
println!("[TARGET]: Result:");
println!();
println!("{:?}", &target_result);
println!();
}
if !self.inputs_only {
if self.verbose {
println!("[TARGET]: Comparing against fixture effects...");
println!();
}
pass &= target_result.compare_with_config(
&effects,
&self.checks,
&Config {
panic: false,
verbose: self.verbose,
},
);
}
if let Some(ground_result) = ground_result {
if self.verbose {
println!("[TEST]: Comparing the two results...");
println!();
}
pass &= ground_result.compare_with_config(
&target_result,
&self.checks,
&Config {
panic: false,
verbose: self.verbose,
},
);
}
if self.verbose {
println!();
}
if pass {
println!("PASS: {}", &fixture_path);
} else {
println!("FAIL: {}", &fixture_path);
}
if self.verbose {
println!("----------------------------------------");
println!();
}
Ok(RunResult { pass, bench_result })
}
pub fn run_all(
&self,
mut ground: Option<&mut Mollusk>,
target: &mut Mollusk,
fixtures: &[String],
) -> Result<(), Box<dyn std::error::Error>> {
let mut failures = 0;
let mut bench_results = Vec::new();
for fixture_path in fixtures {
let mut result = self.run(ground.as_deref_mut(), target, fixture_path)?;
if let Some(bench_result) = result.bench_result.take() {
bench_results.push(bench_result);
}
if !result.pass {
failures += 1;
}
}
println!();
println!("[DONE][TEST RESULT]: {} failures", failures);
if failures > 0 {
std::process::exit(1);
}
if let Some(cus_report) = &self.cus_report {
let solana_version = get_solana_version();
mollusk_svm_bencher::result::write_results(
&PathBuf::from(&cus_report.path),
&cus_report.table_header,
&solana_version,
bench_results,
);
}
Ok(())
}
}
fn parse_fixture_name(fixture_path: &str) -> &str {
fixture_path
.rsplit_once('/')
.map_or(fixture_path, |(_, name)| name)
.split_once('.')
.map_or_else(|| fixture_path, |(name, _)| name)
}