use crate::core::types::ResourceType;
mod exercises;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Coverage {
Exercised,
NotApplicable(&'static str),
}
#[derive(Debug)]
pub struct Outcome {
pub resource_type: String,
pub passed: bool,
pub detail: String,
}
pub const fn coverage(t: &ResourceType) -> Coverage {
match t {
ResourceType::DiskBudget | ResourceType::BackupSync | ResourceType::NasArchive => {
Coverage::Exercised
}
ResourceType::File | ResourceType::Cron => Coverage::Exercised,
ResourceType::Package => Coverage::NotApplicable("mutates system packages"),
ResourceType::Service => Coverage::NotApplicable("mutates systemd on the host"),
ResourceType::Mount => Coverage::NotApplicable("requires mount privileges"),
ResourceType::User => Coverage::NotApplicable("mutates system accounts"),
ResourceType::Docker => Coverage::NotApplicable("requires a docker daemon"),
ResourceType::Pepita => Coverage::NotApplicable("requires namespace privileges"),
ResourceType::Network => Coverage::NotApplicable("mutates firewall rules"),
ResourceType::Gpu => Coverage::NotApplicable("requires GPU hardware"),
ResourceType::Model => Coverage::NotApplicable("downloads multi-GB weights"),
ResourceType::OverlayInterface => Coverage::NotApplicable("binds a host IP"),
ResourceType::GithubRelease => {
Coverage::NotApplicable("network + writes to /usr/local/bin")
}
ResourceType::Image | ResourceType::WasmBundle => {
Coverage::NotApplicable("requires an OCI/wasm toolchain")
}
ResourceType::Build => Coverage::NotApplicable("cross-compiles to another machine"),
ResourceType::Task => Coverage::NotApplicable("runs arbitrary user commands"),
ResourceType::Recipe => Coverage::NotApplicable("expanded before codegen; not a leaf type"),
}
}
pub const ALL_TYPES: &[ResourceType] = &[
ResourceType::Package,
ResourceType::File,
ResourceType::Service,
ResourceType::Mount,
ResourceType::User,
ResourceType::Docker,
ResourceType::Pepita,
ResourceType::Network,
ResourceType::Cron,
ResourceType::Recipe,
ResourceType::Model,
ResourceType::Gpu,
ResourceType::Task,
ResourceType::WasmBundle,
ResourceType::Image,
ResourceType::Build,
ResourceType::GithubRelease,
ResourceType::OverlayInterface,
ResourceType::DiskBudget,
ResourceType::BackupSync,
ResourceType::NasArchive,
];
pub fn run() -> Result<Vec<Outcome>, Vec<Outcome>> {
let mut outcomes = Vec::new();
for t in ALL_TYPES {
if coverage(t) != Coverage::Exercised {
continue;
}
outcomes.push(exercises::run_for(t));
}
if outcomes.iter().all(|o| o.passed) {
Ok(outcomes)
} else {
Err(outcomes)
}
}
pub fn not_applicable() -> Vec<(String, &'static str)> {
ALL_TYPES
.iter()
.filter_map(|t| match coverage(t) {
Coverage::NotApplicable(why) => Some((t.to_string(), why)),
Coverage::Exercised => None,
})
.collect()
}
pub fn report(json: bool) -> Result<(), String> {
let result = run();
let outcomes = match &result {
Ok(o) | Err(o) => o,
};
if json {
let items: Vec<String> = outcomes
.iter()
.map(|o| {
format!(
"{{\"type\":\"{}\",\"passed\":{},\"detail\":{:?}}}",
o.resource_type, o.passed, o.detail
)
})
.collect();
println!("{{\"dogfood\":[{}]}}", items.join(","));
} else {
for o in outcomes {
let mark = if o.passed { "PASS" } else { "FAIL" };
println!("{mark} {:<18} {}", o.resource_type, o.detail);
}
let debt = not_applicable();
if !debt.is_empty() {
println!("\nnot exercised ({} types):", debt.len());
for (name, why) in debt {
println!(" {name:<18} {why}");
}
}
}
match result {
Ok(_) => Ok(()),
Err(o) => Err(format!(
"{} dogfood exercise(s) failed",
o.iter().filter(|x| !x.passed).count()
)),
}
}