use crate::system_check::check::SystemCheck;
use crate::system_check::report::{CheckResult, CheckStatus, SystemCheckReport};
pub fn run(slices: &[&'static [&'static dyn SystemCheck]]) -> SystemCheckReport {
let mut report = SystemCheckReport::default();
for slice in slices {
for &check in *slice {
let result = run_one(check);
report.results.push(result);
}
}
report
}
fn run_one(check: &'static dyn SystemCheck) -> CheckResult {
let guard = std::panic::AssertUnwindSafe(check);
match std::panic::catch_unwind(|| {
let check = *guard;
check.run()
}) {
Ok(result) => result,
Err(_) => CheckResult {
id: check.id(),
category: check.category(),
severity: check.severity(),
status: CheckStatus::Fail,
description: check.description(),
context: format!(
"check {} panicked during run; the check is buggy and must be fixed",
check.id()
),
fix_hint: Some("report this as a bug in the contributing crate".to_string()),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::system_check::category::CheckCategory;
use crate::system_check::id::CheckId;
use crate::system_check::report::CheckResult;
use crate::system_check::severity::CheckSeverity;
struct PassCheck {
id: &'static str,
}
impl SystemCheck for PassCheck {
fn id(&self) -> CheckId {
CheckId::new_unchecked(self.id)
}
fn severity(&self) -> CheckSeverity {
CheckSeverity::Info
}
fn category(&self) -> CheckCategory {
CheckCategory::Framework
}
fn description(&self) -> &'static str {
"a passing check"
}
fn run(&self) -> CheckResult {
CheckResult {
id: self.id(),
category: self.category(),
severity: self.severity(),
status: CheckStatus::Pass,
description: self.description(),
context: "ok".to_string(),
fix_hint: None,
}
}
}
struct FailCheck {
id: &'static str,
severity: CheckSeverity,
}
impl SystemCheck for FailCheck {
fn id(&self) -> CheckId {
CheckId::new_unchecked(self.id)
}
fn severity(&self) -> CheckSeverity {
self.severity
}
fn category(&self) -> CheckCategory {
CheckCategory::Security
}
fn description(&self) -> &'static str {
"a failing check"
}
fn run(&self) -> CheckResult {
CheckResult {
id: self.id(),
category: self.category(),
severity: self.severity(),
status: CheckStatus::Fail,
description: self.description(),
context: "something is wrong".to_string(),
fix_hint: Some("fix it".to_string()),
}
}
}
struct SkipCheck;
impl SystemCheck for SkipCheck {
fn id(&self) -> CheckId {
CheckId::new_unchecked("ARC0003")
}
fn severity(&self) -> CheckSeverity {
CheckSeverity::Info
}
fn category(&self) -> CheckCategory {
CheckCategory::Database
}
fn description(&self) -> &'static str {
"a skipped check"
}
fn run(&self) -> CheckResult {
CheckResult {
id: self.id(),
category: self.category(),
severity: self.severity(),
status: CheckStatus::Skip,
description: self.description(),
context: "database not enabled".to_string(),
fix_hint: None,
}
}
}
#[test]
fn run_aggregates_multiple_slices_in_order() {
let slice_a: &'static [&'static dyn SystemCheck] = &[&PassCheck { id: "ARC0001" }];
let slice_b: &'static [&'static dyn SystemCheck] = &[
&FailCheck {
id: "ARC0002",
severity: CheckSeverity::Warning,
},
&SkipCheck,
];
let report = run(&[slice_a, slice_b]);
assert_eq!(report.len(), 3);
assert_eq!(report.results[0].id.as_str(), "ARC0001");
assert_eq!(report.results[1].id.as_str(), "ARC0002");
assert_eq!(report.results[2].id.as_str(), "ARC0003");
}
#[test]
fn run_does_not_abort_on_a_failing_check() {
let slice: &'static [&'static dyn SystemCheck] = &[
&FailCheck {
id: "ARC0002",
severity: CheckSeverity::Error,
},
&PassCheck { id: "ARC0001" },
];
let report = run(&[slice]);
assert_eq!(report.len(), 2);
assert!(report.has_error());
assert_eq!(report.results[1].status, CheckStatus::Pass);
}
#[test]
fn run_has_error_only_for_error_severity_fails() {
let slice: &'static [&'static dyn SystemCheck] = &[&FailCheck {
id: "ARC0002",
severity: CheckSeverity::Warning,
}];
let report = run(&[slice]);
assert!(!report.has_error());
assert!(report.has_failure());
}
#[test]
fn run_with_no_slices_is_empty() {
let report = run(&[]);
assert!(report.is_empty());
}
}