use super::types::{Check, CheckResult};
pub struct Doctor {
checks: Vec<Box<dyn Check>>,
}
impl Doctor {
pub fn new() -> Self {
Self {
checks: super::checks::default_checks(),
}
}
pub fn run_all(&self) -> Vec<CheckResult> {
self.checks.iter().flat_map(|c| c.run()).collect()
}
pub fn run_and_fix(&self, output: &crate::output::Output) -> Vec<CheckResult> {
let mut all_results = Vec::new();
for check in &self.checks {
let results = check.run();
for result in results {
if result.is_error() {
if let Some(fixed_result) = check.fix(&result, output) {
output.doctor_check(&fixed_result);
all_results.push(fixed_result);
continue;
}
}
output.doctor_check(&result);
all_results.push(result);
}
}
all_results
}
}
impl Default for Doctor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct FixableCheck;
impl Check for FixableCheck {
fn id(&self) -> &'static str {
"fixable"
}
fn name(&self) -> &'static str {
"fixable check"
}
fn run(&self) -> Vec<CheckResult> {
vec![CheckResult::error("fixable", "fixable check", "boom")]
}
fn fix(
&self,
result: &CheckResult,
_output: &crate::output::Output,
) -> Option<CheckResult> {
if result.is_error() {
Some(CheckResult::ok("fixable", "fixable check"))
} else {
None
}
}
}
struct UnfixableCheck;
impl Check for UnfixableCheck {
fn id(&self) -> &'static str {
"unfixable"
}
fn name(&self) -> &'static str {
"unfixable check"
}
fn run(&self) -> Vec<CheckResult> {
vec![CheckResult::error(
"unfixable",
"unfixable check",
"still broken",
)]
}
}
fn quiet_output() -> crate::output::Output {
crate::output::Output::new(0, true, true, false)
}
#[test]
fn run_all_collects_every_check_result_without_fixing() {
let doctor = Doctor {
checks: vec![Box::new(FixableCheck), Box::new(UnfixableCheck)],
};
let results = doctor.run_all();
assert_eq!(results.len(), 2);
assert!(results.iter().any(|r| r.id == "fixable" && r.is_error()));
assert!(results.iter().any(|r| r.id == "unfixable" && r.is_error()));
}
#[test]
fn run_and_fix_replaces_error_when_fix_returns_some() {
let doctor = Doctor {
checks: vec![Box::new(FixableCheck)],
};
let results = doctor.run_and_fix(&quiet_output());
assert_eq!(results.len(), 1);
assert!(
results[0].is_ok(),
"a fixable error must be replaced by the fixed ok result: {:#?}",
results[0]
);
}
#[test]
fn run_and_fix_keeps_raw_error_when_fix_returns_none() {
let doctor = Doctor {
checks: vec![Box::new(UnfixableCheck)],
};
let results = doctor.run_and_fix(&quiet_output());
assert_eq!(results.len(), 1);
assert!(
results[0].is_error(),
"an unfixable error must pass through unchanged"
);
}
#[test]
fn run_and_fix_dispatches_fix_per_check() {
let doctor = Doctor {
checks: vec![Box::new(FixableCheck), Box::new(UnfixableCheck)],
};
let results = doctor.run_and_fix(&quiet_output());
assert_eq!(results.len(), 2);
assert!(
results.iter().any(|r| r.id == "fixable" && r.is_ok()),
"fixable check's error should be fixed"
);
assert!(
results.iter().any(|r| r.id == "unfixable" && r.is_error()),
"unfixable check's error should remain"
);
}
#[test]
fn test_doctor_has_default_checks() {
let doctor = Doctor::new();
assert!(!doctor.checks.is_empty());
}
#[test]
fn doctor_registers_legacy_check() {
let doctor = Doctor::new();
assert!(
doctor.checks.iter().any(|c| c.id() == "legacy"),
"Doctor::new() must register the legacy check"
);
}
}