use crate::Result;
use tokio::process::Command as TokioCommand;
pub struct ProcessManager;
impl ProcessManager {
pub fn new() -> Self {
Self
}
pub async fn kill_process(&self, pid: u32) -> Result<()> {
self.kill_process_unix(pid).await
}
async fn kill_process_unix(&self, pid: u32) -> Result<()> {
let output = TokioCommand::new("kill")
.arg("-TERM")
.arg(pid.to_string())
.output()
.await
.map_err(|e| crate::Error::CommandFailed(format!("kill command failed: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("No such process") {
return Err(crate::Error::ProcessNotFound(pid));
} else if stderr.contains("Operation not permitted") {
return Err(crate::Error::PermissionDenied(
"プロセス終了の権限がありません。sudoで実行してください。".to_string(),
));
}
return Err(crate::Error::CommandFailed(format!(
"Failed to kill process: {stderr}"
)));
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
if self.process_exists(pid).await? {
let output = TokioCommand::new("kill")
.arg("-KILL")
.arg(pid.to_string())
.output()
.await
.map_err(|e| crate::Error::CommandFailed(format!("kill -KILL failed: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(crate::Error::CommandFailed(format!(
"Failed to force kill process: {stderr}"
)));
}
}
Ok(())
}
async fn process_exists(&self, pid: u32) -> Result<bool> {
self.process_exists_unix(pid).await
}
async fn process_exists_unix(&self, pid: u32) -> Result<bool> {
let output = TokioCommand::new("ps")
.arg("-p")
.arg(pid.to_string())
.output()
.await
.map_err(|e| crate::Error::CommandFailed(format!("ps command failed: {e}")))?;
Ok(output.status.success())
}
pub async fn get_process_info(&self, pid: u32) -> Result<(String, String)> {
self.get_process_info_unix(pid).await
}
async fn get_process_info_unix(&self, pid: u32) -> Result<(String, String)> {
let output = TokioCommand::new("ps")
.arg("-p")
.arg(pid.to_string())
.arg("-o")
.arg("comm=,command=")
.output()
.await
.map_err(|e| crate::Error::CommandFailed(format!("ps command failed: {e}")))?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().nth(1) {
let parts: Vec<&str> = line.splitn(2, ' ').collect();
if parts.len() >= 2 {
return Ok((parts[0].to_string(), parts[1].to_string()));
} else if parts.len() == 1 {
return Ok((parts[0].to_string(), parts[0].to_string()));
}
}
}
Err(crate::Error::ProcessNotFound(pid))
}
}
impl Default for ProcessManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_process_manager_creation() {
let process_manager = ProcessManager::new();
let _ = &process_manager;
}
#[tokio::test]
async fn test_process_manager_default() {
let process_manager = ProcessManager;
let _ = &process_manager;
}
#[tokio::test]
async fn test_process_exists_with_invalid_pid() {
let process_manager = ProcessManager::new();
match process_manager.process_exists(99999).await {
Ok(exists) => {
assert!(!exists, "PID 99999 should not exist");
}
Err(_) => {
}
}
}
#[tokio::test]
async fn test_process_exists_with_current_process() {
let process_manager = ProcessManager::new();
let current_pid = std::process::id();
match process_manager.process_exists(current_pid).await {
Ok(exists) => {
assert!(exists, "Current process should exist");
}
Err(_) => {
}
}
}
#[tokio::test]
async fn test_kill_process_non_existent() {
let process_manager = ProcessManager::new();
let result = process_manager.kill_process(99998).await;
assert!(result.is_err());
if let Err(e) = result {
match e {
crate::Error::ProcessNotFound(pid) => {
assert_eq!(pid, 99998);
}
crate::Error::CommandFailed(_) => {
}
_ => {
}
}
}
}
#[tokio::test]
async fn test_get_process_info_with_invalid_pid() {
let process_manager = ProcessManager::new();
let result = process_manager.get_process_info(99997).await;
assert!(result.is_err());
if let Err(e) = result {
match e {
crate::Error::ProcessNotFound(pid) => {
assert_eq!(pid, 99997);
}
crate::Error::CommandFailed(_) => {
}
_ => {
}
}
}
}
#[tokio::test]
async fn test_get_process_info_with_current_process() {
let process_manager = ProcessManager::new();
let current_pid = std::process::id();
match process_manager.get_process_info(current_pid).await {
Ok((comm, command)) => {
assert!(!comm.is_empty(), "Process command name should not be empty");
assert!(
!command.is_empty(),
"Process command line should not be empty"
);
assert!(!comm.is_empty());
assert!(command.len() >= comm.len());
}
Err(_) => {
}
}
}
#[tokio::test]
async fn test_process_exists_edge_cases() {
let process_manager = ProcessManager::new();
let edge_pids = [0, 1, u32::MAX];
for pid in edge_pids {
match process_manager.process_exists(pid).await {
Ok(exists) => {
if pid == u32::MAX {
assert!(!exists, "PID {} should not exist", pid);
}
}
Err(_) => {
}
}
}
}
#[tokio::test]
async fn test_kill_process_error_handling() {
let process_manager = ProcessManager::new();
let result = process_manager.kill_process(1).await;
match result {
Ok(_) => {
}
Err(e) => {
let error_msg = e.to_string();
assert!(
error_msg.contains("Permission denied")
|| error_msg.contains("Operation not permitted")
|| error_msg.contains("command failed")
|| error_msg.contains("ProcessNotFound")
|| !error_msg.is_empty()
);
}
}
}
#[tokio::test]
async fn test_process_manager_consistency() {
let process_manager = ProcessManager::new();
let current_pid = std::process::id();
let exists_result_1 = process_manager.process_exists(current_pid).await;
let exists_result_2 = process_manager.process_exists(current_pid).await;
match (exists_result_1, exists_result_2) {
(Ok(exists1), Ok(exists2)) => {
assert_eq!(
exists1, exists2,
"process_exists should return consistent results"
);
}
_ => {
}
}
}
#[tokio::test]
async fn test_multiple_process_manager_instances() {
let pm1 = ProcessManager::new();
let pm2 = ProcessManager::new();
let pm3 = ProcessManager;
let current_pid = std::process::id();
let results = vec![
pm1.process_exists(current_pid).await,
pm2.process_exists(current_pid).await,
pm3.process_exists(current_pid).await,
];
for result in results {
match result {
Ok(_exists) => {
}
Err(_) => {
}
}
}
}
#[test]
fn test_process_manager_struct_properties() {
let pm1 = ProcessManager::new();
let pm2 = ProcessManager;
assert!(std::mem::size_of::<ProcessManager>() == 0);
assert_eq!(std::mem::size_of_val(&pm1), std::mem::size_of_val(&pm2));
}
}