pub mod samples;
use crate::shared::samples::Sample;
use anyhow::{
bail,
ensure,
Context,
Result,
};
use assert_cmd::Command;
use phink_lib::cli::config::Configuration;
use assert_cmd::assert::Assert;
use phink_lib::ResultOf;
use std::{
collections::HashSet,
ffi::OsStr,
fs,
io::Read,
path::{
Path,
PathBuf,
},
process::{
Child,
Command as NativeCommand,
Stdio,
},
thread,
time::{
Duration,
Instant,
},
};
pub const DEFAULT_TEST_PHINK_TOML: &str = "phink_temp_test.toml";
pub fn with_modified_phink_config<F>(config: &Configuration, executed_test: F) -> Result<()>
where
F: FnOnce() -> Result<()>,
{
try_cleanup_instrumented(config);
try_cleanup_fuzzoutput(config);
config.save_as_toml(DEFAULT_TEST_PHINK_TOML)?;
let test_result = executed_test();
ensure!(
PathBuf::from(DEFAULT_TEST_PHINK_TOML).exists(),
"{DEFAULT_TEST_PHINK_TOML} doesn't exist"
);
let _ = fs::remove_file(DEFAULT_TEST_PHINK_TOML);
try_cleanup_instrumented(config);
try_cleanup_fuzzoutput(config);
test_result
}
#[allow(clippy::zombie_processes)]
pub fn ensure_while_fuzzing<F>(
config: &Configuration,
timeout: Duration,
mut executed_test: F,
) -> Result<()>
where
F: FnMut() -> Result<()>,
{
let mut child = fuzz_test(config.verbose, DEFAULT_TEST_PHINK_TOML)?;
let start_time = Instant::now();
loop {
let test_result = executed_test();
if test_result.is_ok() {
println!(
"Fuzzing test passed in {} seconds",
start_time.elapsed().as_secs()
);
child.kill().with_context(|| "Failed to kill Ziggy")?;
return Ok(())
}
if start_time.elapsed() > timeout {
child.kill().with_context(|| "Failed to kill Ziggy")?;
bail!(
"Couldn't check the assert within the given timeout. Here is the latest error we've got: {:?}", test_result.unwrap_err()
);
}
thread::sleep(Duration::from_secs(1));
}
}
pub fn try_cleanup_instrumented(config: &Configuration) {
let _ = fs::remove_dir_all(
config
.to_owned()
.instrumented_contract_path
.unwrap_or_default()
.path,
);
}
pub fn try_cleanup_fuzzoutput(config: &Configuration) {
let output = config.clone().fuzz_output.unwrap_or_default();
let result = fs::remove_dir_all(&output);
if config.verbose {
match result {
Ok(()) => {
println!("Removed {}", output.display());
}
Err(_) => {
println!("*DIDN'T* removed {}", output.display());
}
};
}
}
#[must_use]
pub fn instrument(contract_path: Sample) -> Assert {
let mut cmd = Command::cargo_bin("phink").unwrap();
cmd.args(["--config", DEFAULT_TEST_PHINK_TOML])
.arg("instrument")
.arg(contract_path.path())
.assert()
.success()
}
pub fn fuzz_test(verbose: bool, phink_toml: &str) -> Result<Child> {
let stdio_stdout = if verbose {
Stdio::inherit()
} else {
Stdio::null()
};
let stdio_stderr = if verbose {
Stdio::inherit()
} else {
Stdio::null()
};
let child = NativeCommand::new("cargo")
.arg("run")
.arg("--")
.args(["--config", phink_toml])
.arg("fuzz")
.stdout(stdio_stdout)
.stderr(stdio_stderr)
.spawn();
match child {
Ok(child) => Ok(child),
Err(e) => {
bail!(format!("{e:?}"))
}
}
}
#[must_use]
fn find_string_in_rs_files(dir: &Path, matching_string: &str) -> bool {
fn file_contains_string(file_path: &Path, target: &str) -> bool {
let mut file = fs::File::open(file_path).expect("Unable to open file");
let mut content = String::new();
file.read_to_string(&mut content)
.expect("Unable to read file");
content.contains(target)
}
for entry in fs::read_dir(dir).unwrap() {
let entry = entry.expect("Unable to get directory entry");
let path = entry.path();
if path.is_dir() {
if find_string_in_rs_files(&path, matching_string) {
return true;
}
} else if path.extension() == Some(OsStr::new("rs"))
&& file_contains_string(&path, matching_string)
{
return true;
}
}
false
}
pub fn is_instrumented(path_instrumented_contract: &Path) -> bool {
find_string_in_rs_files(
path_instrumented_contract,
"ink::env::debug_println!(\"COV={}\",",
)
}
pub fn is_compiled(path_instrumented_contract: &Path) -> bool {
let target_dir = path_instrumented_contract.join("target");
if !target_dir.exists() {
return false
}
target_dir.join("debug").exists() || target_dir.join("release").exists()
}
pub fn get_corpus_files(corpus_path: &PathBuf) -> ResultOf<HashSet<PathBuf>> {
let entries = fs::read_dir(corpus_path).context(format!("Can't read {corpus_path:?}"))?;
let paths: Vec<_> = entries
.filter_map(|entry| entry.ok().map(|e| e.path()))
.collect();
let i = paths.len();
println!("Could read {corpus_path:?} containing {i:?} files");
let corpus_files: HashSet<PathBuf> = paths.into_iter().collect();
println!(
"Got {} corpus files in {:?}",
corpus_files.len(),
corpus_path.canonicalize()?.to_str().unwrap()
);
Ok(corpus_files)
}