use bssh::executor::{ExecutionResult, ParallelExecutor};
use bssh::node::Node;
use bssh::ssh::client::CommandResult;
use bssh::ssh::known_hosts::StrictHostKeyChecking;
use serial_test::serial;
fn 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 failure_result(host: &str, exit_code: u32) -> 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,
}),
is_main_rank: false,
}
}
fn error_result(host: &str, error_msg: &str) -> ExecutionResult {
ExecutionResult {
node: Node::new(host.to_string(), 22, "user".to_string()),
result: Err(anyhow::anyhow!("{}", error_msg)),
is_main_rank: false,
}
}
#[test]
#[serial]
fn test_fail_fast_builder_method() {
let nodes = vec![
Node::new("host1".to_string(), 22, "user".to_string()),
Node::new("host2".to_string(), 22, "user".to_string()),
];
let _executor = ParallelExecutor::new(nodes.clone(), 10, None);
let _executor = ParallelExecutor::new(nodes.clone(), 10, None).with_fail_fast(true);
let _executor = ParallelExecutor::new(nodes, 10, None).with_fail_fast(false);
}
#[test]
#[serial]
fn test_fail_fast_all_options_constructor() {
let nodes = vec![Node::new("host1".to_string(), 22, "user".to_string())];
let _executor = ParallelExecutor::new(nodes.clone(), 10, None).with_fail_fast(true);
let _executor =
ParallelExecutor::new_with_strict_mode(nodes.clone(), 10, None, StrictHostKeyChecking::Yes)
.with_fail_fast(true);
let _executor = ParallelExecutor::new_with_strict_mode_and_agent(
nodes.clone(),
10,
None,
StrictHostKeyChecking::Yes,
true,
)
.with_fail_fast(true);
let _executor = ParallelExecutor::new_with_all_options(
nodes,
10,
None,
StrictHostKeyChecking::Yes,
true,
false,
)
.with_fail_fast(true);
}
#[test]
#[serial]
fn test_fail_fast_result_classification() {
let result = success_result("host1");
assert!(result.is_success(), "Exit code 0 should be success");
let result = failure_result("host1", 1);
assert!(!result.is_success(), "Exit code 1 should be failure");
let result = failure_result("host1", 137);
assert!(
!result.is_success(),
"Exit code 137 (OOM) should be failure"
);
let result = failure_result("host1", 139);
assert!(
!result.is_success(),
"Exit code 139 (SIGSEGV) should be failure"
);
let result = error_result("host1", "Connection refused");
assert!(
!result.is_success(),
"Connection error should be classified as failure"
);
}
#[test]
#[serial]
fn test_fail_fast_exit_code_extraction() {
let result = success_result("host1");
assert_eq!(
result.get_exit_code(),
0,
"Success should return exit code 0"
);
let result = failure_result("host1", 42);
assert_eq!(result.get_exit_code(), 42, "Should return actual exit code");
let result = error_result("host1", "Connection error");
assert_eq!(
result.get_exit_code(),
1,
"Connection error should return exit code 1"
);
}
#[test]
#[serial]
fn test_fail_fast_with_require_all_success_interaction() {
let results = [
success_result("host1"),
failure_result("host2", 1),
error_result("host3", "Execution cancelled due to fail-fast"),
];
let has_any_failure = results.iter().any(|r| !r.is_success());
assert!(
has_any_failure,
"Should detect failure in results even with cancelled tasks"
);
}
#[test]
#[serial]
fn test_cancellation_error_message() {
let result = error_result("host1", "Execution cancelled due to fail-fast");
if let Err(e) = &result.result {
let msg = format!("{}", e);
assert!(
msg.contains("fail-fast"),
"Cancellation error should mention fail-fast: {msg}"
);
} else {
panic!("Expected error result for cancelled task");
}
}
#[test]
#[serial]
fn test_cli_fail_fast_flag_parsing() {
use bssh::cli::Cli;
use clap::Parser;
let args = ["bssh", "-H", "host1,host2", "-k", "echo test"];
let cli = Cli::try_parse_from(args).expect("Should parse with -k flag");
assert!(cli.fail_fast, "Short flag -k should set fail_fast=true");
let args = ["bssh", "-H", "host1,host2", "--fail-fast", "echo test"];
let cli = Cli::try_parse_from(args).expect("Should parse with --fail-fast flag");
assert!(
cli.fail_fast,
"Long flag --fail-fast should set fail_fast=true"
);
let args = ["bssh", "-H", "host1,host2", "echo test"];
let cli = Cli::try_parse_from(args).expect("Should parse without fail-fast flag");
assert!(!cli.fail_fast, "Default should be fail_fast=false");
}
#[test]
#[serial]
fn test_fail_fast_with_parallelism() {
let nodes = vec![
Node::new("host1".to_string(), 22, "user".to_string()),
Node::new("host2".to_string(), 22, "user".to_string()),
Node::new("host3".to_string(), 22, "user".to_string()),
Node::new("host4".to_string(), 22, "user".to_string()),
Node::new("host5".to_string(), 22, "user".to_string()),
];
let _executor = ParallelExecutor::new(nodes.clone(), 10, None).with_fail_fast(true);
let _executor = ParallelExecutor::new(nodes.clone(), 2, None).with_fail_fast(true);
let _executor = ParallelExecutor::new(nodes, 1, None).with_fail_fast(true);
}
#[test]
#[serial]
fn test_fail_fast_flag_combinations() {
use bssh::cli::Cli;
use clap::Parser;
let args = [
"bssh",
"-H",
"host1,host2",
"--fail-fast",
"--require-all-success",
"echo test",
];
let cli = Cli::try_parse_from(args).expect("Should parse with both flags");
assert!(cli.fail_fast);
assert!(cli.require_all_success);
let args = [
"bssh",
"-H",
"host1,host2",
"--fail-fast",
"--check-all-nodes",
"echo test",
];
let cli = Cli::try_parse_from(args).expect("Should parse with fail-fast and check-all-nodes");
assert!(cli.fail_fast);
assert!(cli.check_all_nodes);
let args = ["bssh", "-H", "host1,host2", "-k", "-v", "echo test"];
let cli = Cli::try_parse_from(args).expect("Should parse with fail-fast and verbose");
assert!(cli.fail_fast);
assert_eq!(cli.verbose, 1);
let args = [
"bssh",
"-H",
"host1,host2",
"-k",
"--timeout",
"60",
"echo test",
];
let cli = Cli::try_parse_from(args).expect("Should parse with fail-fast and timeout");
assert!(cli.fail_fast);
assert_eq!(cli.timeout, Some(60));
}
#[test]
#[serial]
fn test_k_flag_no_conflict() {
use bssh::cli::Cli;
use clap::Parser;
let args = ["bssh", "-H", "host1", "-k", "uptime"];
let result = Cli::try_parse_from(args);
assert!(result.is_ok(), "-k should be a valid flag");
let cli = result.unwrap();
assert!(cli.fail_fast, "-k should set fail_fast=true");
}