use serde::{Deserialize, Serialize};
use tatara_lisp::DeriveTataraDomain;
use crate::SpecError;
#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone)]
#[tatara(keyword = "defnix-replacement-surface")]
pub struct NixReplacementSurface {
pub name: String,
pub category: WorkloadCategory,
pub status: CoverageStatus,
pub owns: String,
pub notes: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WorkloadCategory {
Storage,
SubstrateL1,
EvalEngine,
Derivation,
Build,
Fetcher,
Substituter,
Daemon,
SystemRebuild,
Convenience,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CoverageStatus {
NotStarted,
Queued,
InProgress,
Done,
}
impl CoverageStatus {
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::NotStarted => "NotStarted",
Self::Queued => "Queued",
Self::InProgress => "InProgress",
Self::Done => "Done",
}
}
}
pub const CANONICAL_NIX_REPLACEMENT_COVERAGE_LISP: &str =
include_str!("../specs/nix_replacement_coverage.lisp");
pub fn load_canonical() -> Result<Vec<NixReplacementSurface>, SpecError> {
crate::loader::load_all::<NixReplacementSurface>(CANONICAL_NIX_REPLACEMENT_COVERAGE_LISP)
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CoverageHistogram {
pub done: u32,
pub in_progress: u32,
pub queued: u32,
pub not_started: u32,
pub total: u32,
}
impl CoverageHistogram {
#[must_use]
pub fn from_surfaces(surfaces: &[NixReplacementSurface]) -> Self {
let mut h = Self::default();
for s in surfaces {
h.total += 1;
match s.status {
CoverageStatus::Done => h.done += 1,
CoverageStatus::InProgress => h.in_progress += 1,
CoverageStatus::Queued => h.queued += 1,
CoverageStatus::NotStarted => h.not_started += 1,
}
}
h
}
#[must_use]
pub fn done_fraction(&self) -> f32 {
if self.total == 0 {
0.0
} else {
self.done as f32 / self.total as f32
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn catalog_loads() {
let cat = load_canonical().expect("catalog parses");
let categories: std::collections::HashSet<_> = cat.iter().map(|s| s.category).collect();
assert!(categories.contains(&WorkloadCategory::Storage));
assert!(categories.contains(&WorkloadCategory::SubstrateL1));
assert!(categories.contains(&WorkloadCategory::EvalEngine));
assert!(categories.contains(&WorkloadCategory::Derivation));
assert!(categories.contains(&WorkloadCategory::Fetcher));
assert!(categories.contains(&WorkloadCategory::Substituter));
assert!(categories.contains(&WorkloadCategory::Daemon));
assert!(categories.contains(&WorkloadCategory::SystemRebuild));
}
#[test]
fn histogram_sums_to_total() {
let cat = load_canonical().unwrap();
let h = CoverageHistogram::from_surfaces(&cat);
assert_eq!(h.total, cat.len() as u32);
assert_eq!(
h.done + h.in_progress + h.queued + h.not_started,
h.total
);
}
#[test]
fn done_fraction_in_range() {
let cat = load_canonical().unwrap();
let h = CoverageHistogram::from_surfaces(&cat);
let f = h.done_fraction();
assert!((0.0..=1.0).contains(&f));
}
#[test]
fn every_surface_has_owns_pointer() {
let cat = load_canonical().unwrap();
for s in &cat {
assert!(!s.owns.is_empty(), "row {} missing :owns", s.name);
}
}
#[test]
fn lockfile_graph_is_done() {
let cat = load_canonical().unwrap();
let row = cat
.iter()
.find(|s| s.name == "lockfile-graph")
.expect("lockfile-graph row");
assert!(matches!(row.status, CoverageStatus::Done));
}
#[test]
fn daemon_graph_protocol_is_done() {
let cat = load_canonical().unwrap();
let row = cat
.iter()
.find(|s| s.name == "daemon-graph-protocol-native")
.expect("daemon-graph-protocol-native row");
assert!(matches!(row.status, CoverageStatus::Done));
}
}