use std::fmt;
pub const HIL_OK_MARKER: &str = "HIL OK";
pub const HIL_FAIL_MARKER: &str = "HIL FAIL";
const UART_LINE_TAG: &str = ": [host: ";
#[derive(Debug, Clone)]
pub struct HilOutput {
stdout: String,
stderr: String,
exit_code: Option<i32>,
timed_out: bool,
}
impl HilOutput {
#[must_use]
pub fn new(stdout: String, stderr: String, exit_code: Option<i32>, timed_out: bool) -> Self {
Self {
stdout,
stderr,
exit_code,
timed_out,
}
}
#[must_use]
pub fn passed(&self) -> bool {
self.contains(HIL_OK_MARKER) && !self.failed()
}
#[must_use]
pub fn failed(&self) -> bool {
self.contains(HIL_FAIL_MARKER)
}
#[must_use]
pub fn contains(&self, marker: &str) -> bool {
self.stdout.contains(marker) || self.stderr.contains(marker) || self.uart().contains(marker)
}
#[must_use]
pub fn uart(&self) -> String {
let mut text = String::new();
for line in self.stdout.lines() {
let Some(i) = line.find(UART_LINE_TAG) else {
continue;
};
let Some(j) = line[i..].find("] ") else {
continue;
};
text.push_str(&line[i + j + 2..]);
text.push('\n');
}
text
}
#[must_use]
pub fn stdout(&self) -> &str {
&self.stdout
}
#[must_use]
pub fn stderr(&self) -> &str {
&self.stderr
}
#[must_use]
pub fn combined(&self) -> String {
format!("{}\n{}", self.stdout, self.stderr)
}
#[must_use]
pub fn exit_code(&self) -> Option<i32> {
self.exit_code
}
#[must_use]
pub fn timed_out(&self) -> bool {
self.timed_out
}
}
impl fmt::Display for HilOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let code = match self.exit_code {
Some(c) => c.to_string(),
None => "none".to_string(),
};
write!(
f,
"exit={code}\ntimed_out={}\n--- stdout ---\n{}\n--- stderr ---\n{}",
self.timed_out, self.stdout, self.stderr
)
}
}
#[macro_export]
macro_rules! assert_hil_ok {
($output:expr) => {
$crate::assert_hil_ok!($output, $crate::HIL_OK_MARKER)
};
($output:expr, $marker:expr) => {
assert!(
!$output.failed(),
"HIL firmware reported FAILURE (fail marker or panic).\n{}",
$output,
);
assert!(
$output.contains($marker),
"HIL test failed -- expected {:?}.\n{}",
$marker,
$output,
)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn passed_detects_marker_in_either_stream() {
assert!(HilOutput::new("boot\nHIL OK\n".into(), String::new(), Some(0), false).passed());
assert!(HilOutput::new(String::new(), "HIL OK".into(), Some(0), false).passed());
assert!(!HilOutput::new("timeout".into(), String::new(), Some(1), false).passed());
}
#[test]
fn fail_marker_overrides_pass() {
let o = HilOutput::new(
"HIL OK\nHIL FAIL (panic)".into(),
String::new(),
Some(0),
false,
);
assert!(o.failed());
assert!(!o.passed());
}
#[test]
#[should_panic(expected = "reported FAILURE")]
fn assert_macro_reports_failure() {
let o = HilOutput::new("HIL FAIL".into(), String::new(), Some(0), false);
assert_hil_ok!(o);
}
#[test]
fn uart_strips_analyzer_prefix() {
let log = "20:11:03.3151 [INFO] uart0: [host: 34.36ms (+34.36ms)|virt: 0s (+0s)] hello\n\
20:11:03.3338 [WARNING] cpu: HIL OK\n\
20:11:03.3400 [INFO] hil/uart0: [host: 35ms (+1ms)|virt: 0s (+0s)] world";
let o = HilOutput::new(log.into(), String::new(), Some(0), false);
assert_eq!(o.uart(), "hello\nworld\n");
assert!(o.contains("hello\nworld"));
}
#[test]
fn contains_and_combined() {
let o = HilOutput::new("hello".into(), "world".into(), Some(0), false);
assert!(o.contains("hello"));
assert!(o.contains("world"));
assert_eq!(o.combined(), "hello\nworld");
}
#[test]
fn display_includes_code_and_streams() {
let s = HilOutput::new("hi".into(), "there".into(), Some(42), false).to_string();
assert!(s.contains("42"));
assert!(s.contains("hi"));
assert!(s.contains("there"));
let none = HilOutput::new(String::new(), String::new(), None, false).to_string();
assert!(none.contains("exit=none"));
}
#[test]
fn display_includes_timed_out() {
let s = HilOutput::new(String::new(), String::new(), None, true).to_string();
assert!(s.contains("timed_out=true"));
}
#[test]
fn assert_macro_passes() {
let o = HilOutput::new("HIL OK".into(), String::new(), Some(0), false);
assert_hil_ok!(o);
let o2 = HilOutput::new("CUSTOM".into(), String::new(), Some(0), false);
assert_hil_ok!(o2, "CUSTOM");
}
}