sui_spec/
nix_replacement_coverage.rs1use serde::{Deserialize, Serialize};
35use tatara_lisp::DeriveTataraDomain;
36
37use crate::SpecError;
38
39#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone)]
41#[tatara(keyword = "defnix-replacement-surface")]
42pub struct NixReplacementSurface {
43 pub name: String,
45 pub category: WorkloadCategory,
47 pub status: CoverageStatus,
49 pub owns: String,
52 pub notes: String,
54}
55
56#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum WorkloadCategory {
59 Storage,
61 SubstrateL1,
63 EvalEngine,
65 Derivation,
67 Build,
69 Fetcher,
71 Substituter,
73 Daemon,
76 SystemRebuild,
78 Convenience,
81}
82
83#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
85pub enum CoverageStatus {
86 NotStarted,
88 Queued,
90 InProgress,
92 Done,
95}
96
97impl CoverageStatus {
98 #[must_use]
100 pub fn name(self) -> &'static str {
101 match self {
102 Self::NotStarted => "NotStarted",
103 Self::Queued => "Queued",
104 Self::InProgress => "InProgress",
105 Self::Done => "Done",
106 }
107 }
108}
109
110pub const CANONICAL_NIX_REPLACEMENT_COVERAGE_LISP: &str =
111 include_str!("../specs/nix_replacement_coverage.lisp");
112
113pub fn load_canonical() -> Result<Vec<NixReplacementSurface>, SpecError> {
119 crate::loader::load_all::<NixReplacementSurface>(CANONICAL_NIX_REPLACEMENT_COVERAGE_LISP)
120}
121
122#[derive(Debug, Default, Clone, Copy)]
124pub struct CoverageHistogram {
125 pub done: u32,
126 pub in_progress: u32,
127 pub queued: u32,
128 pub not_started: u32,
129 pub total: u32,
130}
131
132impl CoverageHistogram {
133 #[must_use]
135 pub fn from_surfaces(surfaces: &[NixReplacementSurface]) -> Self {
136 let mut h = Self::default();
137 for s in surfaces {
138 h.total += 1;
139 match s.status {
140 CoverageStatus::Done => h.done += 1,
141 CoverageStatus::InProgress => h.in_progress += 1,
142 CoverageStatus::Queued => h.queued += 1,
143 CoverageStatus::NotStarted => h.not_started += 1,
144 }
145 }
146 h
147 }
148
149 #[must_use]
151 pub fn done_fraction(&self) -> f32 {
152 if self.total == 0 {
153 0.0
154 } else {
155 self.done as f32 / self.total as f32
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use pretty_assertions::assert_eq;
164
165 #[test]
166 fn catalog_loads() {
167 let cat = load_canonical().expect("catalog parses");
168 let categories: std::collections::HashSet<_> = cat.iter().map(|s| s.category).collect();
170 assert!(categories.contains(&WorkloadCategory::Storage));
171 assert!(categories.contains(&WorkloadCategory::SubstrateL1));
172 assert!(categories.contains(&WorkloadCategory::EvalEngine));
173 assert!(categories.contains(&WorkloadCategory::Derivation));
174 assert!(categories.contains(&WorkloadCategory::Fetcher));
175 assert!(categories.contains(&WorkloadCategory::Substituter));
176 assert!(categories.contains(&WorkloadCategory::Daemon));
177 assert!(categories.contains(&WorkloadCategory::SystemRebuild));
178 }
179
180 #[test]
181 fn histogram_sums_to_total() {
182 let cat = load_canonical().unwrap();
183 let h = CoverageHistogram::from_surfaces(&cat);
184 assert_eq!(h.total, cat.len() as u32);
185 assert_eq!(
186 h.done + h.in_progress + h.queued + h.not_started,
187 h.total
188 );
189 }
190
191 #[test]
192 fn done_fraction_in_range() {
193 let cat = load_canonical().unwrap();
194 let h = CoverageHistogram::from_surfaces(&cat);
195 let f = h.done_fraction();
196 assert!((0.0..=1.0).contains(&f));
197 }
198
199 #[test]
200 fn every_surface_has_owns_pointer() {
201 let cat = load_canonical().unwrap();
202 for s in &cat {
203 assert!(!s.owns.is_empty(), "row {} missing :owns", s.name);
204 }
205 }
206
207 #[test]
208 fn lockfile_graph_is_done() {
209 let cat = load_canonical().unwrap();
210 let row = cat
211 .iter()
212 .find(|s| s.name == "lockfile-graph")
213 .expect("lockfile-graph row");
214 assert!(matches!(row.status, CoverageStatus::Done));
215 }
216
217 #[test]
218 fn daemon_graph_protocol_is_done() {
219 let cat = load_canonical().unwrap();
220 let row = cat
221 .iter()
222 .find(|s| s.name == "daemon-graph-protocol-native")
223 .expect("daemon-graph-protocol-native row");
224 assert!(matches!(row.status, CoverageStatus::Done));
225 }
226}