use std::{process::Command, sync::mpsc, thread, time::Duration};
use tracing_test::traced_test;
fn get_node_script_infinite() -> String {
r"
// console.log('infinite. pid:', process.pid);
setInterval(() => {}, 1000);
"
.to_string()
}
fn get_node_script_spawn_infinite_child() -> String {
r#"
// console.log('spawn child. pid:', process.pid);
const { spawn } = require('child_process');
// const child = spawn('node', ['-e', 'console.log("infinite. pid:", process.pid);setInterval(() => {}, 1000);'], {
const child = spawn('node', ['-e', 'setInterval(() => {}, 1000);'], {
stdio: 'inherit',
});
// child.on('exit', (code, signal) => {
// console.log('child process exited with ' +
// `code ${code} and signal ${signal}`);
// });
setInterval(() => {}, 1000);
"#
.to_string()
}
#[traced_test]
#[test]
fn kill_tree_default() {
let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
let mut child = Command::new("node")
.arg("-e")
.arg(get_node_script_infinite())
.spawn()
.unwrap();
let target_process_id = child.id();
thread::sleep(Duration::from_secs(1));
tx.send(target_process_id).unwrap();
let _ = child.wait();
});
let target_process_id = rx.recv().unwrap();
let outputs = kill_tree::blocking::kill_tree(target_process_id).expect("Failed to kill");
println!("{outputs:?}");
assert_eq!(outputs.len(), 1);
let output = &outputs[0];
match output {
kill_tree::Output::Killed {
process_id,
parent_process_id,
name,
} => {
assert_eq!(*process_id, target_process_id);
assert_eq!(*parent_process_id, std::process::id());
println!("name: {name}");
assert!(name.starts_with("node"));
}
kill_tree::Output::MaybeAlreadyTerminated { .. } => {
panic!("This should not happen");
}
}
thread.join().unwrap();
}
#[test]
fn kill_tree_with_config_sigkill() {
let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
let mut child = Command::new("node")
.arg("-e")
.arg(get_node_script_infinite())
.spawn()
.unwrap();
let target_process_id = child.id();
thread::sleep(Duration::from_secs(1));
tx.send(target_process_id).unwrap();
let _ = child.wait();
});
let target_process_id = rx.recv().unwrap();
let config = kill_tree::Config {
signal: String::from("SIGKILL"),
..Default::default()
};
let outputs = kill_tree::blocking::kill_tree_with_config(target_process_id, &config)
.expect("Failed to kill");
println!("{outputs:?}");
assert_eq!(outputs.len(), 1);
let output = &outputs[0];
match output {
kill_tree::Output::Killed {
process_id,
parent_process_id,
name,
} => {
assert_eq!(*process_id, target_process_id);
assert_eq!(*parent_process_id, std::process::id());
assert!(name.starts_with("node"));
}
kill_tree::Output::MaybeAlreadyTerminated { .. } => {
panic!("This should not happen");
}
}
thread.join().unwrap();
}
#[traced_test]
#[test]
fn kill_tree_with_config_include_target_false() {
let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
let mut child = Command::new("node")
.arg("-e")
.arg(get_node_script_spawn_infinite_child())
.spawn()
.unwrap();
let target_process_id = child.id();
thread::sleep(Duration::from_secs(1));
tx.send(target_process_id).unwrap();
let _ = child.wait();
});
let target_process_id = rx.recv().unwrap();
let config = kill_tree::Config {
include_target: false,
..Default::default()
};
let outputs = kill_tree::blocking::kill_tree_with_config(target_process_id, &config)
.expect("Failed to kill");
println!("{outputs:?}");
assert!(!outputs.is_empty());
let output = &outputs[0];
match output {
kill_tree::Output::Killed {
process_id: _,
parent_process_id,
name,
} => {
assert_eq!(*parent_process_id, target_process_id);
assert!(name.starts_with("node"));
}
kill_tree::Output::MaybeAlreadyTerminated { .. } => {
panic!("This should not happen");
}
}
thread::sleep(Duration::from_secs(1));
let outputs = kill_tree::blocking::kill_tree(target_process_id).expect("Failed to kill");
println!("{outputs:?}");
assert_eq!(outputs.len(), 1);
let output = &outputs[0];
match output {
kill_tree::Output::Killed {
process_id,
parent_process_id,
name,
} => {
assert_eq!(*process_id, target_process_id);
assert_eq!(*parent_process_id, std::process::id());
assert!(name.starts_with("node"));
}
kill_tree::Output::MaybeAlreadyTerminated { .. } => {
panic!("This should not happen");
}
}
thread.join().unwrap();
}
#[test]
fn kill_tree_child_tree() {
let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
let mut child = Command::new("node")
.arg("-e")
.arg(get_node_script_spawn_infinite_child())
.spawn()
.unwrap();
thread::sleep(Duration::from_secs(1));
let target_process_id = child.id();
tx.send(target_process_id).unwrap();
let _ = child.wait();
});
let target_process_id = rx.recv().unwrap();
let outputs = kill_tree::blocking::kill_tree(target_process_id).expect("Failed to kill");
assert_eq!(outputs.len(), 2);
let target_output = outputs
.iter()
.find(|output| match output {
kill_tree::Output::Killed {
process_id: _,
parent_process_id,
name: _,
} => *parent_process_id == std::process::id(),
kill_tree::Output::MaybeAlreadyTerminated { .. } => false,
})
.unwrap();
match target_output {
kill_tree::Output::Killed {
process_id,
parent_process_id,
name,
} => {
assert_eq!(*process_id, target_process_id);
assert_eq!(*parent_process_id, std::process::id());
assert!(name.starts_with("node"));
}
kill_tree::Output::MaybeAlreadyTerminated { .. } => {
panic!("This should not happen");
}
}
let child_output = outputs
.iter()
.find(|output| match output {
kill_tree::Output::Killed {
process_id: _,
parent_process_id,
name: _,
} => *parent_process_id == target_process_id,
kill_tree::Output::MaybeAlreadyTerminated { .. } => false,
})
.unwrap();
match child_output {
kill_tree::Output::Killed {
process_id: _,
parent_process_id,
name,
} => {
assert_eq!(*parent_process_id, target_process_id);
assert!(name.starts_with("node"));
}
kill_tree::Output::MaybeAlreadyTerminated { .. } => {
panic!("This should not happen");
}
}
thread.join().unwrap();
}