nmbrs_runtime/readouts/builtins/
session_summary.rs1use std::fmt::Write as _;
14
15use crate::lifecycle::SubjectKind;
16use crate::readouts::buf::ReadoutBuf;
17use crate::readouts::context::ReadoutContext;
18use crate::readouts::readout::{ContentMode, Lod, Readout, ReadoutOptions};
19
20pub struct SessionSummary;
21
22impl Readout for SessionSummary {
23 fn name(&self) -> &'static str {
24 "session_summary"
25 }
26 fn accepts(&self) -> &'static [SubjectKind] {
27 &[SubjectKind::Session]
28 }
29
30 fn render(
31 &self,
32 ctx: &dyn ReadoutContext,
33 lod: Lod,
34 mode: ContentMode,
35 _opts: &ReadoutOptions,
36 out: &mut dyn ReadoutBuf,
37 ) -> usize {
38 match (lod, mode) {
39 (Lod::Compact, ContentMode::Value) => render_compact(ctx, out),
40 (Lod::Labeled, ContentMode::Value) => render_labeled(ctx, out),
41 (Lod::Expanded, ContentMode::Value) => render_expanded(ctx, out),
42 (_, ContentMode::Explanation) => render_explanation(out),
43 }
44 }
45}
46
47fn render_compact(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
50 let color = ctx.use_color();
51 let dim = if color { "\x1b[2m" } else { "" };
52 let green = if color { "\x1b[32m" } else { "" };
53 let red = if color { "\x1b[1;31m" } else { "" };
54 let reset = if color { "\x1b[0m" } else { "" };
55 let f = ctx.session_phases_failed();
56 let p = ctx.session_phases_pending();
57 let fail_color = if f > 0 { red } else { dim };
58 let pending_color = dim;
61 let mut tmp = String::with_capacity(64);
62 let _ = write!(
63 &mut tmp,
64 "{green}{c}{reset}/{fail_color}{f}{reset}/{pending_color}{p}{reset}/{dim}{t}{reset}",
65 c = ctx.session_phases_completed(),
66 t = ctx.session_phases_total(),
67 );
68 let len = tmp.len();
69 let _ = out.write_str(&tmp);
70 len
71}
72
73fn render_labeled(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
77 let tmp = labeled_phase_rollup(
78 ctx.session_phases_completed(),
79 ctx.session_phases_failed(),
80 ctx.session_phases_pending(),
81 ctx.session_phases_total(),
82 ctx.use_color(),
83 );
84 let len = tmp.len();
85 let _ = out.write_str(&tmp);
86 len
87}
88
89pub fn labeled_phase_rollup(
103 completed: usize,
104 failed: usize,
105 pending: usize,
106 total: usize,
107 color: bool,
108) -> String {
109 let bold = if color { "\x1b[1m" } else { "" };
110 let dim = if color { "\x1b[2m" } else { "" };
111 let green = if color { "\x1b[32m" } else { "" };
112 let red = if color { "\x1b[1;31m" } else { "" };
113 let reset = if color { "\x1b[0m" } else { "" };
114 let fail_color = if failed > 0 { red } else { dim };
115 let mut tmp = String::with_capacity(128);
116 let _ = write!(
117 &mut tmp,
118 "{bold}phases:{reset} \
119 {green}{completed}{reset} completed, \
120 {fail_color}{failed}{reset} failed, \
121 {dim}{pending}{reset} not run \
122 {dim}(of {total} total){reset}",
123 );
124 tmp
125}
126
127fn render_expanded(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
130 let color = ctx.use_color();
131 let bold = if color { "\x1b[1m" } else { "" };
132 let dim = if color { "\x1b[2m" } else { "" };
133 let green = if color { "\x1b[32m" } else { "" };
134 let red = if color { "\x1b[1;31m" } else { "" };
135 let reset = if color { "\x1b[0m" } else { "" };
136 let f = ctx.session_phases_failed();
137 let fail_color = if f > 0 { red } else { dim };
138 let mut tmp = String::with_capacity(192);
139 let _ = write!(
140 &mut tmp,
141 "{bold}session totals{reset}\n \
142 {dim}completed:{reset} {green}{c}{reset}\n \
143 {dim}failed:{reset} {fail_color}{f}{reset}\n \
144 {dim}not run:{reset} {dim}{p}{reset}\n \
145 {dim}total:{reset} {dim}{t}{reset}",
146 c = ctx.session_phases_completed(),
147 p = ctx.session_phases_pending(),
148 t = ctx.session_phases_total(),
149 );
150 let len = tmp.len();
151 let _ = out.write_str(&tmp);
152 len
153}
154
155fn render_explanation(out: &mut dyn ReadoutBuf) -> usize {
156 let s = "phases: <completed-count> completed, <failed-count> failed, \
157 <pending-count> not run (of <total> total)";
158 let _ = out.write_str(s);
159 s.len()
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::readouts::buf::StringBuf;
166
167 #[derive(Default)]
168 struct TestCtx {
169 completed: usize,
170 failed: usize,
171 pending: usize,
172 total: usize,
173 }
174 impl ReadoutContext for TestCtx {
175 fn subject_name(&self) -> &str {
176 "session"
177 }
178 fn subject_seq(&self) -> Option<(usize, usize)> {
179 None
180 }
181 fn subject_labels(&self) -> &str {
182 ""
183 }
184 fn cycles_completed(&self) -> u64 {
185 0
186 }
187 fn cycles_total(&self) -> u64 {
188 0
189 }
190 fn ops_ok(&self) -> u64 {
191 0
192 }
193 fn errors(&self) -> u64 {
194 0
195 }
196 fn retries(&self) -> u64 {
197 0
198 }
199 fn concurrency(&self) -> usize {
200 0
201 }
202 fn elapsed_secs(&self) -> f64 {
203 0.0
204 }
205 fn consumed(&self) -> u64 {
206 0
207 }
208 fn status_metric_chips(&self) -> String {
209 String::new()
210 }
211 fn depth_indent(&self) -> &str {
212 ""
213 }
214 fn use_color(&self) -> bool {
215 false
216 }
217 fn event(&self) -> crate::lifecycle::EventType {
218 crate::lifecycle::EventType::SessionEnd
219 }
220 fn session_phases_completed(&self) -> usize {
221 self.completed
222 }
223 fn session_phases_failed(&self) -> usize {
224 self.failed
225 }
226 fn session_phases_pending(&self) -> usize {
227 self.pending
228 }
229 fn session_phases_total(&self) -> usize {
230 self.total
231 }
232 }
233
234 fn render(ctx: &TestCtx, lod: Lod) -> String {
235 let mut s = String::new();
236 let mut buf = StringBuf::new(&mut s);
237 SessionSummary.render(
238 ctx,
239 lod,
240 ContentMode::Value,
241 &ReadoutOptions::new(),
242 &mut buf,
243 );
244 s
245 }
246
247 #[test]
248 fn labeled_matches_pre_engine_format() {
249 let ctx = TestCtx {
250 completed: 7,
251 failed: 1,
252 pending: 0,
253 total: 8,
254 };
255 assert_eq!(
256 render(&ctx, Lod::Labeled),
257 "phases: 7 completed, 1 failed, 0 not run (of 8 total)",
258 );
259 }
260
261 #[test]
262 fn compact_packs_into_slash_form() {
263 let ctx = TestCtx {
264 completed: 5,
265 failed: 2,
266 pending: 1,
267 total: 8,
268 };
269 assert_eq!(render(&ctx, Lod::Compact), "5/2/1/8");
270 }
271
272 #[test]
273 fn expanded_breaks_onto_multiple_lines() {
274 let ctx = TestCtx {
275 completed: 1,
276 failed: 0,
277 pending: 0,
278 total: 1,
279 };
280 let out = render(&ctx, Lod::Expanded);
281 assert!(out.lines().count() >= 4);
282 assert!(out.contains("completed: 1"));
283 assert!(out.contains("total: 1"));
284 }
285
286 #[test]
287 fn explanation_shows_field_descriptors() {
288 let ctx = TestCtx::default();
289 let mut s = String::new();
290 let mut buf = StringBuf::new(&mut s);
291 SessionSummary.render(
292 &ctx,
293 Lod::Labeled,
294 ContentMode::Explanation,
295 &ReadoutOptions::new(),
296 &mut buf,
297 );
298 assert!(s.contains("completed-count"));
299 assert!(s.contains("failed-count"));
300 assert!(s.contains("pending-count"));
301 }
302}