1use std::time::Duration;
4
5#[derive(Debug, Clone)]
7#[non_exhaustive]
8pub struct ProcessResult {
9 pub exit_code: Option<i32>,
11 pub stdout: String,
13 pub stdout_bytes: Vec<u8>,
15 pub stderr: String,
17 pub stderr_bytes: Vec<u8>,
19 pub stdout_truncated: bool,
21 pub stderr_truncated: bool,
23 pub duration: Duration,
25 pub timed_out: bool,
27 pub cancelled: bool,
29}
30
31impl ProcessResult {
32 #[must_use]
34 #[allow(clippy::too_many_arguments)]
35 pub fn completed(
36 exit_code: Option<i32>,
37 stdout_bytes: Vec<u8>,
38 stderr_bytes: Vec<u8>,
39 stdout_truncated: bool,
40 stderr_truncated: bool,
41 duration: Duration,
42 timed_out: bool,
43 cancelled: bool,
44 ) -> Self {
45 Self {
46 exit_code,
47 stdout: String::from_utf8_lossy(&stdout_bytes).into_owned(),
48 stdout_bytes,
49 stderr: String::from_utf8_lossy(&stderr_bytes).into_owned(),
50 stderr_bytes,
51 stdout_truncated,
52 stderr_truncated,
53 duration,
54 timed_out,
55 cancelled,
56 }
57 }
58
59 pub fn success(&self) -> bool {
81 self.exit_code == Some(0)
82 }
83
84 pub fn check(&self) -> crate::AppResult<&Self> {
100 if self.cancelled {
101 return Err(
102 crate::AppError::new(crate::ErrorCode::Cancelled, "process cancelled")
103 .with_detail("cancelled", true),
104 );
105 }
106 if self.timed_out {
107 return Err(
108 crate::AppError::new(crate::ErrorCode::Timeout, "process timed out")
109 .with_detail("timed_out", true),
110 );
111 }
112
113 match self.exit_code {
114 Some(0) => Ok(self),
115 Some(code) => Err(crate::AppError::new(
116 crate::ErrorCode::Internal,
117 format!("process exited with code {}", code),
118 )
119 .with_detail("exit_code", code)),
120 None => Err(
121 crate::AppError::new(crate::ErrorCode::Internal, "process was killed")
122 .with_detail("killed", true),
123 ),
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::ErrorCode;
132
133 fn result(exit_code: Option<i32>, timed_out: bool, cancelled: bool) -> ProcessResult {
134 ProcessResult::completed(
135 exit_code,
136 b"out".to_vec(),
137 b"err".to_vec(),
138 false,
139 true,
140 Duration::from_millis(5),
141 timed_out,
142 cancelled,
143 )
144 }
145
146 #[test]
147 fn completed_result_preserves_bytes_strings_flags_and_duration() {
148 let result = result(Some(0), false, false);
149
150 assert!(result.success());
151 assert_eq!(result.stdout, "out");
152 assert_eq!(result.stdout_bytes, b"out");
153 assert_eq!(result.stderr, "err");
154 assert_eq!(result.stderr_bytes, b"err");
155 assert!(!result.stdout_truncated);
156 assert!(result.stderr_truncated);
157 assert_eq!(result.duration, Duration::from_millis(5));
158 assert!(std::ptr::eq(result.check().unwrap(), &result));
159 }
160
161 #[test]
162 fn check_reports_cancelled_timeout_failed_and_killed_states() {
163 assert_eq!(
164 result(Some(0), false, true).check().unwrap_err().code(),
165 ErrorCode::Cancelled
166 );
167 assert_eq!(
168 result(Some(0), true, false).check().unwrap_err().code(),
169 ErrorCode::Timeout
170 );
171 assert_eq!(
172 result(Some(2), false, false).check().unwrap_err().code(),
173 ErrorCode::Internal
174 );
175 assert_eq!(
176 result(None, false, false).check().unwrap_err().code(),
177 ErrorCode::Internal
178 );
179 assert!(!result(Some(2), false, false).success());
180 }
181}