use std::fmt::Write as _;
use crate::lifecycle::SubjectKind;
use crate::readouts::buf::ReadoutBuf;
use crate::readouts::context::ReadoutContext;
use crate::readouts::readout::{ContentMode, Lod, Readout, ReadoutOptions};
pub struct SessionSummary;
impl Readout for SessionSummary {
fn name(&self) -> &'static str {
"session_summary"
}
fn accepts(&self) -> &'static [SubjectKind] {
&[SubjectKind::Session]
}
fn render(
&self,
ctx: &dyn ReadoutContext,
lod: Lod,
mode: ContentMode,
_opts: &ReadoutOptions,
out: &mut dyn ReadoutBuf,
) -> usize {
match (lod, mode) {
(Lod::Compact, ContentMode::Value) => render_compact(ctx, out),
(Lod::Labeled, ContentMode::Value) => render_labeled(ctx, out),
(Lod::Expanded, ContentMode::Value) => render_expanded(ctx, out),
(_, ContentMode::Explanation) => render_explanation(out),
}
}
}
fn render_compact(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
let color = ctx.use_color();
let dim = if color { "\x1b[2m" } else { "" };
let green = if color { "\x1b[32m" } else { "" };
let red = if color { "\x1b[1;31m" } else { "" };
let reset = if color { "\x1b[0m" } else { "" };
let f = ctx.session_phases_failed();
let p = ctx.session_phases_pending();
let fail_color = if f > 0 { red } else { dim };
let pending_color = dim;
let mut tmp = String::with_capacity(64);
let _ = write!(
&mut tmp,
"{green}{c}{reset}/{fail_color}{f}{reset}/{pending_color}{p}{reset}/{dim}{t}{reset}",
c = ctx.session_phases_completed(),
t = ctx.session_phases_total(),
);
let len = tmp.len();
let _ = out.write_str(&tmp);
len
}
fn render_labeled(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
let tmp = labeled_phase_rollup(
ctx.session_phases_completed(),
ctx.session_phases_failed(),
ctx.session_phases_pending(),
ctx.session_phases_total(),
ctx.use_color(),
);
let len = tmp.len();
let _ = out.write_str(&tmp);
len
}
pub fn labeled_phase_rollup(
completed: usize,
failed: usize,
pending: usize,
total: usize,
color: bool,
) -> String {
let bold = if color { "\x1b[1m" } else { "" };
let dim = if color { "\x1b[2m" } else { "" };
let green = if color { "\x1b[32m" } else { "" };
let red = if color { "\x1b[1;31m" } else { "" };
let reset = if color { "\x1b[0m" } else { "" };
let fail_color = if failed > 0 { red } else { dim };
let mut tmp = String::with_capacity(128);
let _ = write!(
&mut tmp,
"{bold}phases:{reset} \
{green}{completed}{reset} completed, \
{fail_color}{failed}{reset} failed, \
{dim}{pending}{reset} not run \
{dim}(of {total} total){reset}",
);
tmp
}
fn render_expanded(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
let color = ctx.use_color();
let bold = if color { "\x1b[1m" } else { "" };
let dim = if color { "\x1b[2m" } else { "" };
let green = if color { "\x1b[32m" } else { "" };
let red = if color { "\x1b[1;31m" } else { "" };
let reset = if color { "\x1b[0m" } else { "" };
let f = ctx.session_phases_failed();
let fail_color = if f > 0 { red } else { dim };
let mut tmp = String::with_capacity(192);
let _ = write!(
&mut tmp,
"{bold}session totals{reset}\n \
{dim}completed:{reset} {green}{c}{reset}\n \
{dim}failed:{reset} {fail_color}{f}{reset}\n \
{dim}not run:{reset} {dim}{p}{reset}\n \
{dim}total:{reset} {dim}{t}{reset}",
c = ctx.session_phases_completed(),
p = ctx.session_phases_pending(),
t = ctx.session_phases_total(),
);
let len = tmp.len();
let _ = out.write_str(&tmp);
len
}
fn render_explanation(out: &mut dyn ReadoutBuf) -> usize {
let s = "phases: <completed-count> completed, <failed-count> failed, \
<pending-count> not run (of <total> total)";
let _ = out.write_str(s);
s.len()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::readouts::buf::StringBuf;
#[derive(Default)]
struct TestCtx {
completed: usize,
failed: usize,
pending: usize,
total: usize,
}
impl ReadoutContext for TestCtx {
fn subject_name(&self) -> &str {
"session"
}
fn subject_seq(&self) -> Option<(usize, usize)> {
None
}
fn subject_labels(&self) -> &str {
""
}
fn cycles_completed(&self) -> u64 {
0
}
fn cycles_total(&self) -> u64 {
0
}
fn ops_ok(&self) -> u64 {
0
}
fn errors(&self) -> u64 {
0
}
fn retries(&self) -> u64 {
0
}
fn concurrency(&self) -> usize {
0
}
fn elapsed_secs(&self) -> f64 {
0.0
}
fn consumed(&self) -> u64 {
0
}
fn status_metric_chips(&self) -> String {
String::new()
}
fn depth_indent(&self) -> &str {
""
}
fn use_color(&self) -> bool {
false
}
fn event(&self) -> crate::lifecycle::EventType {
crate::lifecycle::EventType::SessionEnd
}
fn session_phases_completed(&self) -> usize {
self.completed
}
fn session_phases_failed(&self) -> usize {
self.failed
}
fn session_phases_pending(&self) -> usize {
self.pending
}
fn session_phases_total(&self) -> usize {
self.total
}
}
fn render(ctx: &TestCtx, lod: Lod) -> String {
let mut s = String::new();
let mut buf = StringBuf::new(&mut s);
SessionSummary.render(
ctx,
lod,
ContentMode::Value,
&ReadoutOptions::new(),
&mut buf,
);
s
}
#[test]
fn labeled_matches_pre_engine_format() {
let ctx = TestCtx {
completed: 7,
failed: 1,
pending: 0,
total: 8,
};
assert_eq!(
render(&ctx, Lod::Labeled),
"phases: 7 completed, 1 failed, 0 not run (of 8 total)",
);
}
#[test]
fn compact_packs_into_slash_form() {
let ctx = TestCtx {
completed: 5,
failed: 2,
pending: 1,
total: 8,
};
assert_eq!(render(&ctx, Lod::Compact), "5/2/1/8");
}
#[test]
fn expanded_breaks_onto_multiple_lines() {
let ctx = TestCtx {
completed: 1,
failed: 0,
pending: 0,
total: 1,
};
let out = render(&ctx, Lod::Expanded);
assert!(out.lines().count() >= 4);
assert!(out.contains("completed: 1"));
assert!(out.contains("total: 1"));
}
#[test]
fn explanation_shows_field_descriptors() {
let ctx = TestCtx::default();
let mut s = String::new();
let mut buf = StringBuf::new(&mut s);
SessionSummary.render(
&ctx,
Lod::Labeled,
ContentMode::Explanation,
&ReadoutOptions::new(),
&mut buf,
);
assert!(s.contains("completed-count"));
assert!(s.contains("failed-count"));
assert!(s.contains("pending-count"));
}
}