use crate::error::{MutationError, Result};
use crate::project::Project;
use std::path::Path;
pub trait ProjectCommands {
fn build_command(&self) -> String;
fn build_timeout_secs(&self) -> u64 {
3600 }
fn test_command(&self, target_file_path: &str, jobs: u32) -> Result<String>;
}
pub fn for_project(project: Project) -> Box<dyn ProjectCommands> {
match project {
Project::BitcoinCore => Box::new(BitcoinCore),
Project::Secp256k1 => Box::new(Secp256k1),
}
}
fn with_jobs(mut build: String, jobs: u32) -> String {
if jobs > 0 {
build.push_str(&format!(" -j{}", jobs));
}
build
}
pub struct BitcoinCore;
impl ProjectCommands for BitcoinCore {
fn build_command(&self) -> String {
"rm -rf build && cmake -B build -DENABLE_IPC=OFF && cmake --build build -j $(nproc)"
.to_string()
}
fn test_command(&self, target_file_path: &str, jobs: u32) -> Result<String> {
let build_command = with_jobs("cmake --build build".to_string(), jobs);
let command = if target_file_path.contains("functional") {
format!("./build/{}", target_file_path)
} else if target_file_path.contains("test") {
let filename_with_extension = Path::new(target_file_path)
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| MutationError::InvalidInput("Invalid file path".to_string()))?;
let test_to_run = filename_with_extension
.rsplit('.')
.nth(1)
.ok_or_else(|| {
MutationError::InvalidInput("Cannot extract test name".to_string())
})?;
format!(
"{} && ./build/bin/test_bitcoin --run_test={}",
build_command, test_to_run
)
} else {
format!(
"{} && ctest --output-on-failure --stop-on-failure -C Release && CI_FAILFAST_TEST_LEAVE_DANGLING=1 ./build/test/functional/test_runner.py -F",
build_command
)
};
Ok(command)
}
}
pub struct Secp256k1;
impl ProjectCommands for Secp256k1 {
fn build_command(&self) -> String {
"rm -rf build && cmake -B build && cmake --build build -j $(nproc)".to_string()
}
fn test_command(&self, _target_file_path: &str, jobs: u32) -> Result<String> {
let build_command = with_jobs("cmake --build build".to_string(), jobs);
Ok(format!(
"{} && ctest --test-dir build --output-on-failure",
build_command
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bitcoin_core_test_command() {
let cmds = BitcoinCore;
let cmd = cmds
.test_command("test/functional/test_example.py", 4)
.unwrap();
assert_eq!(cmd, "./build/test/functional/test_example.py");
let cmd = cmds.test_command("src/test/test_example.cpp", 0).unwrap();
assert_eq!(
cmd,
"cmake --build build && ./build/bin/test_bitcoin --run_test=test_example"
);
let cmd = cmds.test_command("src/wallet/wallet.cpp", 2).unwrap();
assert!(cmd.contains("cmake --build build -j2"));
assert!(cmd.contains("ctest"));
assert!(cmd.contains("test_runner.py"));
}
#[test]
fn test_secp256k1_test_command_runs_ctest() {
let cmds = Secp256k1;
let cmd = cmds.test_command("src/field_impl.h", 3).unwrap();
assert!(cmd.contains("cmake --build build -j3"));
assert!(cmd.contains("ctest --test-dir build"));
assert!(!cmd.contains("test_bitcoin"));
assert!(!cmd.contains("test_runner.py"));
}
#[test]
fn test_build_commands_differ() {
assert!(BitcoinCore.build_command().contains("-DENABLE_IPC=OFF"));
assert!(!Secp256k1.build_command().contains("-DENABLE_IPC=OFF"));
}
}