1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum ControlFlow {
6 #[default]
7 None,
8 Break(u32),
10 Continue(u32),
12 Return(i32),
14}
15
16#[derive(Debug, Clone)]
27pub enum BuiltinSideEffect {
28 ShiftPositional(usize),
30 SetPositional(Vec<String>),
32 SetArray { name: String, elements: Vec<String> },
34 SetIndexedArray {
36 name: String,
37 entries: Vec<(usize, String)>,
38 },
39 RemoveArray(String),
41 ClearHistory,
43 SetLastExitCode(i32),
45}
46
47#[derive(Debug, Clone, Default)]
49pub struct ExecResult {
50 pub stdout: String,
52 pub stderr: String,
54 pub exit_code: i32,
56 pub control_flow: ControlFlow,
58 pub stdout_truncated: bool,
60 pub stderr_truncated: bool,
62 pub final_env: Option<std::collections::HashMap<String, String>>,
64 pub events: Vec<crate::trace::TraceEvent>,
66 pub side_effects: Vec<BuiltinSideEffect>,
69}
70
71impl ExecResult {
72 pub fn ok(stdout: impl Into<String>) -> Self {
74 Self {
75 stdout: stdout.into(),
76 stderr: String::new(),
77 exit_code: 0,
78 ..Default::default()
79 }
80 }
81
82 pub fn err(stderr: impl Into<String>, exit_code: i32) -> Self {
84 Self {
85 stdout: String::new(),
86 stderr: stderr.into(),
87 exit_code,
88 ..Default::default()
89 }
90 }
91
92 pub fn with_code(stdout: impl Into<String>, exit_code: i32) -> Self {
94 Self {
95 stdout: stdout.into(),
96 stderr: String::new(),
97 exit_code,
98 ..Default::default()
99 }
100 }
101
102 pub fn with_control_flow(control_flow: ControlFlow) -> Self {
104 Self {
105 control_flow,
106 ..Default::default()
107 }
108 }
109
110 pub fn is_success(&self) -> bool {
112 self.exit_code == 0
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
123 fn control_flow_default_is_none() {
124 assert_eq!(ControlFlow::default(), ControlFlow::None);
125 }
126
127 #[test]
128 fn control_flow_break_stores_level() {
129 let cf = ControlFlow::Break(2);
130 assert_eq!(cf, ControlFlow::Break(2));
131 assert_ne!(cf, ControlFlow::Break(1));
132 }
133
134 #[test]
135 fn control_flow_continue_stores_level() {
136 let cf = ControlFlow::Continue(3);
137 assert_eq!(cf, ControlFlow::Continue(3));
138 }
139
140 #[test]
141 fn control_flow_return_stores_code() {
142 let cf = ControlFlow::Return(42);
143 assert_eq!(cf, ControlFlow::Return(42));
144 }
145
146 #[test]
147 fn control_flow_variants_not_equal() {
148 assert_ne!(ControlFlow::None, ControlFlow::Break(0));
149 assert_ne!(ControlFlow::Break(1), ControlFlow::Continue(1));
150 assert_ne!(ControlFlow::Continue(1), ControlFlow::Return(1));
151 }
152
153 #[test]
154 fn control_flow_clone() {
155 let cf = ControlFlow::Return(5);
156 let cloned = cf;
157 assert_eq!(cf, cloned);
158 }
159
160 #[test]
163 fn exec_result_ok_sets_stdout() {
164 let r = ExecResult::ok("hello");
165 assert_eq!(r.stdout, "hello");
166 assert_eq!(r.stderr, "");
167 assert_eq!(r.exit_code, 0);
168 assert_eq!(r.control_flow, ControlFlow::None);
169 assert!(!r.stdout_truncated);
170 assert!(!r.stderr_truncated);
171 }
172
173 #[test]
174 fn exec_result_ok_empty_string() {
175 let r = ExecResult::ok("");
176 assert_eq!(r.stdout, "");
177 assert!(r.is_success());
178 }
179
180 #[test]
181 fn exec_result_ok_accepts_string() {
182 let s = String::from("owned");
183 let r = ExecResult::ok(s);
184 assert_eq!(r.stdout, "owned");
185 }
186
187 #[test]
190 fn exec_result_err_sets_stderr_and_code() {
191 let r = ExecResult::err("bad command", 127);
192 assert_eq!(r.stdout, "");
193 assert_eq!(r.stderr, "bad command");
194 assert_eq!(r.exit_code, 127);
195 assert_eq!(r.control_flow, ControlFlow::None);
196 }
197
198 #[test]
199 fn exec_result_err_is_not_success() {
200 let r = ExecResult::err("fail", 1);
201 assert!(!r.is_success());
202 }
203
204 #[test]
205 fn exec_result_err_with_code_zero_is_success() {
206 let r = ExecResult::err("warning", 0);
208 assert!(r.is_success());
209 }
210
211 #[test]
214 fn exec_result_with_code_sets_stdout_and_code() {
215 let r = ExecResult::with_code("partial", 2);
216 assert_eq!(r.stdout, "partial");
217 assert_eq!(r.stderr, "");
218 assert_eq!(r.exit_code, 2);
219 assert_eq!(r.control_flow, ControlFlow::None);
220 }
221
222 #[test]
223 fn exec_result_with_code_zero() {
224 let r = ExecResult::with_code("ok", 0);
225 assert!(r.is_success());
226 }
227
228 #[test]
229 fn exec_result_with_code_negative() {
230 let r = ExecResult::with_code("", -1);
231 assert!(!r.is_success());
232 assert_eq!(r.exit_code, -1);
233 }
234
235 #[test]
238 fn exec_result_with_control_flow_break() {
239 let r = ExecResult::with_control_flow(ControlFlow::Break(1));
240 assert_eq!(r.stdout, "");
241 assert_eq!(r.stderr, "");
242 assert_eq!(r.exit_code, 0);
243 assert_eq!(r.control_flow, ControlFlow::Break(1));
244 }
245
246 #[test]
247 fn exec_result_with_control_flow_continue() {
248 let r = ExecResult::with_control_flow(ControlFlow::Continue(1));
249 assert_eq!(r.control_flow, ControlFlow::Continue(1));
250 }
251
252 #[test]
253 fn exec_result_with_control_flow_return() {
254 let r = ExecResult::with_control_flow(ControlFlow::Return(0));
255 assert_eq!(r.control_flow, ControlFlow::Return(0));
256 }
257
258 #[test]
259 fn exec_result_with_control_flow_none() {
260 let r = ExecResult::with_control_flow(ControlFlow::None);
261 assert_eq!(r.control_flow, ControlFlow::None);
262 assert!(r.is_success());
263 }
264
265 #[test]
268 fn exec_result_is_success_true_for_zero() {
269 let r = ExecResult::ok("x");
270 assert!(r.is_success());
271 }
272
273 #[test]
274 fn exec_result_is_success_false_for_nonzero() {
275 let r = ExecResult::err("x", 1);
276 assert!(!r.is_success());
277 let r2 = ExecResult::with_code("", 255);
278 assert!(!r2.is_success());
279 }
280
281 #[test]
284 fn exec_result_default() {
285 let r = ExecResult::default();
286 assert_eq!(r.stdout, "");
287 assert_eq!(r.stderr, "");
288 assert_eq!(r.exit_code, 0);
289 assert_eq!(r.control_flow, ControlFlow::None);
290 assert!(!r.stdout_truncated);
291 assert!(!r.stderr_truncated);
292 assert!(r.final_env.is_none());
293 assert!(r.is_success());
294 }
295
296 #[test]
299 fn exec_result_debug_format() {
300 let r = ExecResult::ok("test");
301 let dbg = format!("{:?}", r);
302 assert!(dbg.contains("ExecResult"));
303 assert!(dbg.contains("test"));
304 }
305
306 #[test]
307 fn control_flow_debug_format() {
308 let cf = ControlFlow::Break(3);
309 let dbg = format!("{:?}", cf);
310 assert!(dbg.contains("Break"));
311 assert!(dbg.contains("3"));
312 }
313}