Skip to main content

mixtape_tools/process/
kill_process.rs

1use crate::prelude::*;
2use sysinfo::{Pid, Signal, System};
3
4/// Input for killing a process
5#[derive(Debug, Deserialize, JsonSchema)]
6pub struct KillProcessInput {
7    /// Process ID (PID) to terminate
8    pub pid: u32,
9}
10
11/// Tool for terminating processes
12pub struct KillProcessTool;
13
14impl Tool for KillProcessTool {
15    type Input = KillProcessInput;
16
17    fn name(&self) -> &str {
18        "kill_process"
19    }
20
21    fn description(&self) -> &str {
22        "Terminate a running process by its PID. Use with caution as this forcefully kills the process."
23    }
24
25    async fn execute(&self, input: Self::Input) -> std::result::Result<ToolResult, ToolError> {
26        let mut sys = System::new();
27        let pid = Pid::from_u32(input.pid);
28
29        // Refresh all processes to find the target
30        sys.refresh_processes(sysinfo::ProcessesToUpdate::All);
31
32        if let Some(process) = sys.process(pid) {
33            let name = process.name().to_string_lossy().to_string();
34
35            // Try to kill the process
36            if process.kill_with(Signal::Term).is_some() {
37                Ok(format!(
38                    "Successfully sent termination signal to process {} (PID: {})",
39                    name, input.pid
40                )
41                .into())
42            } else {
43                Err(format!(
44                    "Failed to terminate process {} (PID: {}). Permission denied or process already terminated.",
45                    name, input.pid
46                ).into())
47            }
48        } else {
49            Err(format!("Process with PID {} not found", input.pid).into())
50        }
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[tokio::test]
59    async fn test_kill_nonexistent_process() {
60        let tool = KillProcessTool;
61        let input = KillProcessInput { pid: 99999999 };
62
63        let result = tool.execute(input).await;
64        assert!(result.is_err());
65        assert!(result.unwrap_err().to_string().contains("not found"));
66    }
67
68    #[tokio::test]
69    async fn test_kill_process_success() {
70        use crate::process::start_process::{StartProcessInput, StartProcessTool};
71
72        // Start a process we can kill
73        let start_tool = StartProcessTool;
74        let start_input = StartProcessInput {
75            command: "sleep 30".to_string(),
76            timeout_ms: Some(60000),
77            shell: None,
78        };
79
80        let start_result = start_tool.execute(start_input).await;
81        if start_result.is_err() {
82            // Skip if process creation fails
83            return;
84        }
85
86        let start_output = start_result.unwrap().as_text();
87        // Extract PID
88        if let Some(pid_line) = start_output.lines().find(|l| l.contains("PID:")) {
89            if let Some(pid_str) = pid_line.split(':').nth(1) {
90                if let Ok(pid) = pid_str.trim().parse::<u32>() {
91                    // Give the process time to fully start
92                    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
93
94                    // Kill the process
95                    let kill_tool = KillProcessTool;
96                    let kill_input = KillProcessInput { pid };
97
98                    let result = kill_tool.execute(kill_input).await;
99                    if result.is_err() {
100                        eprintln!("Kill failed: {:?}", result.as_ref().err());
101                        // This is acceptable - process may have already exited or be managed differently
102                        return;
103                    }
104                    let output = result.unwrap().as_text();
105                    assert!(output.contains("Successfully sent termination signal"));
106                    return;
107                }
108            }
109        }
110    }
111
112    // ==================== Tool metadata tests ====================
113
114    #[test]
115    fn test_tool_name() {
116        let tool = KillProcessTool;
117        assert_eq!(tool.name(), "kill_process");
118    }
119
120    #[test]
121    fn test_tool_description() {
122        let tool = KillProcessTool;
123        assert!(!tool.description().is_empty());
124        assert!(tool.description().contains("Terminate") || tool.description().contains("kill"));
125    }
126
127    // ==================== Input struct tests ====================
128
129    #[test]
130    fn test_kill_process_input_debug() {
131        let input = KillProcessInput { pid: 12345 };
132        let debug_str = format!("{:?}", input);
133        assert!(debug_str.contains("12345"));
134    }
135}