cargo_image_runner/runner/
io.rs1#[derive(Debug)]
17pub enum IoAction {
18 Continue,
20 SendInput(Vec<u8>),
22 Shutdown,
24}
25
26#[derive(Debug, Clone)]
28pub struct CapturedIo {
29 pub serial: Vec<u8>,
31 pub stderr: Vec<u8>,
33}
34
35pub trait IoHandler: Send {
40 fn on_output(&mut self, data: &[u8]) -> IoAction {
42 let _ = data;
43 IoAction::Continue
44 }
45
46 fn on_stderr(&mut self, data: &[u8]) {
48 let _ = data;
49 }
50
51 fn on_exit(&mut self, exit_code: i32, timed_out: bool) {
53 let _ = (exit_code, timed_out);
54 }
55
56 fn on_start(&mut self, command: &std::process::Command) {
58 let _ = command;
59 }
60
61 fn finish(self: Box<Self>) -> Option<CapturedIo> {
63 None
64 }
65}
66
67#[derive(Debug, Default)]
79pub struct CaptureHandler {
80 serial: Vec<u8>,
81 stderr: Vec<u8>,
82}
83
84impl CaptureHandler {
85 pub fn new() -> Self {
87 Self::default()
88 }
89}
90
91impl IoHandler for CaptureHandler {
92 fn on_output(&mut self, data: &[u8]) -> IoAction {
93 self.serial.extend_from_slice(data);
94 IoAction::Continue
95 }
96
97 fn on_stderr(&mut self, data: &[u8]) {
98 self.stderr.extend_from_slice(data);
99 }
100
101 fn finish(self: Box<Self>) -> Option<CapturedIo> {
102 Some(CapturedIo {
103 serial: self.serial,
104 stderr: self.stderr,
105 })
106 }
107}
108
109#[derive(Debug, Default)]
113pub struct TeeHandler {
114 capture: CaptureHandler,
115}
116
117impl TeeHandler {
118 pub fn new() -> Self {
120 Self::default()
121 }
122}
123
124impl IoHandler for TeeHandler {
125 fn on_output(&mut self, data: &[u8]) -> IoAction {
126 use std::io::Write;
127 let _ = std::io::stdout().write_all(data);
128 self.capture.on_output(data)
129 }
130
131 fn on_stderr(&mut self, data: &[u8]) {
132 use std::io::Write;
133 let _ = std::io::stderr().write_all(data);
134 self.capture.on_stderr(data);
135 }
136
137 fn finish(self: Box<Self>) -> Option<CapturedIo> {
138 Box::new(self.capture).finish()
139 }
140}
141
142#[derive(Debug, Clone)]
144struct PatternRule {
145 pattern: String,
146 response: Vec<u8>,
147}
148
149#[derive(Debug, Default)]
163pub struct PatternResponder {
164 rules: Vec<PatternRule>,
165 buffer: Vec<u8>,
167 capture: CaptureHandler,
168}
169
170impl PatternResponder {
171 pub fn new() -> Self {
173 Self::default()
174 }
175
176 pub fn on_pattern(mut self, pattern: &str, response: &[u8]) -> Self {
179 self.rules.push(PatternRule {
180 pattern: pattern.to_string(),
181 response: response.to_vec(),
182 });
183 self
184 }
185}
186
187impl IoHandler for PatternResponder {
188 fn on_output(&mut self, data: &[u8]) -> IoAction {
189 self.capture.on_output(data);
190 self.buffer.extend_from_slice(data);
191
192 let max_pattern_len = self.rules.iter().map(|r| r.pattern.len()).max().unwrap_or(0);
194 let max_buf = max_pattern_len.max(4096);
195 if self.buffer.len() > max_buf * 2 {
196 let drain = self.buffer.len() - max_buf;
197 self.buffer.drain(..drain);
198 }
199
200 let buf_str = String::from_utf8_lossy(&self.buffer);
202 for rule in &self.rules {
203 if buf_str.contains(&rule.pattern) {
204 self.buffer.clear();
206 return IoAction::SendInput(rule.response.clone());
207 }
208 }
209
210 IoAction::Continue
211 }
212
213 fn on_stderr(&mut self, data: &[u8]) {
214 self.capture.on_stderr(data);
215 }
216
217 fn finish(self: Box<Self>) -> Option<CapturedIo> {
218 Box::new(self.capture).finish()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_capture_handler_accumulates() {
228 let mut handler = CaptureHandler::new();
229 handler.on_output(b"hello ");
230 handler.on_output(b"world");
231 handler.on_stderr(b"err1");
232 handler.on_stderr(b"err2");
233
234 let captured = Box::new(handler).finish().unwrap();
235 assert_eq!(captured.serial, b"hello world");
236 assert_eq!(captured.stderr, b"err1err2");
237 }
238
239 #[test]
240 fn test_capture_handler_empty() {
241 let handler = CaptureHandler::new();
242 let captured = Box::new(handler).finish().unwrap();
243 assert!(captured.serial.is_empty());
244 assert!(captured.stderr.is_empty());
245 }
246
247 #[test]
248 fn test_tee_handler_captures() {
249 let mut handler = TeeHandler::new();
250 handler.on_output(b"data");
251 handler.on_stderr(b"err");
252
253 let captured = Box::new(handler).finish().unwrap();
254 assert_eq!(captured.serial, b"data");
255 assert_eq!(captured.stderr, b"err");
256 }
257
258 #[test]
259 fn test_pattern_responder_matches() {
260 let mut handler = PatternResponder::new()
261 .on_pattern("login:", b"root\n")
262 .on_pattern("$ ", b"ls\n");
263
264 let action = handler.on_output(b"booting...\n");
266 assert!(matches!(action, IoAction::Continue));
267
268 let action = handler.on_output(b"login:");
270 match action {
271 IoAction::SendInput(data) => assert_eq!(data, b"root\n"),
272 other => panic!("expected SendInput, got {:?}", other),
273 }
274
275 let action = handler.on_output(b"root@host:~$ ");
277 match action {
278 IoAction::SendInput(data) => assert_eq!(data, b"ls\n"),
279 other => panic!("expected SendInput, got {:?}", other),
280 }
281 }
282
283 #[test]
284 fn test_pattern_responder_captures() {
285 let mut handler = PatternResponder::new().on_pattern("x", b"y");
286 handler.on_output(b"abc");
287 handler.on_stderr(b"err");
288
289 let captured = Box::new(handler).finish().unwrap();
290 assert_eq!(captured.serial, b"abc");
291 assert_eq!(captured.stderr, b"err");
292 }
293
294 #[test]
295 fn test_pattern_responder_no_rules() {
296 let mut handler = PatternResponder::new();
297 let action = handler.on_output(b"anything");
298 assert!(matches!(action, IoAction::Continue));
299 }
300
301 #[test]
302 fn test_default_io_handler_noop() {
303 struct Noop;
304 impl IoHandler for Noop {}
305
306 let mut handler = Noop;
307 let action = handler.on_output(b"data");
308 assert!(matches!(action, IoAction::Continue));
309 handler.on_stderr(b"err");
310 handler.on_exit(0, false);
311 assert!(Box::new(handler).finish().is_none());
312 }
313}