use crate::warehouse::test_results::{status, summarize_runs, TestResultRow};
pub const ASPECT_ORDER: &[&str] = &[
"build",
"unit",
"doctest",
"clippy",
"fmt",
"audit",
"bench-smoke",
"coverage",
"feature-powerset",
"msrv",
"examples",
];
pub const SPARK_RUNS: usize = 12;
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
pub status: String,
pub metric: f64,
pub count: usize,
pub passed: usize,
pub failed: usize,
pub red_cases: Vec<RedCase>,
}
impl Cell {
fn not_run() -> Self {
Self {
status: String::new(),
metric: 0.0,
count: 0,
passed: 0,
failed: 0,
red_cases: Vec::new(),
}
}
pub fn ran(&self) -> bool {
!self.status.is_empty()
}
pub fn is_red(&self) -> bool {
status::is_red(&self.status)
}
pub fn is_green(&self) -> bool {
status::is_green(&self.status)
}
pub fn badge(&self, aspect: &str) -> String {
if !self.ran() {
return String::new();
}
match aspect {
"coverage" => format!("{:.0}%", self.metric),
"clippy" => format!("âš {}", self.metric as i64),
"audit" => format!("🛡{}", self.metric as i64),
"fmt" => {
if self.metric > 0.0 {
format!("±{}", self.metric as i64)
} else {
String::new()
}
}
"unit" => {
if self.failed > 0 {
format!("{}✓ {}✗", self.passed, self.failed)
} else {
format!("{}✓", self.passed)
}
}
_ => String::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RedCase {
pub test_name: String,
pub suite: String,
pub status: String,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct RepoRow {
pub repo: String,
pub run_id: String,
pub ts_micros: i64,
pub cells: Vec<Cell>,
pub health: f64,
pub sparks: Vec<Vec<f64>>,
}
#[derive(Debug, Clone)]
pub struct Matrix {
pub aspects: Vec<String>,
pub rows: Vec<RepoRow>,
pub total_green: usize,
pub total_red: usize,
pub total_skip: usize,
pub total_blank: usize,
}
impl Matrix {
pub fn build(rows: &[TestResultRow]) -> Self {
use std::collections::BTreeMap;
let mut by_repo: BTreeMap<String, Vec<&TestResultRow>> = BTreeMap::new();
for r in rows {
by_repo.entry(r.repo.clone()).or_default().push(r);
}
let present: std::collections::HashSet<&str> =
rows.iter().map(|r| r.aspect.as_str()).collect();
let aspects: Vec<String> = ASPECT_ORDER
.iter()
.filter(|a| present.contains(**a))
.map(|a| a.to_string())
.collect();
let mut out_rows = Vec::new();
let (mut tg, mut tr, mut ts, mut tb) = (0usize, 0usize, 0usize, 0usize);
for (repo, repo_rows) in &by_repo {
let summaries = summarize_runs(
&repo_rows.iter().map(|r| (*r).clone()).collect::<Vec<_>>(),
);
let Some(latest) = summaries.first() else { continue };
let latest_id = latest.run_id.clone();
let mut cells = Vec::with_capacity(aspects.len());
for aspect in &aspects {
let cell = build_cell(repo_rows, &latest_id, aspect);
match () {
_ if !cell.ran() => tb += 1,
_ if cell.is_red() => tr += 1,
_ if cell.is_green() => tg += 1,
_ => ts += 1, }
cells.push(cell);
}
let health = health_score(&aspects, &cells);
let sparks = aspects
.iter()
.map(|a| spark_series(repo_rows, &summaries, a))
.collect();
out_rows.push(RepoRow {
repo: repo.clone(),
run_id: latest_id,
ts_micros: latest.ts_micros,
cells,
health,
sparks,
});
}
Matrix {
aspects,
rows: out_rows,
total_green: tg,
total_red: tr,
total_skip: ts,
total_blank: tb,
}
}
}
fn build_cell(repo_rows: &[&TestResultRow], run_id: &str, aspect: &str) -> Cell {
let hits: Vec<&&TestResultRow> = repo_rows
.iter()
.filter(|r| r.run_id == run_id && r.aspect == aspect)
.collect();
if hits.is_empty() {
return Cell::not_run();
}
if aspect == "unit" {
let mut passed = 0;
let mut failed = 0;
let mut red_cases = Vec::new();
for r in &hits {
if status::is_green(&r.status) {
passed += 1;
} else if status::is_red(&r.status) {
failed += 1;
}
if status::is_red(&r.status) {
red_cases.push(RedCase {
test_name: r.test_name.clone(),
suite: r.suite.clone(),
status: r.status.clone(),
message: r.message.clone(),
});
}
}
let cell_status = if failed > 0 { status::FAIL } else { status::PASS };
return Cell {
status: cell_status.to_string(),
metric: failed as f64,
count: hits.len(),
passed,
failed,
red_cases,
};
}
let r = hits[0];
let red_cases = if status::is_red(&r.status) {
vec![RedCase {
test_name: r.test_name.clone(),
suite: r.suite.clone(),
status: r.status.clone(),
message: r.message.clone(),
}]
} else {
Vec::new()
};
Cell {
status: r.status.clone(),
metric: r.metric,
count: hits.len(),
passed: usize::from(status::is_green(&r.status)),
failed: usize::from(status::is_red(&r.status)),
red_cases,
}
}
pub fn health_score(aspects: &[String], cells: &[Cell]) -> f64 {
let mut green = 0.0;
let mut considered = 0.0;
let mut coverage: Option<f64> = None;
let mut penalty = 0.0;
for (a, c) in aspects.iter().zip(cells) {
if !c.ran() {
continue;
}
if c.is_green() || c.is_red() || c.status == status::SKIP {
considered += 1.0;
if c.is_green() {
green += 1.0;
}
}
match a.as_str() {
"coverage" if c.ran() => coverage = Some(c.metric),
"clippy" => penalty += c.metric.min(8.0) * 4.0,
"audit" => penalty += c.metric.min(8.0) * 6.0,
_ => {}
}
}
if considered == 0.0 {
return 0.0;
}
let green_ratio = green / considered; let (base, score) = match coverage {
Some(cov) => {
let s = green_ratio * 60.0 + (cov / 100.0).clamp(0.0, 1.0) * 25.0 + 15.0;
(60.0 + 25.0 + 15.0, s)
}
None => {
let s = green_ratio * 85.0 + 15.0;
(85.0 + 15.0, s)
}
};
let _ = base;
(score - penalty).clamp(0.0, 100.0)
}
pub fn spark_series(
repo_rows: &[&TestResultRow],
summaries: &[crate::warehouse::test_results::RunSummary],
aspect: &str,
) -> Vec<f64> {
let mut runs: Vec<&crate::warehouse::test_results::RunSummary> =
summaries.iter().take(SPARK_RUNS).collect();
runs.reverse();
let mut series = Vec::with_capacity(runs.len());
for run in runs {
let hits: Vec<&&TestResultRow> = repo_rows
.iter()
.filter(|r| r.run_id == run.run_id && r.aspect == aspect)
.collect();
if hits.is_empty() {
continue; }
let v = match aspect {
"coverage" => hits.iter().map(|r| r.metric).fold(0.0, f64::max),
"clippy" | "audit" | "fmt" => {
let count = hits.iter().map(|r| r.metric).fold(0.0, f64::max);
(1.0 - (count / 10.0).clamp(0.0, 1.0)) * 100.0
}
"unit" => {
let total = hits.len() as f64;
let pass = hits.iter().filter(|r| status::is_green(&r.status)).count() as f64;
if total == 0.0 {
50.0
} else {
pass / total * 100.0
}
}
_ => {
let r = hits[0];
if status::is_green(&r.status) {
100.0
} else if status::is_red(&r.status) {
0.0
} else {
50.0
}
}
};
series.push(v);
}
series
}
#[cfg(test)]
mod tests {
use super::*;
fn row(run: &str, repo: &str, aspect: &str, name: &str, st: &str, metric: f64, ts: i64) -> TestResultRow {
TestResultRow {
run_id: run.into(),
repo: repo.into(),
suite: repo.into(),
test_name: name.into(),
status: st.into(),
duration_ms: 1.0,
ts_micros: ts,
message: if status::is_red(st) { format!("{name} boom") } else { String::new() },
aspect: aspect.into(),
metric,
}
}
#[test]
fn matrix_rows_are_repos_cols_are_aspects() {
let rows = vec![
row("r1", "alpha", "build", "build", status::PASS, 0.0, 100),
row("r1", "alpha", "clippy", "clippy", status::FAIL, 3.0, 100),
row("r1", "beta", "build", "build", status::PASS, 0.0, 100),
row("r1", "beta", "coverage", "coverage", status::PASS, 87.0, 100),
];
let m = Matrix::build(&rows);
assert_eq!(m.rows.iter().map(|r| r.repo.as_str()).collect::<Vec<_>>(), vec!["alpha", "beta"]);
assert_eq!(m.aspects, vec!["build", "clippy", "coverage"]);
}
#[test]
fn cells_carry_status_and_metric_badges() {
let rows = vec![
row("r1", "alpha", "clippy", "clippy", status::FAIL, 3.0, 100),
row("r1", "alpha", "coverage", "coverage", status::PASS, 87.0, 100),
row("r1", "alpha", "audit", "audit", status::PASS, 0.0, 100),
];
let m = Matrix::build(&rows);
let a = &m.rows[0];
let clippy = a.cells[m.aspects.iter().position(|x| x == "clippy").unwrap()].clone();
assert_eq!(clippy.status, status::FAIL);
assert_eq!(clippy.metric, 3.0);
assert_eq!(clippy.badge("clippy"), "âš 3");
let cov = a.cells[m.aspects.iter().position(|x| x == "coverage").unwrap()].clone();
assert_eq!(cov.badge("coverage"), "87%");
let audit = a.cells[m.aspects.iter().position(|x| x == "audit").unwrap()].clone();
assert_eq!(audit.badge("audit"), "🛡0");
}
#[test]
fn unit_cell_rolls_up_pass_fail_counts_and_badge() {
let rows = vec![
row("r1", "alpha", "unit", "a::t1", status::PASS, 0.0, 100),
row("r1", "alpha", "unit", "a::t2", status::PASS, 0.0, 100),
row("r1", "alpha", "unit", "a::t3", status::FAIL, 0.0, 100),
];
let m = Matrix::build(&rows);
let unit = &m.rows[0].cells[0];
assert_eq!(unit.passed, 2);
assert_eq!(unit.failed, 1);
assert_eq!(unit.status, status::FAIL);
assert_eq!(unit.badge("unit"), "2✓ 1✗");
assert_eq!(unit.red_cases.len(), 1);
assert_eq!(unit.red_cases[0].test_name, "a::t3");
}
#[test]
fn latest_run_wins_per_repo() {
let rows = vec![
row("old", "alpha", "clippy", "clippy", status::FAIL, 5.0, 100),
row("new", "alpha", "clippy", "clippy", status::PASS, 0.0, 200),
];
let m = Matrix::build(&rows);
let clippy = &m.rows[0].cells[0];
assert_eq!(m.rows[0].run_id, "new", "newest run selected");
assert_eq!(clippy.status, status::PASS, "latest run's verdict");
}
#[test]
fn not_run_cell_is_blank_and_counted() {
let rows = vec![
row("r1", "alpha", "clippy", "clippy", status::PASS, 0.0, 100),
row("r1", "beta", "build", "build", status::PASS, 0.0, 100),
];
let m = Matrix::build(&rows);
let beta = m.rows.iter().find(|r| r.repo == "beta").unwrap();
let clippy_idx = m.aspects.iter().position(|x| x == "clippy").unwrap();
assert!(!beta.cells[clippy_idx].ran(), "beta never ran clippy");
assert!(m.total_blank >= 1, "blank cells counted in the summary strip");
}
#[test]
fn health_high_for_green_high_coverage_low_warnings() {
let aspects: Vec<String> = vec!["build".into(), "coverage".into(), "clippy".into()];
let cells = vec![
Cell { status: status::PASS.into(), metric: 0.0, count: 1, passed: 1, failed: 0, red_cases: vec![] },
Cell { status: status::PASS.into(), metric: 95.0, count: 1, passed: 1, failed: 0, red_cases: vec![] },
Cell { status: status::PASS.into(), metric: 0.0, count: 1, passed: 1, failed: 0, red_cases: vec![] },
];
let h = health_score(&aspects, &cells);
assert!(h > 90.0, "all green + 95% coverage + no warnings → near 100, got {h}");
}
#[test]
fn health_drops_for_failures_and_warnings() {
let aspects: Vec<String> = vec!["build".into(), "coverage".into(), "clippy".into()];
let cells = vec![
Cell { status: status::FAIL.into(), metric: 1.0, count: 1, passed: 0, failed: 1, red_cases: vec![] },
Cell { status: status::PASS.into(), metric: 40.0, count: 1, passed: 1, failed: 0, red_cases: vec![] },
Cell { status: status::FAIL.into(), metric: 6.0, count: 1, passed: 0, failed: 1, red_cases: vec![] },
];
let h = health_score(&aspects, &cells);
assert!(h < 50.0, "a build fail + low coverage + 6 warnings → unhealthy, got {h}");
}
#[test]
fn sparkline_tracks_coverage_over_runs() {
let rows = vec![
row("run1", "alpha", "coverage", "coverage", status::PASS, 70.0, 100),
row("run2", "alpha", "coverage", "coverage", status::PASS, 80.0, 200),
row("run3", "alpha", "coverage", "coverage", status::PASS, 90.0, 300),
];
let m = Matrix::build(&rows);
let cov_idx = m.aspects.iter().position(|x| x == "coverage").unwrap();
let series = &m.rows[0].sparks[cov_idx];
assert_eq!(series.len(), 3, "one point per run");
assert_eq!(series, &vec![70.0, 80.0, 90.0], "oldest→newest, rising coverage");
}
#[test]
fn sparkline_inverts_warning_counts() {
let rows = vec![
row("run1", "alpha", "clippy", "clippy", status::FAIL, 8.0, 100),
row("run2", "alpha", "clippy", "clippy", status::PASS, 0.0, 200),
];
let m = Matrix::build(&rows);
let idx = m.aspects.iter().position(|x| x == "clippy").unwrap();
let series = &m.rows[0].sparks[idx];
assert_eq!(series.len(), 2);
assert!(series[0] < series[1], "fewer warnings later → spark rises: {series:?}");
assert!((series[1] - 100.0).abs() < 1e-9, "0 warnings = 100");
}
}