#![cfg(unix)]
use crate::config::profile::{
active_profile, is_pid_alive, parse_launchctl_labels, preempt_instances_in,
};
use std::fs;
use tempfile::TempDir;
#[test]
fn empty_lock_dir_preempts_nothing() {
let tmp = TempDir::new().unwrap();
let preempted = preempt_instances_in(tmp.path(), false);
assert!(
preempted.is_empty(),
"an empty lock dir must preempt nothing, got: {preempted:?}"
);
}
#[test]
fn dead_pid_lock_is_skipped() {
let tmp = TempDir::new().unwrap();
let profile = active_profile().unwrap_or("default");
fs::write(
tmp.path().join("telegram_deadbeef.lock"),
format!("{profile}:999999"),
)
.unwrap();
let preempted = preempt_instances_in(tmp.path(), false);
assert!(
preempted.is_empty(),
"a dead-PID lock must be skipped, got: {preempted:?}"
);
}
#[test]
fn other_profile_lock_is_not_touched() {
let tmp = TempDir::new().unwrap();
let other = format!("not-{}", active_profile().unwrap_or("default"));
fs::write(
tmp.path().join("telegram_cafe.lock"),
format!("{other}:{}", std::process::id()),
)
.unwrap();
let preempted = preempt_instances_in(tmp.path(), false);
assert!(
preempted.is_empty(),
"a different profile's lock must not be preempted, got: {preempted:?}"
);
}
#[test]
#[ignore = "spawns and kills a child process; run with `cargo test -- --ignored`"]
fn preempts_and_kills_a_live_instance() {
let tmp = TempDir::new().unwrap();
let profile = active_profile().unwrap_or("default");
let mut child = std::process::Command::new("sleep")
.arg("60")
.spawn()
.expect("spawn sleep child");
let child_pid = child.id();
fs::write(
tmp.path().join("_test_iso_telegram_abc123.lock"),
format!("{profile}:{child_pid}"),
)
.unwrap();
let preempted = preempt_instances_in(tmp.path(), false);
let ours = preempted
.iter()
.find(|p| p.pid == child_pid)
.expect("the live instance must be detected and preempted");
assert!(
ours.channels.iter().any(|c| c == "_test_iso_telegram"),
"preempted instance must report the channel it held: {:?}",
ours.channels
);
assert!(
ours.stopped,
"the instance must be stopped (SIGTERM/SIGKILL)"
);
let killed = !is_pid_alive(child_pid);
let _ = child.kill();
let _ = child.wait();
assert!(
killed,
"the background process must be gone after preemption"
);
}
#[test]
fn parses_opencrabs_labels_from_launchctl_list() {
let out = "PID\tStatus\tLabel\n\
812\t0\tcom.apple.Finder\n\
4043b\t0\tcom.opencrabs.daemon\n\
-\t1\tcom.opencrabs.daemon.hermes\n\
221\t0\tcom.opencrabs.agent\n\
9\t0\torg.example.thing\n";
let labels = parse_launchctl_labels(out);
assert_eq!(
labels,
vec![
"com.opencrabs.daemon".to_string(),
"com.opencrabs.daemon.hermes".to_string(),
"com.opencrabs.agent".to_string(),
],
"exactly our labels, in output order, foreign services excluded"
);
}
#[test]
fn launchctl_parse_dedupes_and_survives_empty_input() {
let dupes = "PID\tStatus\tLabel\n\
1\t0\tcom.opencrabs.daemon\n\
2\t0\tcom.opencrabs.daemon\n";
assert_eq!(
parse_launchctl_labels(dupes),
vec!["com.opencrabs.daemon".to_string()],
"duplicate rows (two profiles' list snapshots) collapse to one bootout"
);
assert!(
parse_launchctl_labels("").is_empty(),
"empty launchctl output parses to nothing"
);
assert!(
parse_launchctl_labels("PID\tStatus\tLabel\n").is_empty(),
"bare header line parses to nothing"
);
}
#[test]
fn launchctl_parse_rejects_lookalike_prefixes() {
let sneaky = "PID\tStatus\tLabel\n\
1\t0\txcom.opencrabs.daemon\n\
2\t0\tcom.opencrabsx.evil\n\
3\t0\tcom.opencrabs.daemon\n";
assert_eq!(
parse_launchctl_labels(sneaky),
vec!["com.opencrabs.daemon".to_string()],
"prefix must anchor at label start; near-misses are not ours to bootout"
);
}