use std::io::Write;
use crate::{
diag::Diagnostic,
start::{BootFrame, Outcome},
};
#[derive(Debug, Default)]
pub struct BootReport {
pub started: usize,
pub failures: Vec<Diagnostic>,
}
impl BootReport {
pub fn all_ok(&self) -> bool {
self.failures.is_empty()
}
}
pub fn render_boot<W: Write>(
frames: impl IntoIterator<Item = BootFrame>,
verbose: bool,
mut out: W,
) -> BootReport {
let mut report = BootReport::default();
for frame in frames {
match frame {
BootFrame::UnitStarting { service, .. } => {
if verbose {
let _ = writeln!(out, "Starting {service}...");
}
}
BootFrame::Unit {
service, outcome, ..
} => match outcome {
Outcome::Up(live) => {
report.started += 1;
if verbose {
let _ = writeln!(out, " \u{2713} {service} [pid {}]", live.pid);
}
}
Outcome::Completed => {
report.started += 1;
if verbose {
let _ = writeln!(out, " \u{2713} {service} completed");
}
}
Outcome::Skipped => {
if verbose {
let _ = writeln!(out, " \u{2013} {service} skipped");
}
}
Outcome::Stopped => {
if verbose {
let _ = writeln!(out, " \u{2714} {service} stopped");
}
}
Outcome::Failed(diag) => {
if verbose {
let _ = writeln!(
out,
" \u{2717} {service} \u{2014} {}: {}",
diag.code_str(),
diag.title
);
}
report.failures.push(diag);
}
},
BootFrame::UnitStep { .. } => {}
BootFrame::Done { .. } => {}
}
}
report
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
diag::SgCode,
start::{Liveness, outcome},
};
fn starting(service: &str) -> BootFrame {
BootFrame::UnitStarting {
project: "p".into(),
service: service.into(),
}
}
fn up(service: &str, pid: u32) -> BootFrame {
BootFrame::Unit {
project: "p".into(),
service: service.into(),
outcome: Outcome::Up(Liveness { pid }),
}
}
fn failed(service: &str) -> BootFrame {
BootFrame::Unit {
project: "p".into(),
service: service.into(),
outcome: Outcome::Failed(outcome::immediate_exit(service, Some(1))),
}
}
#[test]
fn verbose_prints_a_line_per_service() {
let frames = vec![starting("web"), up("web", 42), starting("db"), up("db", 43)];
let mut buf = Vec::new();
let report = render_boot(frames, true, &mut buf);
let text = String::from_utf8(buf).unwrap();
assert!(text.contains("Starting web..."));
assert!(text.contains("web [pid 42]"));
assert!(text.contains("Starting db..."));
assert_eq!(report.started, 2);
assert!(report.all_ok());
}
#[test]
fn quiet_prints_nothing_but_still_reports() {
let frames = vec![starting("web"), up("web", 42), failed("worker")];
let mut buf = Vec::new();
let report = render_boot(frames, false, &mut buf);
assert!(buf.is_empty());
assert_eq!(report.started, 1);
assert_eq!(report.failures.len(), 1);
assert!(!report.all_ok());
}
#[test]
fn failures_collect_their_diagnostics() {
let frames = vec![
failed("worker"),
BootFrame::Done {
started: 0,
failed: 1,
},
];
let mut buf = Vec::new();
let report = render_boot(frames, true, &mut buf);
assert_eq!(report.failures.len(), 1);
assert_eq!(report.failures[0].code, SgCode::UnitImmediateExit);
let text = String::from_utf8(buf).unwrap();
assert!(text.contains("SG0102"));
}
}