bcore_mutation/
commands.rs1use crate::error::{MutationError, Result};
13use crate::project::Project;
14use std::path::Path;
15
16pub trait ProjectCommands {
18 fn build_command(&self) -> String;
21
22 fn build_timeout_secs(&self) -> u64 {
24 3600 }
26
27 fn test_command(&self, target_file_path: &str, jobs: u32) -> Result<String>;
30}
31
32pub fn for_project(project: Project) -> Box<dyn ProjectCommands> {
34 match project {
35 Project::BitcoinCore => Box::new(BitcoinCore),
36 Project::Secp256k1 => Box::new(Secp256k1),
37 }
38}
39
40fn with_jobs(mut build: String, jobs: u32) -> String {
42 if jobs > 0 {
43 build.push_str(&format!(" -j{}", jobs));
44 }
45 build
46}
47
48pub struct BitcoinCore;
49
50impl ProjectCommands for BitcoinCore {
51 fn build_command(&self) -> String {
52 "rm -rf build && cmake -B build -DENABLE_IPC=OFF && cmake --build build -j $(nproc)"
53 .to_string()
54 }
55
56 fn test_command(&self, target_file_path: &str, jobs: u32) -> Result<String> {
57 let build_command = with_jobs("cmake --build build".to_string(), jobs);
58
59 let command = if target_file_path.contains("functional") {
60 format!("./build/{}", target_file_path)
61 } else if target_file_path.contains("test") {
62 let filename_with_extension = Path::new(target_file_path)
63 .file_name()
64 .and_then(|n| n.to_str())
65 .ok_or_else(|| MutationError::InvalidInput("Invalid file path".to_string()))?;
66
67 let test_to_run = filename_with_extension
68 .rsplit('.')
69 .nth(1)
70 .ok_or_else(|| {
71 MutationError::InvalidInput("Cannot extract test name".to_string())
72 })?;
73
74 format!(
75 "{} && ./build/bin/test_bitcoin --run_test={}",
76 build_command, test_to_run
77 )
78 } else {
79 format!(
80 "{} && ctest --output-on-failure --stop-on-failure -C Release && CI_FAILFAST_TEST_LEAVE_DANGLING=1 ./build/test/functional/test_runner.py -F",
81 build_command
82 )
83 };
84
85 Ok(command)
86 }
87}
88
89pub struct Secp256k1;
90
91impl ProjectCommands for Secp256k1 {
92 fn build_command(&self) -> String {
93 "rm -rf build && cmake -B build && cmake --build build -j $(nproc)".to_string()
97 }
98
99 fn test_command(&self, _target_file_path: &str, jobs: u32) -> Result<String> {
100 let build_command = with_jobs("cmake --build build".to_string(), jobs);
104 Ok(format!(
105 "{} && ctest --test-dir build --output-on-failure",
106 build_command
107 ))
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_bitcoin_core_test_command() {
117 let cmds = BitcoinCore;
118
119 let cmd = cmds
121 .test_command("test/functional/test_example.py", 4)
122 .unwrap();
123 assert_eq!(cmd, "./build/test/functional/test_example.py");
124
125 let cmd = cmds.test_command("src/test/test_example.cpp", 0).unwrap();
127 assert_eq!(
128 cmd,
129 "cmake --build build && ./build/bin/test_bitcoin --run_test=test_example"
130 );
131
132 let cmd = cmds.test_command("src/wallet/wallet.cpp", 2).unwrap();
134 assert!(cmd.contains("cmake --build build -j2"));
135 assert!(cmd.contains("ctest"));
136 assert!(cmd.contains("test_runner.py"));
137 }
138
139 #[test]
140 fn test_secp256k1_test_command_runs_ctest() {
141 let cmds = Secp256k1;
142 let cmd = cmds.test_command("src/field_impl.h", 3).unwrap();
143 assert!(cmd.contains("cmake --build build -j3"));
144 assert!(cmd.contains("ctest --test-dir build"));
145 assert!(!cmd.contains("test_bitcoin"));
147 assert!(!cmd.contains("test_runner.py"));
148 }
149
150 #[test]
151 fn test_build_commands_differ() {
152 assert!(BitcoinCore.build_command().contains("-DENABLE_IPC=OFF"));
153 assert!(!Secp256k1.build_command().contains("-DENABLE_IPC=OFF"));
154 }
155}