use std::time::Duration;
use systemd_transient::{
ExecCommand, ServiceType, SystemdTransientManager, UnitExit, UnitProperty, escape_unit_name,
};
fn name(test: &str) -> String {
format!("systemd-transient-test-{test}-{}", std::process::id())
}
fn command(args: &[&str]) -> Vec<UnitProperty> {
vec![
UnitProperty::Type(ServiceType::Exec),
UnitProperty::ExecStart(vec![ExecCommand::new("/usr/bin/env", args.iter().copied())]),
]
}
async fn manager() -> SystemdTransientManager {
SystemdTransientManager::session().await.unwrap()
}
async fn wait_unloaded(manager: &SystemdTransientManager, unit: &str) {
for _ in 0..50 {
if manager.list_units(&[unit]).await.unwrap().is_empty() {
return;
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
panic!("{unit} is still loaded");
}
#[tokio::test]
#[ignore]
async fn adopted_unit_survives_the_starting_connection() {
let name = name("adopt-running");
let unit = format!("{name}.service");
let first = manager().await;
let started = first.start(&name, command(&["sleep", "30"])).await.unwrap();
let second = manager().await;
let adopted = second.adopt(&name).await.unwrap().expect("unit is loaded");
drop(started);
drop(first);
tokio::time::sleep(Duration::from_millis(500)).await;
let listed = second.list_units(&[&unit]).await.unwrap();
assert_eq!(listed[0].active_state, "active");
assert_eq!(adopted.stop().await.unwrap(), UnitExit::Inactive);
adopted.release().await.unwrap();
wait_unloaded(&second, &unit).await;
}
#[tokio::test]
#[ignore]
async fn failed_unit_stays_adoptable_without_references() {
let name = name("adopt-failed");
let unit = format!("{name}.service");
let first = manager().await;
let started = first.start(&name, command(&["false"])).await.unwrap();
assert_eq!(started.wait_for_exit().await.unwrap(), UnitExit::Failed);
started.release().await.unwrap();
drop(first);
let second = manager().await;
let adopted = second
.adopt(&name)
.await
.unwrap()
.expect("failed units stay loaded");
assert_eq!(adopted.wait_for_exit().await.unwrap(), UnitExit::Failed);
adopted.release().await.unwrap();
let again = second.start(&name, command(&["true"])).await.unwrap();
assert_eq!(again.wait_for_exit().await.unwrap(), UnitExit::Inactive);
again.release().await.unwrap();
wait_unloaded(&second, &unit).await;
}
#[tokio::test]
#[ignore]
async fn cleanly_exited_unit_is_collected_and_not_adoptable() {
let name = name("adopt-inactive");
let unit = format!("{name}.service");
let manager = manager().await;
let started = manager.start(&name, command(&["true"])).await.unwrap();
assert_eq!(started.wait_for_exit().await.unwrap(), UnitExit::Inactive);
started.release().await.unwrap();
wait_unloaded(&manager, &unit).await;
assert!(manager.adopt(&name).await.unwrap().is_none());
wait_unloaded(&manager, &unit).await;
}
#[tokio::test]
#[ignore]
async fn transient_slice_with_escaped_child_name() {
let parent = name("slice");
let slice = format!("{parent}-{}.slice", escape_unit_name("a-b"));
let manager = manager().await;
let slice_handle = manager.start(&slice, []).await.unwrap();
let service = name("in-slice");
let mut props = command(&["sleep", "30"]);
props.push(UnitProperty::Slice(slice.clone()));
let service_handle = manager.start(&service, props).await.unwrap();
let listed = manager.list_units(&[&slice]).await.unwrap();
assert_eq!(listed[0].active_state, "active");
assert_eq!(slice_handle.stop().await.unwrap(), UnitExit::Inactive);
assert_eq!(
service_handle.wait_for_exit().await.unwrap(),
UnitExit::Inactive
);
service_handle.release().await.unwrap();
slice_handle.release().await.unwrap();
}