Skip to main content

torrust_tracker_deployer_lib/shared/command/
result.rs

1//! Command execution result
2//!
3//! This module provides the `CommandResult` struct that represents the complete
4//! result of a successfully executed command, including exit status, stdout, and stderr.
5
6use std::process::ExitStatus;
7
8/// Represents the complete result of a successfully executed command
9///
10/// This struct provides complete information about a command execution,
11/// including the exit status and both stdout and stderr output streams.
12#[derive(Debug, Clone)]
13pub struct CommandResult {
14    /// The exit status of the command
15    pub exit_status: ExitStatus,
16
17    /// The standard output (stdout) of the command
18    pub stdout: String,
19
20    /// The standard error output (stderr) of the command  
21    pub stderr: String,
22}
23
24impl CommandResult {
25    /// Creates a new `CommandResult` instance
26    #[must_use]
27    pub fn new(exit_status: ExitStatus, stdout: String, stderr: String) -> Self {
28        Self {
29            exit_status,
30            stdout,
31            stderr,
32        }
33    }
34
35    /// Returns true if the command executed successfully (exit code 0)
36    #[must_use]
37    pub fn is_success(&self) -> bool {
38        self.exit_status.success()
39    }
40
41    /// Returns the exit code if available
42    #[must_use]
43    pub fn exit_code(&self) -> Option<i32> {
44        self.exit_status.code()
45    }
46
47    /// Returns the stdout output, trimmed of leading/trailing whitespace
48    #[must_use]
49    pub fn stdout_trimmed(&self) -> &str {
50        self.stdout.trim()
51    }
52
53    /// Returns the stderr output, trimmed of leading/trailing whitespace
54    #[must_use]
55    pub fn stderr_trimmed(&self) -> &str {
56        self.stderr.trim()
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use std::process::{Command, Stdio};
64
65    #[test]
66    fn it_should_provide_complete_command_result_information() {
67        // Create a simple command to get real ExitStatus
68        let output = Command::new("echo")
69            .arg("test")
70            .stdout(Stdio::piped())
71            .stderr(Stdio::piped())
72            .output()
73            .expect("Failed to execute echo command");
74
75        let command_result = CommandResult::new(
76            output.status,
77            String::from_utf8_lossy(&output.stdout).to_string(),
78            String::from_utf8_lossy(&output.stderr).to_string(),
79        );
80
81        // Test that we have access to all execution information
82        assert!(command_result.is_success());
83        assert_eq!(command_result.exit_code(), Some(0));
84        assert_eq!(command_result.stdout_trimmed(), "test");
85        assert_eq!(command_result.stderr_trimmed(), "");
86
87        // Test that raw outputs are also available
88        assert!(command_result.stdout.contains("test"));
89        assert!(command_result.stderr.is_empty());
90    }
91
92    #[test]
93    fn it_should_handle_command_with_stderr() {
94        // Use a command that writes to stderr (ls on a non-existent directory)
95        let output = Command::new("ls")
96            .arg("/nonexistent_directory_xyz123")
97            .stdout(Stdio::piped())
98            .stderr(Stdio::piped())
99            .output()
100            .expect("Failed to execute ls command");
101
102        let command_result = CommandResult::new(
103            output.status,
104            String::from_utf8_lossy(&output.stdout).to_string(),
105            String::from_utf8_lossy(&output.stderr).to_string(),
106        );
107
108        // This command should fail
109        assert!(!command_result.is_success());
110        assert!(command_result.exit_code().is_some());
111        assert_ne!(command_result.exit_code(), Some(0));
112
113        // Should have error in stderr
114        assert!(!command_result.stderr_trimmed().is_empty());
115    }
116
117    #[test]
118    fn it_should_trim_whitespace_correctly() {
119        // Create a command result with whitespace
120        let output = Command::new("echo")
121            .arg("  spaced  ")
122            .stdout(Stdio::piped())
123            .stderr(Stdio::piped())
124            .output()
125            .expect("Failed to execute echo command");
126
127        let command_result = CommandResult::new(
128            output.status,
129            String::from_utf8_lossy(&output.stdout).to_string(),
130            String::from_utf8_lossy(&output.stderr).to_string(),
131        );
132
133        // Test trimming
134        assert_eq!(command_result.stdout_trimmed(), "spaced");
135        // Raw stdout should preserve whitespace
136        assert!(command_result.stdout.contains("  spaced  "));
137    }
138}