Skip to main content

cargo_image_runner/runner/
mod.rs

1//! Runner trait and QEMU implementation for executing bootable images.
2
3use crate::core::context::Context;
4use crate::core::error::Result;
5use std::path::Path;
6
7pub mod io;
8
9// Runner implementations
10#[cfg(feature = "qemu")]
11pub mod qemu;
12
13/// Runner trait for executing images.
14pub trait Runner: Send + Sync {
15    /// Execute the image.
16    ///
17    /// Returns information about the run result.
18    fn run(&self, ctx: &Context, image_path: &Path) -> Result<RunResult>;
19
20    /// Run with an I/O handler for serial capture/streaming.
21    ///
22    /// The handler receives callbacks for serial output, stderr, and process lifecycle.
23    /// Default implementation ignores the handler and delegates to [`run()`](Self::run).
24    fn run_with_io(
25        &self,
26        ctx: &Context,
27        image_path: &Path,
28        handler: &mut dyn io::IoHandler,
29    ) -> Result<RunResult> {
30        let _ = handler;
31        self.run(ctx, image_path)
32    }
33
34    /// Check if the runner is available on the system.
35    fn is_available(&self) -> bool;
36
37    /// Validate runner configuration.
38    fn validate(&self, ctx: &Context) -> Result<()> {
39        if !self.is_available() {
40            return Err(crate::core::error::Error::runner(format!(
41                "{} is not available on this system",
42                self.name()
43            )));
44        }
45
46        let _ = ctx;
47        Ok(())
48    }
49
50    /// Get a human-readable name for this runner.
51    fn name(&self) -> &str;
52}
53
54/// Result of running an image.
55#[derive(Debug)]
56pub struct RunResult {
57    /// Exit code from the runner.
58    pub exit_code: i32,
59
60    /// Whether the run was considered successful.
61    pub success: bool,
62
63    /// Captured stdout/stderr output (populated in test mode).
64    pub captured_output: Option<CapturedOutput>,
65
66    /// Whether the run was terminated due to a timeout.
67    pub timed_out: bool,
68}
69
70/// Captured stdout and stderr from a runner execution.
71#[non_exhaustive]
72#[derive(Debug, Clone)]
73pub struct CapturedOutput {
74    pub stdout: String,
75    pub stderr: String,
76    /// Captured serial output (populated when an [`io::IoHandler`] is used).
77    pub serial: Option<String>,
78}
79
80impl RunResult {
81    /// Create a new run result.
82    pub fn new(exit_code: i32, success: bool) -> Self {
83        Self {
84            exit_code,
85            success,
86            captured_output: None,
87            timed_out: false,
88        }
89    }
90
91    /// Create a successful result with exit code 0.
92    pub fn success() -> Self {
93        Self {
94            exit_code: 0,
95            success: true,
96            captured_output: None,
97            timed_out: false,
98        }
99    }
100
101    /// Create a failed result with the given exit code.
102    pub fn failed(exit_code: i32) -> Self {
103        Self {
104            exit_code,
105            success: false,
106            captured_output: None,
107            timed_out: false,
108        }
109    }
110
111    /// Attach captured output to the result.
112    pub fn with_output(mut self, stdout: String, stderr: String) -> Self {
113        self.captured_output = Some(CapturedOutput {
114            stdout,
115            stderr,
116            serial: None,
117        });
118        self
119    }
120
121    /// Attach captured serial output from an I/O handler.
122    pub fn with_serial(mut self, serial: String) -> Self {
123        if let Some(ref mut output) = self.captured_output {
124            output.serial = Some(serial);
125        } else {
126            self.captured_output = Some(CapturedOutput {
127                stdout: String::new(),
128                stderr: String::new(),
129                serial: Some(serial),
130            });
131        }
132        self
133    }
134
135    /// Mark the result as timed out.
136    pub fn with_timeout(mut self) -> Self {
137        self.timed_out = true;
138        self
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_run_result_success() {
148        let result = RunResult::success();
149        assert_eq!(result.exit_code, 0);
150        assert!(result.success);
151    }
152
153    #[test]
154    fn test_run_result_failed() {
155        let result = RunResult::failed(1);
156        assert_eq!(result.exit_code, 1);
157        assert!(!result.success);
158    }
159
160    #[test]
161    fn test_run_result_custom() {
162        let result = RunResult::new(33, true);
163        assert_eq!(result.exit_code, 33);
164        assert!(result.success);
165    }
166}