#[path = "common/mod.rs"]
mod common;
use std::{fs, thread, time::Duration};
use common::{HomeEnvGuard, wait_for_file_value, wait_for_pid, wait_for_pid_removed};
use systemg::{config::load_config, daemon::Daemon};
use tempfile::tempdir;
#[test]
fn service_start_stop() {
let temp = tempdir().unwrap();
let dir = temp.path();
let home = dir.join("home");
fs::create_dir_all(&home).unwrap();
let _home = HomeEnvGuard::set(&home);
let marker = dir.join("marker.txt");
let config_yaml = format!(
r#"
version: '1'
services:
test_service:
command: "sh -c 'echo running > {} && sleep 2'"
other_service:
command: "sleep 10"
"#,
marker.display()
);
let config_path = dir.join("systemg.yaml");
fs::write(&config_path, config_yaml).unwrap();
let config = load_config(Some(config_path.to_str().unwrap())).unwrap();
let daemon = Daemon::from_config(config.clone(), false).unwrap();
daemon.start_services().unwrap();
thread::sleep(Duration::from_millis(500));
wait_for_pid("test_service");
wait_for_pid("other_service");
wait_for_file_value(&marker, "running");
daemon.stop_service("test_service").unwrap();
wait_for_pid_removed("test_service");
assert!(
systemg::daemon::PidFile::load()
.unwrap()
.pid_for("other_service")
.is_some()
);
let test_service_config = &config.services["test_service"];
daemon
.start_service("test_service", test_service_config)
.unwrap();
thread::sleep(Duration::from_millis(500));
wait_for_pid("test_service");
daemon.shutdown_monitor();
daemon.stop_services().unwrap();
}
#[test]
fn restart_records_new_pid() {
let temp = tempdir().unwrap();
let dir = temp.path();
let home = dir.join("home");
fs::create_dir_all(&home).unwrap();
let _home = HomeEnvGuard::set(&home);
let config_yaml = r#"
version: '1'
services:
sleepy:
command: "sleep 60"
"#;
let config_path = dir.join("systemg.yaml");
fs::write(&config_path, config_yaml).unwrap();
let config = load_config(Some(config_path.to_str().unwrap())).unwrap();
let daemon = Daemon::from_config(config.clone(), false).unwrap();
daemon.start_services().unwrap();
let pid1 = wait_for_pid("sleepy");
assert!(common::is_process_alive(pid1));
daemon
.restart_service("sleepy", &config.services["sleepy"])
.unwrap();
let pid2 = wait_for_pid("sleepy");
assert_ne!(pid1, pid2, "Should have new PID after restart");
assert!(
!common::is_process_alive(pid1),
"Old process should be gone"
);
assert!(
common::is_process_alive(pid2),
"New process should be running"
);
daemon.shutdown_monitor();
daemon.stop_services().unwrap();
}
#[test]
fn manual_stop_prevents_restart() {
let temp = tempdir().unwrap();
let dir = temp.path();
let home = dir.join("home");
fs::create_dir_all(&home).unwrap();
let _home = HomeEnvGuard::set(&home);
let counter = dir.join("counter.txt");
let config_yaml = format!(
r#"
version: '1'
services:
flaky:
command: "sh -c 'echo $$ >> {}; sleep 0.5; exit 1'"
restart_policy: "always"
backoff: "1s"
"#,
counter.display()
);
let config_path = dir.join("systemg.yaml");
fs::write(&config_path, config_yaml).unwrap();
let config = load_config(Some(config_path.to_str().unwrap())).unwrap();
let daemon = Daemon::from_config(config, false).unwrap();
let result = daemon.start_services();
println!("start_services result: {:?}", result);
result.unwrap();
thread::sleep(Duration::from_secs(4));
let content = fs::read_to_string(&counter).unwrap_or_else(|_| String::from(""));
let initial_runs = content.lines().count();
println!("Initial runs after 4s: {}", initial_runs);
assert!(
initial_runs >= 2,
"Service should have restarted, but only ran {} times",
initial_runs
);
daemon.stop_service("flaky").unwrap();
thread::sleep(Duration::from_millis(500));
let content = fs::read_to_string(&counter).unwrap();
let final_runs = content.lines().count();
assert_eq!(
initial_runs, final_runs,
"Service should not restart after manual stop"
);
daemon.shutdown_monitor();
}
#[test]
fn skip_flag_prevents_service_start() {
let temp = tempdir().unwrap();
let dir = temp.path();
let home = dir.join("home");
fs::create_dir_all(&home).unwrap();
let _home = HomeEnvGuard::set(&home);
let marker = dir.join("marker.txt");
let config_yaml = format!(
r#"
version: '1'
services:
skipped:
command: "touch {}"
skip: true
"#,
marker.display()
);
let config_path = dir.join("systemg.yaml");
fs::write(&config_path, config_yaml).unwrap();
let config = load_config(Some(config_path.to_str().unwrap())).unwrap();
let daemon = Daemon::from_config(config, false).unwrap();
daemon.start_services().unwrap();
thread::sleep(Duration::from_millis(200));
assert!(!marker.exists(), "Skipped service should not execute");
daemon.shutdown_monitor();
}
#[test]
fn skip_flag_controls_execution() {
let temp = tempdir().unwrap();
let dir = temp.path();
let home = dir.join("home");
fs::create_dir_all(&home).unwrap();
let _home = HomeEnvGuard::set(&home);
let marker = dir.join("marker.txt");
let config_yaml_false = format!(
r#"
version: '1'
services:
test:
command: "sh -c 'echo \"service executed\" > {}'"
skip: false
"#,
marker.display()
);
let config_path = dir.join("systemg.yaml");
fs::write(&config_path, config_yaml_false).unwrap();
let config = load_config(Some(config_path.to_str().unwrap())).unwrap();
let daemon = Daemon::from_config(config.clone(), false).unwrap();
for (service_name, service_config) in &config.services {
if service_config.cron.is_none() {
daemon.start_service(service_name, service_config).unwrap();
}
}
wait_for_file_value(&marker, "service executed");
daemon.stop_services().unwrap();
let _ = fs::remove_file(&marker);
let config_yaml_true = format!(
r#"
version: '1'
services:
test:
command: "sh -c 'echo \"service executed\" > {}'"
skip: true
"#,
marker.display()
);
fs::write(&config_path, config_yaml_true).unwrap();
let config2 = load_config(Some(config_path.to_str().unwrap())).unwrap();
let daemon2 = Daemon::from_config(config2.clone(), false).unwrap();
for (service_name, service_config) in &config2.services {
if service_config.cron.is_none() {
daemon2.start_service(service_name, service_config).unwrap();
}
}
thread::sleep(Duration::from_millis(500));
assert!(
!marker.exists(),
"skip: true should prevent service from executing, but marker file exists"
);
daemon2.shutdown_monitor();
}