use crate::executor::result_types::ExecutionResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitCodeStrategy {
MainRank,
RequireAllSuccess,
MainRankWithFailureCheck,
}
impl ExitCodeStrategy {
pub fn calculate(&self, results: &[ExecutionResult], main_idx: Option<usize>) -> i32 {
match self {
Self::MainRank => {
main_idx
.and_then(|i| results.get(i))
.map(|r| r.get_exit_code())
.unwrap_or(1) }
Self::RequireAllSuccess => {
if results.iter().any(|r| !r.is_success()) {
1
} else {
0
}
}
Self::MainRankWithFailureCheck => {
let main_code = main_idx
.and_then(|i| results.get(i))
.map(|r| r.get_exit_code())
.unwrap_or(0);
let other_failed = results
.iter()
.enumerate()
.any(|(i, r)| Some(i) != main_idx && !r.is_success());
if main_code != 0 {
main_code } else if other_failed {
1 } else {
0 }
}
}
}
}
impl Default for ExitCodeStrategy {
fn default() -> Self {
Self::MainRank
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::Node;
use crate::ssh::client::CommandResult;
use anyhow::anyhow;
fn create_success_result(host: &str) -> ExecutionResult {
ExecutionResult {
node: Node::new(host.to_string(), 22, "user".to_string()),
result: Ok(CommandResult {
host: host.to_string(),
output: Vec::new(),
stderr: Vec::new(),
exit_status: 0,
}),
is_main_rank: false, }
}
fn create_failure_result(host: &str, exit_code: i32) -> ExecutionResult {
ExecutionResult {
node: Node::new(host.to_string(), 22, "user".to_string()),
result: Ok(CommandResult {
host: host.to_string(),
output: Vec::new(),
stderr: Vec::new(),
exit_status: exit_code as u32,
}),
is_main_rank: false, }
}
fn create_error_result(host: &str) -> ExecutionResult {
ExecutionResult {
node: Node::new(host.to_string(), 22, "user".to_string()),
result: Err(anyhow!("Connection failed")),
is_main_rank: false, }
}
#[test]
fn test_default_strategy_is_main_rank() {
assert_eq!(ExitCodeStrategy::default(), ExitCodeStrategy::MainRank);
}
#[test]
fn test_main_rank_all_success() {
let results = vec![
create_success_result("host1"),
create_success_result("host2"),
create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, Some(0));
assert_eq!(exit_code, 0);
}
#[test]
fn test_main_rank_main_failed_with_segfault() {
let results = vec![
create_failure_result("host1", 139), create_success_result("host2"),
create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, Some(0));
assert_eq!(exit_code, 139); }
#[test]
fn test_main_rank_main_ok_other_failed() {
let results = vec![
create_success_result("host1"),
create_failure_result("host2", 1),
create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, Some(0));
assert_eq!(exit_code, 0); }
#[test]
fn test_main_rank_all_failed() {
let results = vec![
create_failure_result("host1", 137), create_failure_result("host2", 1),
create_failure_result("host3", 1),
];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, Some(0));
assert_eq!(exit_code, 137); }
#[test]
fn test_main_rank_no_main_identified() {
let results = vec![
create_success_result("host1"),
create_success_result("host2"),
];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, None);
assert_eq!(exit_code, 1); }
#[test]
fn test_main_rank_with_connection_error() {
let results = vec![create_error_result("host1"), create_success_result("host2")];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, Some(0));
assert_eq!(exit_code, 1); }
#[test]
fn test_require_all_success_all_ok() {
let results = vec![
create_success_result("host1"),
create_success_result("host2"),
create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::RequireAllSuccess.calculate(&results, Some(0));
assert_eq!(exit_code, 0);
}
#[test]
fn test_require_all_success_one_failed() {
let results = vec![
create_success_result("host1"),
create_failure_result("host2", 139), create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::RequireAllSuccess.calculate(&results, Some(0));
assert_eq!(exit_code, 1); }
#[test]
fn test_require_all_success_all_failed() {
let results = vec![
create_failure_result("host1", 139),
create_failure_result("host2", 137),
create_failure_result("host3", 1),
];
let exit_code = ExitCodeStrategy::RequireAllSuccess.calculate(&results, Some(0));
assert_eq!(exit_code, 1);
}
#[test]
fn test_require_all_success_with_error() {
let results = vec![create_success_result("host1"), create_error_result("host2")];
let exit_code = ExitCodeStrategy::RequireAllSuccess.calculate(&results, Some(0));
assert_eq!(exit_code, 1);
}
#[test]
fn test_hybrid_all_success() {
let results = vec![
create_success_result("host1"),
create_success_result("host2"),
create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::MainRankWithFailureCheck.calculate(&results, Some(0));
assert_eq!(exit_code, 0);
}
#[test]
fn test_hybrid_main_failed() {
let results = vec![
create_failure_result("host1", 139),
create_success_result("host2"),
create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::MainRankWithFailureCheck.calculate(&results, Some(0));
assert_eq!(exit_code, 139); }
#[test]
fn test_hybrid_main_ok_other_failed() {
let results = vec![
create_success_result("host1"),
create_failure_result("host2", 137),
create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::MainRankWithFailureCheck.calculate(&results, Some(0));
assert_eq!(exit_code, 1); }
#[test]
fn test_hybrid_all_failed() {
let results = vec![
create_failure_result("host1", 139),
create_failure_result("host2", 137),
create_failure_result("host3", 1),
];
let exit_code = ExitCodeStrategy::MainRankWithFailureCheck.calculate(&results, Some(0));
assert_eq!(exit_code, 139); }
#[test]
fn test_hybrid_no_main_all_ok() {
let results = vec![
create_success_result("host1"),
create_success_result("host2"),
];
let exit_code = ExitCodeStrategy::MainRankWithFailureCheck.calculate(&results, None);
assert_eq!(exit_code, 0); }
#[test]
fn test_hybrid_no_main_with_failures() {
let results = vec![
create_success_result("host1"),
create_failure_result("host2", 1),
];
let exit_code = ExitCodeStrategy::MainRankWithFailureCheck.calculate(&results, None);
assert_eq!(exit_code, 1); }
#[test]
fn test_main_rank_non_zero_index() {
let results = vec![
create_success_result("host1"),
create_failure_result("host2", 124), create_success_result("host3"),
];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, Some(1));
assert_eq!(exit_code, 124);
}
#[test]
fn test_empty_results() {
let results: Vec<ExecutionResult> = vec![];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, None);
assert_eq!(exit_code, 1);
let exit_code = ExitCodeStrategy::RequireAllSuccess.calculate(&results, None);
assert_eq!(exit_code, 0);
let exit_code = ExitCodeStrategy::MainRankWithFailureCheck.calculate(&results, None);
assert_eq!(exit_code, 0);
}
#[test]
fn test_large_exit_code() {
let large_exit_code = u32::MAX; let results = vec![ExecutionResult {
node: Node::new("host1".to_string(), 22, "user".to_string()),
result: Ok(CommandResult {
host: "host1".to_string(),
output: Vec::new(),
stderr: Vec::new(),
exit_status: large_exit_code,
}),
is_main_rank: true,
}];
let exit_code = ExitCodeStrategy::MainRank.calculate(&results, Some(0));
assert_eq!(exit_code, i32::MAX); }
}