#[cfg(test)]
mod tests {
use crate::aggregator::Status;
use crate::config::AppConfig;
use crate::process_manager::{
spawn_complex_process, spawn_simple_process, ChildLock, SupervisedChild, SupervisedProcess,
};
use crate::state_persistence::AppState;
use crate::timestamp::current_timestamp;
use dusa_collection_utils::core::errors::Errors;
use dusa_collection_utils::core::types::pathtype::PathType;
use dusa_collection_utils::core::version::SoftwareVersion;
use nix::unistd::Pid;
use std::path::PathBuf;
use std::time::Duration;
use tokio::process::Command;
#[tokio::test]
async fn test_supervised_child_spawn_and_kill() {
let mut cmd = Command::new("sleep");
cmd.arg("5");
let mut supervised_child = SupervisedChild::new(&mut cmd, None)
.await
.expect("Failed to spawn supervised child");
assert!(supervised_child.running().await, "Child should be running");
supervised_child
.kill()
.await
.expect("Failed to kill process");
assert!(
!supervised_child.running().await,
"Child should not be running"
);
}
#[tokio::test]
async fn test_supervised_child_clone() {
let mut cmd = Command::new("sleep");
cmd.arg("5");
let mut original = SupervisedChild::new(&mut cmd, None)
.await
.expect("Failed to spawn child");
let cloned = original.clone().await;
let orig_pid = original.get_pid().await.unwrap();
let clone_pid = cloned.get_pid().await.unwrap();
assert_eq!(orig_pid, clone_pid, "Cloned child must have same PID");
original.kill().await.expect("Failed to kill process");
}
#[tokio::test]
async fn test_supervised_child_monitor_usage() {
let mut cmd = Command::new("sleep");
cmd.arg("2");
let mut supervised_child = SupervisedChild::new(&mut cmd, None)
.await
.expect("Failed to spawn supervised child");
supervised_child.monitor_usage().await;
tokio::time::sleep(Duration::from_millis(500)).await;
let metrics = supervised_child
.get_metrics()
.await
.expect("Failed to retrieve metrics");
println!("Metrics from child: {:?}", metrics);
supervised_child.kill().await.unwrap();
}
#[tokio::test]
async fn test_supervised_child_stdx_monitor_does_not_stop_resource_monitor() {
let mut cmd = Command::new("sh");
cmd.arg("-c").arg("while :; do :; done");
let mut supervised_child = SupervisedChild::new(&mut cmd, None)
.await
.expect("Failed to spawn supervised child");
supervised_child.monitor_usage().await;
assert!(
supervised_child.monitoring(),
"Resource monitor should be running"
);
supervised_child.monitor_stdx().await;
assert!(
supervised_child.monitoring(),
"Resource monitor should still be running after starting stdx monitor"
);
assert!(
supervised_child.monitoring_stdx(),
"Stdx monitor should be running"
);
supervised_child.terminate_stdx();
assert!(
!supervised_child.monitoring_stdx(),
"Stdx monitor should be stoppable"
);
supervised_child.kill().await.unwrap();
}
#[tokio::test]
async fn test_supervised_child_watchdogs_attach_and_start() {
let mut cmd = Command::new("sleep");
cmd.arg("2");
let mut supervised_child = SupervisedChild::new(&mut cmd, None)
.await
.expect("Failed to spawn supervised child");
let initial_resource = supervised_child.resource_watchdog_snapshot();
let initial_stdx = supervised_child.stdx_watchdog_snapshot();
assert_eq!(initial_resource.start_count, 0);
assert_eq!(initial_stdx.start_count, 0);
supervised_child.monitor_usage().await;
tokio::time::sleep(Duration::from_millis(100)).await;
let resource = supervised_child.resource_watchdog_snapshot();
assert!(
resource.start_count >= 1,
"resource watchdog should be attached and started"
);
assert!(
supervised_child.resource_monitor_valid(Duration::from_secs(5), 10),
"resource watchdog should report monitor as valid"
);
supervised_child.monitor_stdx().await;
tokio::time::sleep(Duration::from_millis(100)).await;
let stdx = supervised_child.stdx_watchdog_snapshot();
assert!(
stdx.start_count >= 1,
"stdx watchdog should be attached and started"
);
assert!(
supervised_child.stdx_monitor_valid(Duration::from_secs(5), 10),
"stdx watchdog should report monitor as valid"
);
supervised_child.kill().await.unwrap();
}
#[tokio::test]
async fn test_supervised_process_with_invalid_pid() {
let invalid_pid = 999999;
let result = SupervisedProcess::new(Pid::from_raw(invalid_pid));
assert!(
result.is_err(),
"Expected error when creating SupervisedProcess with invalid PID"
);
if let Err(e) = result {
assert_eq!(e.err_type, Errors::SupervisedChild);
}
}
#[tokio::test]
async fn test_supervised_process_clone() {
let pid = nix::unistd::getpid();
let mut proc = SupervisedProcess::new(pid)
.expect("Could not create SupervisedProcess for current PID");
let cloned = proc.clone().await;
assert_eq!(cloned.get_pid(), proc.get_pid());
}
#[tokio::test]
async fn test_supervised_process_kill() {
let mut cmd = Command::new("sleep");
cmd.arg("5");
let child = cmd.spawn().expect("Failed to spawn child for test");
let pid = child.id().expect("No PID found") as i32;
let mut sup = SupervisedProcess::new(Pid::from_raw(pid))
.expect("Failed to create SupervisedProcess from existing PID");
assert!(sup.active(), "Process should be active");
sup.kill().expect("Failed to kill the supervised process");
assert!(!sup.active(), "Process should not be active after kill");
}
#[tokio::test]
async fn test_child_lock_concurrency() {
let mut cmd = Command::new("sleep");
cmd.arg("5");
let child = cmd.spawn().expect("Failed to spawn child");
let lock = ChildLock::new(child);
let lock_clone = lock.clone();
let handle_a = tokio::spawn(async move {
let read_guard = lock_clone.0.try_read().await.unwrap();
read_guard.id().expect("No PID from child")
});
let handle_b = tokio::spawn(async move {
lock.kill().await.expect("Failed to kill process from lock");
});
let (pid_res, kill_res) = tokio::join!(handle_a, handle_b);
let pid = pid_res.unwrap();
kill_res.unwrap();
assert!(!ChildLock::running(pid as i32));
}
#[tokio::test]
async fn test_spawn_simple_process_output_capture() {
let mut state = AppState {
data: String::new(),
event_counter: 0,
stared_at: current_timestamp(),
name: String::new(),
version: SoftwareVersion::dummy(),
status: Status::Building,
pid: 0,
last_updated: current_timestamp(),
error_log: Vec::new(),
config: AppConfig::dummy(),
system_application: false,
stderr: Vec::new(),
stdout: Vec::new(),
};
let state_path = PathType::PathBuf(PathBuf::from("/tmp/test_state.json"));
let mut cmd = Command::new("echo");
cmd.arg("HelloTest");
let child = spawn_simple_process(&mut cmd, true, &mut state, &state_path)
.await
.expect("Failed to spawn with output capture");
let output = child
.wait_with_output()
.await
.expect("Failed to get child output");
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "HelloTest");
assert!(
output.stderr.is_empty(),
"Expected no stderr output from echo"
);
assert_eq!(state.data, "Process spawned");
assert_eq!(state.event_counter, 2);
}
#[tokio::test]
async fn test_spawn_simple_process_inherit() {
let mut state = AppState {
data: String::new(),
event_counter: 0,
name: String::new(),
stared_at: current_timestamp(),
version: SoftwareVersion::dummy(),
status: Status::Building,
pid: 0,
last_updated: current_timestamp(),
error_log: Vec::new(),
config: AppConfig::dummy(),
system_application: false,
stderr: Vec::new(),
stdout: Vec::new(),
};
let state_path = PathType::PathBuf(PathBuf::from("/tmp/test_state_inherit.json"));
let mut cmd = Command::new("true");
let mut child = spawn_simple_process(&mut cmd, false, &mut state, &state_path)
.await
.expect("Failed to spawn with inherited output");
let status = child.wait().await.expect("Failed waiting on child");
assert!(status.success(), "Process with `true` should exit 0");
assert_eq!(state.data, "Process spawned");
assert_eq!(state.event_counter, 2);
}
#[tokio::test]
async fn test_spawn_simple_process_failure() {
let mut state = AppState {
data: String::new(),
event_counter: 0,
name: String::new(),
stared_at: current_timestamp(),
version: SoftwareVersion::dummy(),
status: Status::Building,
pid: 0,
last_updated: current_timestamp(),
error_log: Vec::new(),
config: AppConfig::dummy(),
system_application: false,
stderr: Vec::new(),
stdout: Vec::new(),
};
let state_path = PathType::PathBuf(PathBuf::from("/tmp/test_state_failure.json"));
let mut cmd = Command::new("no_such_command_abcdefg");
let result = spawn_simple_process(&mut cmd, true, &mut state, &state_path).await;
assert!(
result.is_err(),
"Expected spawn failure for invalid command"
);
assert_ne!(state.data, "Process spawned");
assert_eq!(state.event_counter, 1, "Error path also increments counter");
}
#[tokio::test]
async fn test_spawn_complex_process() {
let mut cmd = Command::new("sleep");
cmd.arg("5");
let child = spawn_complex_process(
&mut cmd, None, true,
false,
)
.await
.expect("Failed to spawn complex process");
let pid = child
.child
.0
.try_read()
.await
.expect("Failed to lock child")
.id()
.expect("No PID from child");
assert!(ChildLock::running(pid as i32), "Child should be running");
child.child.kill().await.expect("Failed to kill child");
assert!(!ChildLock::running(pid as i32), "Child should be dead");
}
}