use slotgate::execution::job::Job;
use slotgate::execution::job_runner::JobRunner;
use slotgate::execution::job_status::JobStatus;
use slotgate::ports::port_range::PortRange;
use std::env::temp_dir;
use std::fs;
use std::time::Duration;
use std::time::Instant;
fn temp_log_dir(test_name: &str) -> std::path::PathBuf {
let dir = temp_dir().join(format!("slotgate_job_runner_tests_{test_name}"));
let _ = fs::remove_dir_all(&dir);
dir
}
#[tokio::test]
async fn run_a_command_that_exits_nonzero_reports_failed() {
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_secs(10),
temp_log_dir("exits_nonzero"),
);
let job = Job {
name: String::from("fails"),
program: String::from("cmd.exe"),
args: vec![String::from("/C"), String::from("exit 1")],
};
let port_range = PortRange {
base: 31010,
count: 10,
};
let outcome = runner.run(&job, &port_range).await;
assert_eq!(outcome.status, JobStatus::Failed);
}
#[tokio::test]
async fn run_a_command_that_exits_zero_reports_passed() {
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_secs(10),
temp_log_dir("exits_zero"),
);
let job = Job {
name: String::from("succeeds"),
program: String::from("cmd.exe"),
args: vec![String::from("/C"), String::from("exit 0")],
};
let port_range = PortRange {
base: 31000,
count: 10,
};
let outcome = runner.run(&job, &port_range).await;
assert_eq!(outcome.status, JobStatus::Passed);
}
#[tokio::test]
async fn run_a_job_whose_name_contains_double_colons_still_runs_successfully() {
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_secs(10),
temp_log_dir("filesystem_safety"),
);
let job = Job {
name: String::from(
"cluster::byzantine_tests::byzantine_new_view_from_non_proposer_is_rejected",
),
program: String::from("cmd.exe"),
args: vec![String::from("/C"), String::from("exit 0")],
};
let port_range = PortRange {
base: 33000,
count: 10,
};
let outcome = runner.run(&job, &port_range).await;
assert_eq!(outcome.status, JobStatus::Passed);
assert!(outcome.stdout_path.exists());
}
#[tokio::test]
async fn run_captures_stdout_to_the_returned_path() {
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_secs(10),
temp_log_dir("captures_stdout"),
);
let job = Job {
name: String::from("prints_marker"),
program: String::from("cmd.exe"),
args: vec![
String::from("/C"),
String::from("echo distinctive_marker_12345"),
],
};
let port_range = PortRange {
base: 31600,
count: 10,
};
let outcome = runner.run(&job, &port_range).await;
let stdout = fs::read_to_string(&outcome.stdout_path).expect("stdout log should exist");
assert!(stdout.contains("distinctive_marker_12345"));
}
#[tokio::test]
async fn run_enforces_a_timeout_and_reports_timed_out() {
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_millis(300),
temp_log_dir("timeout"),
);
let job = Job {
name: String::from("sleeps_too_long"),
program: String::from("powershell"),
args: vec![
String::from("-Command"),
String::from("Start-Sleep -Seconds 30"),
],
};
let port_range = PortRange {
base: 31700,
count: 10,
};
let started = Instant::now();
let outcome = runner.run(&job, &port_range).await;
let elapsed = started.elapsed();
assert_eq!(outcome.status, JobStatus::TimedOut);
assert!(
elapsed < Duration::from_secs(10),
"timeout enforcement should return well before the job's own 30s sleep, took {elapsed:?}"
);
}
#[tokio::test]
async fn run_injects_port_range_env_vars_readable_by_the_child() {
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_secs(10),
temp_log_dir("env_vars"),
);
let job = Job {
name: String::from("prints_env"),
program: String::from("cmd.exe"),
args: vec![
String::from("/C"),
String::from("echo %PORT_RANGE_BASE% %PORT_RANGE_COUNT%"),
],
};
let port_range = PortRange {
base: 31500,
count: 25,
};
let outcome = runner.run(&job, &port_range).await;
let stdout = fs::read_to_string(&outcome.stdout_path).expect("stdout log should exist");
assert!(stdout.contains("31500"), "stdout was: {stdout}");
assert!(stdout.contains("25"), "stdout was: {stdout}");
}
#[tokio::test]
async fn run_publishes_the_job_log_directory_to_the_child() {
let log_dir = temp_log_dir("publishes_job_log_dir");
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_secs(10),
log_dir.clone(),
);
let job = Job {
name: String::from("cluster::some_tests::a_case"),
program: String::from("cmd.exe"),
args: vec![
String::from("/C"),
String::from("echo %SLOTGATE_JOB_LOG_DIR%"),
],
};
let port_range = PortRange {
base: 31700,
count: 10,
};
let outcome = runner.run(&job, &port_range).await;
let printed = fs::read_to_string(&outcome.stdout_path).expect("stdout log should exist");
let expected = log_dir.join("cluster__some_tests__a_case");
assert_eq!(printed.trim(), expected.to_string_lossy());
}
#[tokio::test]
async fn run_publishes_the_unsanitised_job_name_to_the_child() {
let runner = JobRunner::new(
String::from("PORT_RANGE_BASE"),
String::from("PORT_RANGE_COUNT"),
Duration::from_secs(10),
temp_log_dir("publishes_job_name"),
);
let job = Job {
name: String::from("cluster::some_tests::a_case"),
program: String::from("cmd.exe"),
args: vec![String::from("/C"), String::from("echo %SLOTGATE_JOB_NAME%")],
};
let port_range = PortRange {
base: 31710,
count: 10,
};
let outcome = runner.run(&job, &port_range).await;
let printed = fs::read_to_string(&outcome.stdout_path).expect("stdout log should exist");
assert_eq!(printed.trim(), "cluster::some_tests::a_case");
}