Skip to content

Commit

Permalink
Merge pull request #109 from Ackee-Blockchain/feat/fuzz-directory
Browse files Browse the repository at this point in the history
Improved trdelnik-tests folder structure for PoC and Fuzz Tests
  • Loading branch information
Ikrk authored Jan 29, 2024
2 parents 19b3f55 + 7230fa0 commit 3b52ac8
Show file tree
Hide file tree
Showing 73 changed files with 1,172 additions and 888 deletions.
2 changes: 1 addition & 1 deletion crates/cli/src/command/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ pub async fn build(root: String) {
commander.create_program_client_crate().await?;
commander.build_programs().await?;
commander.generate_program_client_deps().await?;
commander.generate_program_client_lib_rs().await?;
commander.generate_program_client_lib_rs(None).await?;
}
264 changes: 8 additions & 256 deletions crates/cli/src/command/fuzz.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
use std::{
env,
path::{Path, PathBuf},
process,
};

use anyhow::{bail, Context, Error, Result};

use clap::Subcommand;
use fehler::throws;
use trdelnik_client::Commander;

pub const TESTS_WORKSPACE: &str = "trdelnik-tests";
pub const HFUZZ_WORKSPACE: &str = "hfuzz_workspace";
use trdelnik_client::{Commander, TestGenerator};

#[derive(Subcommand)]
#[allow(non_camel_case_types)]
Expand All @@ -31,6 +22,7 @@ pub enum FuzzCommand {
/// Path to the crash file
crash_file_path: String,
},
Add,
}

#[throws]
Expand All @@ -55,26 +47,7 @@ pub async fn fuzz(root: Option<String>, subcmd: FuzzCommand) {
with_exit_code,
} => {
if with_exit_code {
// Parse the HFUZZ run arguments to find out if the crash folder and crash files extension was modified.
let hfuzz_run_args = env::var("HFUZZ_RUN_ARGS").unwrap_or_default();
let (crash_dir, ext) = get_crash_dir_and_ext(&root, &target, &hfuzz_run_args);

if let Ok(crash_files) = get_crash_files(&crash_dir, &ext) {
if !crash_files.is_empty() {
println!("Error: The crash directory {} already contains crash files from previous runs. \n\nTo run Trdelnik fuzzer with exit code, you must either (backup and) remove the old crash files or alternatively change the crash folder using for example the --crashdir option and the HFUZZ_RUN_ARGS env variable such as:\nHFUZZ_RUN_ARGS=\"--crashdir ./new_crash_dir\"", crash_dir.to_string_lossy());
process::exit(1);
}
}
commander.run_fuzzer(target).await?;
if let Ok(crash_files) = get_crash_files(&crash_dir, &ext) {
if !crash_files.is_empty() {
println!(
"The crash directory {} contains new fuzz test crashes. Exiting!",
crash_dir.to_string_lossy()
);
process::exit(1);
}
}
commander.run_fuzzer_with_exit_code(target).await?;
} else {
commander.run_fuzzer(target).await?;
}
Expand All @@ -85,6 +58,11 @@ pub async fn fuzz(root: Option<String>, subcmd: FuzzCommand) {
} => {
commander.run_fuzzer_debug(target, crash_file_path).await?;
}

FuzzCommand::Add => {
let generator = TestGenerator::new();
generator.add_new_fuzz_test().await?;
}
};
}

Expand Down Expand Up @@ -114,229 +92,3 @@ fn _discover() -> Result<Option<String>> {

Ok(None)
}

fn get_crash_dir_and_ext(root: &str, target: &str, hfuzz_run_args: &str) -> (PathBuf, String) {
// FIXME: we split by whitespace without respecting escaping or quotes - same approach as honggfuzz-rs so there is no point to fix it here before the upstream is fixed
let hfuzz_run_args = hfuzz_run_args.split_whitespace();

let extension =
get_cmd_option_value(hfuzz_run_args.clone(), "-e", "--ext").unwrap_or("fuzz".to_string());

let crash_dir = get_cmd_option_value(hfuzz_run_args.clone(), "", "--cr")
.or_else(|| get_cmd_option_value(hfuzz_run_args.clone(), "-W", "--w"));

let crash_path = if let Some(dir) = crash_dir {
Path::new(root).join(TESTS_WORKSPACE).join(dir)
} else {
Path::new(root)
.join(TESTS_WORKSPACE)
.join(HFUZZ_WORKSPACE)
.join(target)
};

(crash_path, extension)
}

fn get_cmd_option_value<'a>(
hfuzz_run_args: impl Iterator<Item = &'a str>,
short_opt: &str,
long_opt: &str,
) -> Option<String> {
let mut args_iter = hfuzz_run_args;
let mut value: Option<String> = None;

// ensure short option starts with one dash and long option with two dashes
let short_opt = format!("-{}", short_opt.trim_start_matches('-'));
let long_opt = format!("--{}", long_opt.trim_start_matches('-'));

while let Some(arg) = args_iter.next() {
match arg.strip_prefix(&short_opt) {
Some(val) if short_opt.len() > 1 => {
if !val.is_empty() {
// -ecrash for crash extension with no space
value = Some(val.to_string());
} else if let Some(next_arg) = args_iter.next() {
// -e crash for crash extension with space
value = Some(next_arg.to_string());
} else {
value = None;
}
}
_ => {
if arg.starts_with(&long_opt) && long_opt.len() > 2 {
value = args_iter.next().map(|a| a.to_string());
}
}
}
}

value
}

fn get_crash_files(
dir: &PathBuf,
extension: &str,
) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
let paths = std::fs::read_dir(dir)?
// Filter out all those directory entries which couldn't be read
.filter_map(|res| res.ok())
// Map the directory entries to paths
.map(|dir_entry| dir_entry.path())
// Filter out all paths with extensions other than `extension`
.filter_map(|path| {
if path.extension().map_or(false, |ext| ext == extension) {
Some(path)
} else {
None
}
})
.collect::<Vec<_>>();
Ok(paths)
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cmd_options_parsing() {
let mut command = String::from("-Q -v --extension fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q --extension fuzz -v");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -e fuzz -v");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q --extension fuzz -v --extension ");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

command = String::from("-Q --extension fuzz -v -e ");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

let mut command = String::from("--extension buzz -e fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v -e fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v -efuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v --ext fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v --extfuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

command = String::from("-Q -v --workspace");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

command = String::from("-Q -v -e");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "", "--ext");
assert_eq!(extension, None);

command = String::from("-Q -v --ext fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "");
assert_eq!(extension, None);
}

#[test]
fn test_get_crash_dir_and_ext() {
let root = "/home/fuzz";
let target = "target";
let default_crash_path = Path::new(root)
.join(TESTS_WORKSPACE)
.join(HFUZZ_WORKSPACE)
.join(target);

let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "");

assert_eq!(crash_dir, default_crash_path);
assert_eq!(&ext, "fuzz");

let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -e");

assert_eq!(crash_dir, default_crash_path);
assert_eq!(&ext, "fuzz");

let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -e crash");

assert_eq!(crash_dir, default_crash_path);
assert_eq!(&ext, "crash");

// test absolute path
let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -W /home/crash -e crash");

let expected_crash_path = Path::new("/home/crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// test absolute path
let (crash_dir, ext) =
get_crash_dir_and_ext(root, target, "-Q --crash /home/crash -e crash");

let expected_crash_path = Path::new("/home/crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// test relative path
let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -W ../crash -e crash");

let expected_crash_path = Path::new(root).join(TESTS_WORKSPACE).join("../crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// test relative path
let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q --crash ../crash -e crash");

let expected_crash_path = Path::new(root).join(TESTS_WORKSPACE).join("../crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// crash directory has precedence before workspace
let (crash_dir, ext) =
get_crash_dir_and_ext(root, target, "-Q --crash ../crash -W /workspace -e crash");

let expected_crash_path = Path::new(root).join(TESTS_WORKSPACE).join("../crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");
}
}
1 change: 1 addition & 0 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ shellexpand = { workspace = true }
trdelnik-derive-displayix = { path = "./derive/display_ix" }
trdelnik-derive-fuzz-deserialize = { path = "./derive/fuzz_deserialize" }
trdelnik-derive-fuzz-test-executor = { path = "./derive/fuzz_test_executor" }
pathdiff="0.2.1"
6 changes: 5 additions & 1 deletion crates/client/src/cleaner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@ impl Cleaner {
async fn clean_hfuzz_target(&self, root: &PathBuf) {
let hfuzz_target_path = Path::new(root)
.join(crate::test_generator::TESTS_WORKSPACE)
.join(crate::test_generator::FUZZ_TEST_DIRECTORY)
.join(crate::test_generator::FUZZING)
.join(crate::test_generator::HFUZZ_TARGET);
if hfuzz_target_path.exists() {
fs::remove_dir_all(hfuzz_target_path).await?;
} else {
println!(
"skipping {}/{} directory: not found",
"skipping {}/{}/{}/{} directory: not found",
crate::test_generator::TESTS_WORKSPACE,
crate::test_generator::FUZZ_TEST_DIRECTORY,
crate::test_generator::FUZZING,
crate::test_generator::HFUZZ_TARGET
)
}
Expand Down
Loading

0 comments on commit 3b52ac8

Please sign in to comment.