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(
35 clippy::too_many_arguments,
36 reason = "distinct completion fields of a public result; grouping them would introduce a new \
37 public parameter type solely to satisfy the lint"
38 )]
39 pub fn completed(
40 exit_code: Option<i32>,
41 stdout_bytes: Vec<u8>,
42 stderr_bytes: Vec<u8>,
43 stdout_truncated: bool,
44 stderr_truncated: bool,
45 duration: Duration,
46 timed_out: bool,
47 cancelled: bool,
48 ) -> Self {
49 Self {
50 exit_code,
51 stdout: String::from_utf8_lossy(&stdout_bytes).into_owned(),
52 stdout_bytes,
53 stderr: String::from_utf8_lossy(&stderr_bytes).into_owned(),
54 stderr_bytes,
55 stdout_truncated,
56 stderr_truncated,
57 duration,
58 timed_out,
59 cancelled,
60 }
61 }
62
63 pub fn success(&self) -> bool {
85 self.exit_code == Some(0)
86 }
87
88 pub fn check(&self) -> crate::AppResult<&Self> {
104 if self.cancelled {
105 return Err(
106 crate::AppError::new(crate::ErrorCode::Cancelled, "process cancelled")
107 .with_detail("cancelled", true),
108 );
109 }
110 if self.timed_out {
111 return Err(
112 crate::AppError::new(crate::ErrorCode::Timeout, "process timed out")
113 .with_detail("timed_out", true),
114 );
115 }
116
117 match self.exit_code {
118 Some(0) => Ok(self),
119 Some(code) => Err(crate::AppError::new(
120 crate::ErrorCode::Internal,
121 format!("process exited with code {}", code),
122 )
123 .with_detail("exit_code", code)),
124 None => Err(
125 crate::AppError::new(crate::ErrorCode::Internal, "process was killed")
126 .with_detail("killed", true),
127 ),
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::ErrorCode;
136
137 fn result(exit_code: Option<i32>, timed_out: bool, cancelled: bool) -> ProcessResult {
138 ProcessResult::completed(
139 exit_code,
140 b"out".to_vec(),
141 b"err".to_vec(),
142 false,
143 true,
144 Duration::from_millis(5),
145 timed_out,
146 cancelled,
147 )
148 }
149
150 #[test]
151 fn completed_result_preserves_bytes_strings_flags_and_duration() {
152 let result = result(Some(0), false, false);
153
154 assert!(result.success());
155 assert_eq!(result.stdout, "out");
156 assert_eq!(result.stdout_bytes, b"out");
157 assert_eq!(result.stderr, "err");
158 assert_eq!(result.stderr_bytes, b"err");
159 assert!(!result.stdout_truncated);
160 assert!(result.stderr_truncated);
161 assert_eq!(result.duration, Duration::from_millis(5));
162 assert!(std::ptr::eq(result.check().unwrap(), &result));
163 }
164
165 #[test]
166 fn check_reports_cancelled_timeout_failed_and_killed_states() {
167 assert_eq!(
168 result(Some(0), false, true).check().unwrap_err().code(),
169 ErrorCode::Cancelled
170 );
171 assert_eq!(
172 result(Some(0), true, false).check().unwrap_err().code(),
173 ErrorCode::Timeout
174 );
175 assert_eq!(
176 result(Some(2), false, false).check().unwrap_err().code(),
177 ErrorCode::Internal
178 );
179 assert_eq!(
180 result(None, false, false).check().unwrap_err().code(),
181 ErrorCode::Internal
182 );
183 assert!(!result(Some(2), false, false).success());
184 }
185}