#![allow(unused)]
#![cfg_attr(coverage_nightly, coverage(off))]
use crate::cli::handlers::comply_handlers::muda_handlers;
use crate::cli::handlers::work_contract::compute_codebase_score;
use crate::cli::RepoScoreOutputFormat;
use crate::services::rust_project_score::models::ScoringMode;
use crate::services::rust_project_score::orchestrator::RustProjectScoreOrchestrator;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
pub type Dimension = std::result::Result<f64, String>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NotMeasured {
pub dimension: String,
pub reason: String,
}
pub const DIMENSIONS: [&str; 8] = [
"rps",
"comply",
"coverage",
"muda_inv",
"evoscore",
"dbc",
"file_health",
"pv_lint",
];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompositeScore {
pub sha: String,
pub timestamp: String,
#[serde(default)]
pub composite: Option<f64>,
pub grade: String,
pub sub_scores: SubScores,
#[serde(default)]
pub not_measured: Vec<NotMeasured>,
#[serde(default)]
pub gated_by: Vec<String>,
#[serde(default)]
pub dimensions_measured: usize,
#[serde(default)]
pub dimensions_total: usize,
pub rps_categories: HashMap<String, f64>,
pub comply_errors: usize,
pub comply_warnings: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubScores {
#[serde(default)]
pub rps: Option<f64>,
#[serde(default)]
pub comply: Option<f64>,
#[serde(default)]
pub coverage: Option<f64>,
#[serde(default)]
pub muda_inv: Option<f64>,
#[serde(default)]
pub evoscore: Option<f64>,
#[serde(default)]
pub dbc: Option<f64>,
#[serde(default)]
pub file_health: Option<f64>,
#[serde(default)]
pub pv_lint: Option<f64>,
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_score(
path: &Path,
gate: Option<f64>,
format: &RepoScoreOutputFormat,
output: Option<&Path>,
trend: bool,
regression_check: bool,
stack: bool,
) -> Result<()> {
debug_assert!(path.exists(), "path must exist: {}", path.display());
if !path.exists() || !path.is_dir() {
anyhow::bail!("Path is not a valid directory: {}", path.display());
}
if trend {
print_trend(path);
return Ok(());
}
crate::status_eprintln!("Computing unified quality score...");
let score = compute_composite(path).await?;
debug_assert!(
score.composite.is_none_or(|c| (0.0..=100.0).contains(&c)),
"composite score out of range: {:?}",
score.composite
);
persist_score(path, &score);
let output_text = render_score(&score, format)?;
if let Some(output_path) = output {
std::fs::write(output_path, &output_text)?;
crate::status_eprintln!("Score written to: {}", output_path.display());
} else {
println!("{}", output_text);
}
if stack {
print_stack_quality(path);
}
let violations = cross_validate(&score);
if !violations.is_empty() {
eprintln!(
"\nCross-validation ({}/{} invariants violated):",
violations.len(),
CROSS_VALIDATION_INVARIANTS
);
for v in &violations {
eprintln!(" {} {}", v.id, v.message);
}
if violations.len() >= 3 {
eprintln!("WARNING: 3+ invariants violated — systemic inconsistency");
}
}
if regression_check {
if let Some(delta) = check_regression(path, &score) {
if delta < -5.0 {
eprintln!(
"REGRESSION: composite dropped {:.1} pts (threshold: -5.0)",
delta
);
std::process::exit(1);
}
}
}
if let Some(threshold) = gate {
if !score.not_measured.is_empty() {
eprintln!(
"GATE SCOPE: composite covers {}/{} dimensions; not measured: {}",
score.dimensions_measured,
score.dimensions_total,
score.not_measured_summary()
);
}
match score.composite {
Some(composite) if composite >= threshold => {}
Some(composite) => {
eprintln!("FAIL: composite {composite:.1} < gate {threshold:.1}");
std::process::exit(1);
}
None => {
eprintln!(
"FAIL: composite is not measured (0/{} dimensions), so gate {:.1} cannot pass",
score.dimensions_total, threshold
);
std::process::exit(1);
}
}
}
Ok(())
}
impl CompositeScore {
fn not_measured_summary(&self) -> String {
self.not_measured
.iter()
.map(|n| format!("{} ({})", n.dimension, n.reason))
.collect::<Vec<_>>()
.join(", ")
}
}
fn render_score(score: &CompositeScore, format: &RepoScoreOutputFormat) -> Result<String> {
Ok(match format {
RepoScoreOutputFormat::Json => serde_json::to_string_pretty(score)?,
RepoScoreOutputFormat::Yaml => serde_yaml_ng::to_string(score)?,
RepoScoreOutputFormat::Markdown => format_markdown(score),
RepoScoreOutputFormat::Text => format_text(score),
})
}
fn format_markdown(score: &CompositeScore) -> String {
use std::fmt::Write as _;
let mut out = String::new();
out.push_str("# PMAT Unified Score\n\n");
let _ = match score.composite {
Some(c) => writeln!(out, "- **Composite**: {c:.1}/100"),
None => writeln!(out, "- **Composite**: not measured"),
};
let _ = writeln!(out, "- **Grade**: {}", score.grade);
let _ = writeln!(
out,
"- **Dimensions measured**: {}/{}",
score.dimensions_measured, score.dimensions_total
);
let _ = writeln!(out, "- **Commit**: {}", score.sha);
let _ = writeln!(out, "- **Timestamp**: {}\n", score.timestamp);
out.push_str("## Sub-Scores\n\n");
out.push_str("| Sub-Score | Value |\n|---|---:|\n");
let s = &score.sub_scores;
let _ = writeln!(out, "| RPS | {} |", md_value(s.rps));
let _ = writeln!(
out,
"| Comply | {} ({} errors, {} warnings) |",
md_value(s.comply),
score.comply_errors,
score.comply_warnings
);
let _ = writeln!(out, "| Coverage | {} |", md_value(s.coverage));
let _ = writeln!(out, "| Muda (inv) | {} |", md_value(s.muda_inv));
let _ = writeln!(out, "| EvoScore | {} |", md_value(s.evoscore));
let _ = writeln!(out, "| DBC | {} |", md_value(s.dbc));
let _ = writeln!(out, "| File Health | {} |", md_value(s.file_health));
let _ = writeln!(out, "| PV Lint | {} |", md_value(s.pv_lint));
if !score.not_measured.is_empty() {
out.push_str("\n## Not Measured (excluded from the composite)\n\n");
out.push_str("| Dimension | Reason |\n|---|---|\n");
for n in &score.not_measured {
let _ = writeln!(out, "| {} | {} |", n.dimension, n.reason);
}
}
if !score.rps_categories.is_empty() {
out.push_str("\n## RPS Categories\n\n");
out.push_str("| Category | Percent |\n|---|---:|\n");
let mut cats: Vec<_> = score.rps_categories.iter().collect();
cats.sort_by(|a, b| a.0.cmp(b.0));
for (name, pct) in cats {
let _ = writeln!(out, "| {name} | {pct:.1} |");
}
}
out
}
fn md_value(value: Option<f64>) -> String {
match value {
Some(v) => format!("{v:.1}"),
None => "not measured".to_string(),
}
}
async fn compute_composite(path: &Path) -> Result<CompositeScore> {
debug_assert!(path.exists(), "path must exist: {}", path.display());
let sha = get_head_sha(path);
let timestamp = chrono::Utc::now().to_rfc3339();
let (rps, rps_categories) = compute_rps(path);
let (comply, comply_errors, comply_warnings) = compute_comply(path).await;
let muda_inv = compute_muda_inv(path);
let coverage = read_coverage_cache(path);
let evoscore = compute_evoscore(path);
let dbc = compute_dbc(path);
let file_health = compute_file_health(path);
let pv_lint = compute_pv_lint(path);
let dimensions: [Dimension; 8] = [
rps,
comply,
coverage,
muda_inv,
evoscore,
dbc,
file_health,
pv_lint,
];
Ok(assemble_score(
sha,
timestamp,
dimensions,
rps_categories,
comply_errors,
comply_warnings,
))
}
fn assemble_score(
sha: String,
timestamp: String,
dimensions: [Dimension; 8],
rps_categories: HashMap<String, f64>,
comply_errors: usize,
comply_warnings: usize,
) -> CompositeScore {
debug_assert_eq!(dimensions.len(), DIMENSIONS.len());
for (name, dim) in DIMENSIONS.iter().zip(dimensions.iter()) {
debug_assert!(
dim.as_ref().is_ok_and(|v| (0.0..=100.0).contains(v)) || dim.is_err(),
"{name} out of range: {dim:?}"
);
}
let values: Vec<f64> = dimensions
.iter()
.filter_map(|d| d.as_ref().ok().copied())
.collect();
let not_measured: Vec<NotMeasured> = DIMENSIONS
.iter()
.zip(dimensions.iter())
.filter_map(|(name, d)| {
d.as_ref().err().map(|reason| NotMeasured {
dimension: (*name).to_string(),
reason: reason.clone(),
})
})
.collect();
let gated_by: Vec<String> = DIMENSIONS
.iter()
.zip(dimensions.iter())
.filter_map(|(name, d)| match d {
Ok(v) if *v <= 0.0 => Some((*name).to_string()),
_ => None,
})
.collect();
let non_gated: Vec<f64> = values.iter().copied().filter(|v| *v > 0.0).collect();
let composite = if values.is_empty() {
None } else if gated_by.is_empty() {
Some(geometric_mean(values.as_slice()))
} else {
(!non_gated.is_empty()).then(|| geometric_mean(non_gated.as_slice()))
};
debug_assert!(
composite.is_none_or(|c| (0.0..=100.0).contains(&c)),
"geometric mean out of range: {composite:?}"
);
let grade = grade_for_verdict(composite, &gated_by);
let [rps, comply, coverage, muda_inv, evoscore, dbc, file_health, pv_lint] = dimensions;
CompositeScore {
sha,
timestamp,
composite,
grade,
sub_scores: SubScores {
rps: rps.ok(),
comply: comply.ok(),
coverage: coverage.ok(),
muda_inv: muda_inv.ok(),
evoscore: evoscore.ok(),
dbc: dbc.ok(),
file_health: file_health.ok(),
pv_lint: pv_lint.ok(),
},
dimensions_measured: values.len(),
dimensions_total: DIMENSIONS.len(),
not_measured,
gated_by,
rps_categories,
comply_errors,
comply_warnings,
}
}
fn grade_for_verdict(composite: Option<f64>, gated_by: &[String]) -> String {
if !gated_by.is_empty() {
return format!("GATED ({})", gated_by.join(", "));
}
grade_for(composite)
}
fn grade_for(composite: Option<f64>) -> String {
let Some(composite) = composite else {
return "n/a".to_string();
};
match composite as u32 {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
}
.to_string()
}
fn compute_rps(path: &Path) -> (Dimension, HashMap<String, f64>) {
debug_assert!(path.exists(), "path must exist: {}", path.display());
let orchestrator = RustProjectScoreOrchestrator::new();
match orchestrator.score_with_mode(path, ScoringMode::Fast) {
Ok(score) => {
let cats = score
.categories
.iter()
.map(|(k, v)| {
let pct = if v.max > 0.0 {
v.earned / v.max * 100.0
} else {
0.0
};
(k.clone(), pct)
})
.collect();
(Ok(score.percentage), cats)
}
Err(e) => (
Err(format!("the Rust Project Score run failed: {e}")),
HashMap::new(),
),
}
}
async fn compute_comply(path: &Path) -> (Dimension, usize, usize) {
debug_assert!(path.exists(), "path must exist: {}", path.display());
let output = std::process::Command::new("pmat")
.args(["comply", "check", "--format", "json"])
.current_dir(path)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.output();
match output {
Ok(o) if o.status.success() || o.status.code() == Some(1) => {
if let Ok(content) = String::from_utf8(o.stdout) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
if let Some(checks) = val.get("checks").and_then(|c| c.as_array()) {
let errors = checks
.iter()
.filter(|c| c.get("status").and_then(|s| s.as_str()) == Some("Fail"))
.count();
let warnings = checks
.iter()
.filter(|c| c.get("status").and_then(|s| s.as_str()) == Some("Warn"))
.count();
let score =
(100.0_f64 - (errors as f64 * 10.0 + warnings as f64 * 3.0)).max(0.0);
return (Ok(score), errors, warnings);
}
}
}
(Err(COMPLY_UNMEASURED.to_string()), 0, 0)
}
_ => (Err(COMPLY_UNMEASURED.to_string()), 0, 0),
}
}
const COMPLY_UNMEASURED: &str =
"`pmat comply check --format json` produced no parsable report (is `pmat` on PATH?)";
fn compute_muda_inv(path: &Path) -> Dimension {
debug_assert!(path.exists(), "path must exist: {}", path.display());
let report = muda_handlers::calculate_muda_score(path);
Ok((100.0 - report.total_score).max(0.0))
}
fn read_coverage_cache(path: &Path) -> Dimension {
debug_assert!(path.exists(), "path must exist: {}", path.display());
let coverage_result = path.join(".pmat-metrics/coverage.result");
let Ok(content) = std::fs::read_to_string(&coverage_result) else {
return Err(format!(
"no coverage run recorded: {} is absent (`pmat score` does not run \
coverage; `make coverage` / scripts/record-metric.sh writes it)",
coverage_result.display()
));
};
let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) else {
return Err(format!("{} is not valid JSON", coverage_result.display()));
};
let Some(pct) = val.get("coverage_pct").and_then(|v| v.as_f64()) else {
return Err(format!(
"{} has no numeric `coverage_pct` key",
coverage_result.display()
));
};
if !(0.0..=100.0).contains(&pct) {
return Err(format!(
"{} reports coverage_pct {pct}, which is not a percentage",
coverage_result.display()
));
}
Ok(pct)
}
fn compute_dbc(path: &Path) -> Dimension {
debug_assert!(path.exists(), "path must exist: {}", path.display());
let score = compute_codebase_score(path);
if score.contract_count == 0 {
return Err(format!(
"no design-by-contract work items: {} holds no <id>/contract.json",
path.join(".pmat-work").display()
));
}
let coverage_pct = score.contract_coverage * 100.0;
let lint_pct = score.lint_pass_rate * 100.0;
let dbc_score = 0.50 * coverage_pct + 0.30 * lint_pct + 0.20 * (score.mean_score * 100.0);
Ok(dbc_score.clamp(0.0, 100.0))
}
fn compute_evoscore(path: &Path) -> Dimension {
debug_assert!(path.exists(), "path must exist: {}", path.display());
let metrics_dir = path.join(".pmat-metrics");
let mut test_records: Vec<(String, String, u64, u64)> = Vec::new();
if let Ok(entries) = std::fs::read_dir(&metrics_dir) {
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.starts_with("commit-") && name_str.ends_with("-tests.json") {
if let Ok(content) = std::fs::read_to_string(entry.path()) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
let pass = val.get("pass").and_then(|v| v.as_u64()).unwrap_or(0);
let total = val.get("total").and_then(|v| v.as_u64()).unwrap_or(pass);
let sha = val
.get("commit")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if total > 0 {
test_records.push((name_str.to_string(), sha, pass, total));
}
}
}
}
}
}
if test_records.is_empty() {
return Err(format!(
"no test history: {} holds no commit-<sha>-tests.json (written by `pmat test-record`)",
metrics_dir.display()
));
}
test_records.sort_by(|a, b| a.0.cmp(&b.0));
let head = get_head_sha(path);
let (_, _, pass, total) = test_records
.iter()
.find(|(_, sha, _, _)| !sha.is_empty() && head.starts_with(sha.as_str()))
.or_else(|| test_records.last())
.expect("test_records is non-empty");
let rate = *pass as f64 / *total as f64;
Ok((rate * 100.0).clamp(0.0, 100.0))
}
include!("score_handler_compute.rs");
include!("score_handler_display.rs");
#[cfg(test)]
mod gated_verdict_tests {
use super::*;
fn dims(values: [Option<f64>; 8]) -> [Dimension; 8] {
values.map(|v| v.ok_or_else(|| "not measured in this fixture".to_string()))
}
fn score_of(values: [Option<f64>; 8]) -> CompositeScore {
assemble_score(
"abc1234".into(),
"2026-08-13T00:00:00Z".into(),
dims(values),
HashMap::new(),
0,
0,
)
}
fn reported_shape() -> CompositeScore {
score_of([
Some(63.1),
Some(0.0),
None,
Some(92.0),
None,
None,
Some(100.0),
Some(48.9),
])
}
#[test]
fn one_red_gate_is_named_instead_of_graded_f() {
let score = reported_shape();
assert_eq!(
score.grade, "GATED (comply)",
"a red gate must name itself, not render as F"
);
assert_eq!(score.gated_by, vec!["comply".to_string()]);
}
#[test]
fn the_composite_is_the_mean_of_the_non_gating_dimensions() {
let score = reported_shape();
let composite = score.composite.expect("four dimensions are non-gating");
assert!(
composite > 50.0 && composite < 100.0,
"a tree at 100.0 File Health and 92.0 Muda must not read as 0.0, got {composite}"
);
let expected = (63.1f64 * 92.0 * 100.0 * 48.9).powf(0.25);
assert!(
(composite - expected).abs() < 1e-6,
"composite {composite} is not the mean of the non-gating dimensions ({expected})"
);
}
#[test]
fn the_dimension_count_still_reports_what_was_measured() {
let score = reported_shape();
assert_eq!(score.dimensions_measured, 5);
assert_eq!(score.dimensions_total, 8);
assert_eq!(score.not_measured.len(), 3);
for n in &score.not_measured {
assert!(!n.reason.is_empty(), "{n:?} must say why");
}
}
#[test]
fn every_red_gate_is_named() {
let score = score_of([
Some(63.1),
Some(0.0),
None,
Some(92.0),
None,
None,
Some(0.0),
Some(48.9),
]);
assert_eq!(score.grade, "GATED (comply, file_health)", "{score:?}");
}
#[test]
fn a_clean_project_still_gets_a_letter_grade() {
let score = score_of([
Some(95.0),
Some(96.0),
Some(94.0),
Some(93.0),
Some(97.0),
Some(92.0),
Some(98.0),
Some(91.0),
]);
assert!(score.gated_by.is_empty(), "{score:?}");
assert_eq!(score.grade, "A");
}
#[test]
fn a_low_but_measured_project_still_grades_f() {
let score = score_of([
Some(10.0),
Some(12.0),
Some(8.0),
Some(15.0),
Some(11.0),
Some(9.0),
Some(14.0),
Some(13.0),
]);
assert!(score.gated_by.is_empty(), "{score:?}");
assert_eq!(score.grade, "F");
}
#[test]
fn all_gates_red_reports_no_composite_at_all() {
let score = score_of([Some(0.0); 8]);
assert_eq!(score.composite, None, "{score:?}");
assert!(score.grade.starts_with("GATED ("), "{score:?}");
}
#[test]
fn nothing_measured_is_not_a_gate() {
let score = score_of([None; 8]);
assert_eq!(score.composite, None);
assert_eq!(score.grade, "n/a");
assert!(score.gated_by.is_empty());
}
#[test]
fn json_carries_the_gate_list_and_the_verdict() {
let json = serde_json::to_value(reported_shape()).expect("serialize");
assert_eq!(json["grade"], "GATED (comply)");
assert_eq!(json["gated_by"], serde_json::json!(["comply"]));
assert_eq!(json["dimensions_measured"], 5);
}
#[test]
fn text_report_says_the_composite_excludes_the_gate() {
let _guard = crate::cli::colors::ForcedColor::off();
let rendered = format_text(&reported_shape());
assert!(rendered.contains("GATED (comply)"), "got:\n{rendered}");
assert!(
rendered.contains("non-gating"),
"the report must say what the composite covers, got:\n{rendered}"
);
}
}
#[cfg(test)]
mod score_quiet_chatter_tests {
use crate::cli::handlers::bottleneck_handler::quiet_chatter_tests::unguarded_stderr_lines;
const SOURCE: &str = include_str!("score_handler.rs");
fn call_line_for(needle: &str) -> &'static str {
let lines: Vec<&str> = SOURCE.lines().collect();
let at = lines
.iter()
.position(|l| l.contains(needle))
.unwrap_or_else(|| panic!("no line carries {needle:?}"));
let macro_needle = concat!("eprint", "ln!");
lines[..=at]
.iter()
.rev()
.take(4)
.find(|l| l.contains(macro_needle))
.unwrap_or_else(|| panic!("{needle:?} is not printed to stderr at all"))
}
#[test]
fn score_progress_is_suppressible() {
for chatter in ["Computing unified quality score", "Score written to:"] {
let line = call_line_for(chatter);
assert!(
unguarded_stderr_lines(line).is_empty(),
"{chatter:?} is progress chatter and must route through the \
quiet rule, but its call is unguarded: {line:?}"
);
}
}
#[test]
fn score_failures_stay_loud() {
for verdict in [
"FAIL: composite",
"REGRESSION: composite dropped",
"WARNING: 3+ invariants violated",
] {
let line = call_line_for(verdict);
assert!(
!unguarded_stderr_lines(line).is_empty(),
"{verdict:?} is a result, not chatter: --quiet must not \
suppress it, but its call is guarded: {line:?}"
);
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use tempfile::TempDir;
fn write(path: &std::path::Path, content: &str) {
std::fs::write(path, content).expect("write");
}
fn mkdir(path: &std::path::Path) {
std::fs::create_dir_all(path).expect("mkdir");
}
fn dummy_score(sub: SubScores, composite: f64, comply_errors: usize) -> CompositeScore {
CompositeScore {
sha: "abc1234".into(),
timestamp: "2026-04-24T08:00:00Z".into(),
composite: Some(composite),
grade: "B".into(),
sub_scores: sub,
not_measured: Vec::new(),
gated_by: Vec::new(),
dimensions_measured: DIMENSIONS.len(),
dimensions_total: DIMENSIONS.len(),
rps_categories: HashMap::new(),
comply_errors,
comply_warnings: 0,
}
}
fn zero_subs() -> SubScores {
SubScores {
rps: Some(0.0),
comply: Some(0.0),
coverage: Some(0.0),
muda_inv: Some(0.0),
evoscore: Some(0.0),
dbc: Some(0.0),
file_health: Some(0.0),
pv_lint: Some(0.0),
}
}
#[test]
fn test_geometric_mean_empty_returns_zero() {
assert_eq!(geometric_mean(&[]), 0.0);
}
#[test]
fn test_geometric_mean_single_value_returns_that_value() {
let result = geometric_mean(&[42.0]);
assert!((result - 42.0).abs() < 1e-10, "got {result}");
}
#[test]
fn test_geometric_mean_all_positive() {
let result = geometric_mean(&[2.0, 8.0]);
assert!((result - 4.0).abs() < 1e-10, "got {result}");
}
#[test]
fn test_geometric_mean_with_zero_is_zero() {
assert_eq!(geometric_mean(&[0.0, 100.0]), 0.0);
assert_eq!(geometric_mean(&[50.0, 0.0, 50.0]), 0.0);
}
#[test]
fn test_geometric_mean_three_values() {
let result = geometric_mean(&[8.0, 27.0, 64.0]);
assert!((result - 24.0).abs() < 1e-6, "got {result}");
}
#[test]
fn test_pv_lint_no_contracts_no_pmat_yaml_no_src_is_not_measured() {
let tmp = TempDir::new().unwrap();
let reason = compute_pv_lint(tmp.path()).expect_err("no contracts/ ⇒ nothing to measure");
assert!(reason.contains("no provable contracts"), "reason: {reason}");
}
#[test]
fn test_pv_lint_no_contracts_cb1202_disabled_is_not_measured() {
let tmp = TempDir::new().unwrap();
write(
&tmp.path().join(".pmat.yaml"),
"checks:\n cb-1202:\n enabled: false\n",
);
let reason = compute_pv_lint(tmp.path()).expect_err("cb-1202 off ⇒ nothing to measure");
assert!(reason.contains("cb-1202"), "reason: {reason}");
}
#[test]
fn test_pv_lint_no_contracts_src_with_critical_keyword_returns_zero() {
let tmp = TempDir::new().unwrap();
let src = tmp.path().join("src");
mkdir(&src);
write(
&src.join("lib.rs"),
"pub fn forward(x: f64) -> f64 { x * 2.0 }",
);
assert_eq!(compute_pv_lint(tmp.path()), Ok(0.0));
}
#[test]
fn test_pv_lint_no_contracts_src_without_critical_keyword_is_not_measured() {
let tmp = TempDir::new().unwrap();
let src = tmp.path().join("src");
mkdir(&src);
write(&src.join("lib.rs"), "pub fn mundane() -> i32 { 42 }");
assert!(compute_pv_lint(tmp.path()).is_err());
}
#[test]
fn test_pv_lint_with_contracts_but_no_pv_cli_fallback_to_pipeline() {
let tmp = TempDir::new().unwrap();
mkdir(&tmp.path().join("contracts"));
let score = compute_pv_lint(tmp.path()).expect("contracts/ exists ⇒ measured");
assert!(
(50.0..=95.0).contains(&score),
"expected clamp [50,95], got {score}"
);
}
#[test]
fn test_pipeline_depth_empty_project_is_zero() {
let tmp = TempDir::new().unwrap();
mkdir(&tmp.path().join("contracts"));
assert_eq!(compute_pipeline_depth(tmp.path()), 0.0);
}
#[test]
fn test_pipeline_depth_yaml_contracts_adds_five() {
let tmp = TempDir::new().unwrap();
let cd = tmp.path().join("contracts");
mkdir(&cd);
write(&cd.join("a.yaml"), "name: a\n");
assert!((compute_pipeline_depth(tmp.path()) - 5.0).abs() < 1e-10);
}
#[test]
fn test_pipeline_depth_build_rs_pre_count_adds_five() {
let tmp = TempDir::new().unwrap();
mkdir(&tmp.path().join("contracts"));
write(&tmp.path().join("build.rs"), "// PRE_COUNT=1\n");
assert!((compute_pipeline_depth(tmp.path()) - 5.0).abs() < 1e-10);
}
#[test]
fn test_pipeline_depth_contract_macro_in_src_adds_five() {
let tmp = TempDir::new().unwrap();
mkdir(&tmp.path().join("contracts"));
let src = tmp.path().join("src");
mkdir(&src);
write(
&src.join("lib.rs"),
"#[contract(\"a\", equation = \"b\")]\npub fn f() {}",
);
assert!((compute_pipeline_depth(tmp.path()) - 5.0).abs() < 1e-10);
}
#[test]
fn test_pipeline_depth_yaml_lean_theorem_and_kani_stacks_fifteen() {
let tmp = TempDir::new().unwrap();
let cd = tmp.path().join("contracts");
mkdir(&cd);
write(
&cd.join("a.yaml"),
"name: a\nlean_theorem: foo\nkani_harnesses:\n - bar\n",
);
assert!((compute_pipeline_depth(tmp.path()) - 15.0).abs() < 1e-10);
}
#[test]
fn test_contract_drift_no_contracts_dir_returns_zeros() {
let tmp = TempDir::new().unwrap();
let (stale, total, ratio) = compute_contract_drift(tmp.path());
assert_eq!((stale, total, ratio), (0, 0, 0.0));
}
#[test]
fn test_contract_drift_yaml_without_matching_src_not_stale() {
let tmp = TempDir::new().unwrap();
let cd = tmp.path().join("contracts");
mkdir(&cd);
write(&cd.join("orphan.yaml"), "name: orphan\n");
let (stale, total, _) = compute_contract_drift(tmp.path());
assert_eq!(stale, 0);
assert_eq!(total, 1);
}
#[test]
fn test_contract_drift_skips_binding_yaml() {
let tmp = TempDir::new().unwrap();
let cd = tmp.path().join("contracts");
mkdir(&cd);
write(&cd.join("binding-spec.yaml"), "name: bind\n");
write(&cd.join("real.yaml"), "name: real\n");
let (_, total, _) = compute_contract_drift(tmp.path());
assert_eq!(total, 1);
}
#[test]
fn test_file_health_no_src_is_not_measured() {
let tmp = TempDir::new().unwrap();
let reason = compute_file_health(tmp.path()).expect_err("no src/ ⇒ nothing to measure");
assert!(reason.contains("no Rust sources"), "reason: {reason}");
}
#[test]
fn test_file_health_empty_src_is_not_measured() {
let tmp = TempDir::new().unwrap();
mkdir(&tmp.path().join("src"));
assert!(compute_file_health(tmp.path()).is_err());
}
#[test]
fn test_file_health_all_small_files_returns_100() {
let tmp = TempDir::new().unwrap();
let src = tmp.path().join("src");
mkdir(&src);
write(&src.join("a.rs"), "fn a() {}");
write(&src.join("b.rs"), "fn b() {}");
assert_eq!(compute_file_health(tmp.path()), Ok(100.0));
}
#[test]
fn test_file_health_one_huge_file_in_two_drops_to_fifty() {
let tmp = TempDir::new().unwrap();
let src = tmp.path().join("src");
mkdir(&src);
write(&src.join("small.rs"), "fn a() {}");
let big: String = (0..1100).map(|i| format!("// line {i}\n")).collect();
write(&src.join("big.rs"), &big);
let result = compute_file_health(tmp.path()).expect("2 .rs files ⇒ measured");
assert!((result - 50.0).abs() < 1e-10, "got {result}");
}
#[test]
fn test_cross_validate_no_violations_when_scores_are_consistent() {
let score = dummy_score(zero_subs(), 50.0, 3);
let violations = cross_validate(&score);
assert!(violations.is_empty(), "violations: {}", violations.len());
}
#[test]
fn test_cross_validate_xv001_clean_comply_but_low_rps_code_quality() {
let mut score = dummy_score(zero_subs(), 70.0, 0);
score.rps_categories.insert("Code Quality".into(), 30.0);
let violations = cross_validate(&score);
assert!(
violations.iter().any(|v| v.id == "XV-001"),
"expected XV-001"
);
}
#[test]
fn test_cross_validate_xv003_high_coverage_low_testing_score() {
let mut subs = zero_subs();
subs.coverage = Some(95.0);
let mut score = dummy_score(subs, 70.0, 5);
score
.rps_categories
.insert("Testing Excellence".into(), 40.0);
let violations = cross_validate(&score);
assert!(
violations.iter().any(|v| v.id == "XV-003"),
"expected XV-003"
);
}
#[test]
fn test_cross_validate_xv007_rps_grade_a_but_low_composite() {
let mut subs = zero_subs();
subs.rps = Some(92.0);
let score = dummy_score(subs, 70.0, 5);
let violations = cross_validate(&score);
assert!(
violations.iter().any(|v| v.id == "XV-007"),
"expected XV-007"
);
}
#[test]
fn test_cross_validate_xv008_clean_comply_but_low_rps() {
let mut subs = zero_subs();
subs.rps = Some(40.0);
let score = dummy_score(subs, 50.0, 0);
let violations = cross_validate(&score);
assert!(
violations.iter().any(|v| v.id == "XV-008"),
"expected XV-008"
);
}
#[test]
fn test_cross_validate_xv009_good_file_health_but_low_muda() {
let mut subs = zero_subs();
subs.file_health = Some(95.0);
subs.muda_inv = Some(50.0);
let mut score = dummy_score(subs, 80.0, 5);
score.sub_scores.rps = Some(70.0);
let violations = cross_validate(&score);
assert!(
violations.iter().any(|v| v.id == "XV-009"),
"expected XV-009"
);
}
#[test]
fn test_cross_validate_xv010_low_coverage_but_high_composite() {
let mut subs = zero_subs();
subs.coverage = Some(30.0);
let score = dummy_score(subs, 85.0, 5);
let violations = cross_validate(&score);
assert!(
violations.iter().any(|v| v.id == "XV-010"),
"expected XV-010"
);
}
#[test]
fn test_persist_score_writes_json_in_pmat_metrics() {
let tmp = TempDir::new().unwrap();
let score = dummy_score(zero_subs(), 50.0, 0);
persist_score(tmp.path(), &score);
let out = tmp
.path()
.join(".pmat-metrics")
.join("commit-abc1234-meta.json");
assert!(out.exists(), "persist file missing: {}", out.display());
let content = std::fs::read_to_string(&out).unwrap();
let parsed: CompositeScore = serde_json::from_str(&content).unwrap();
assert_eq!(parsed.sha, "abc1234");
assert_eq!(parsed.composite, Some(50.0));
}
#[test]
fn test_pre_existing_score_files_still_deserialize() {
let legacy = r#"{
"sha": "deadbee",
"timestamp": "2026-04-01T00:00:00Z",
"composite": 52.86,
"grade": "F",
"sub_scores": {
"rps": 43.5, "comply": 28.0, "coverage": 50.0, "muda_inv": 75.7,
"evoscore": 50.0, "dbc": 50.0, "file_health": 100.0, "pv_lint": 50.0
},
"rps_categories": {},
"comply_errors": 3,
"comply_warnings": 14
}"#;
let parsed: CompositeScore = serde_json::from_str(legacy).expect("legacy score file");
assert_eq!(parsed.composite, Some(52.86));
assert_eq!(parsed.sub_scores.coverage, Some(50.0));
assert!(parsed.not_measured.is_empty());
assert_eq!(parsed.dimensions_total, 0, "absent in the legacy shape");
}
#[test]
fn test_get_head_sha_returns_unknown_outside_git_repo() {
let tmp = TempDir::new().unwrap();
let sha = get_head_sha(tmp.path());
assert!(
sha == "unknown" || sha.chars().all(|c| c.is_ascii_hexdigit()),
"got: {sha:?}"
);
}
#[test]
fn test_check_pv_lint_gates_returns_true_when_pv_unavailable() {
let tmp = TempDir::new().unwrap();
let _ = check_pv_lint_gates(tmp.path());
}
fn unmeasured_subject() -> ([Dimension; 8], Vec<f64>) {
let dims: [Dimension; 8] = [
Ok(40.0), Ok(60.0), Err("no coverage run".into()), Ok(90.0), Err("no test history".into()), Err("no contracts".into()), Ok(100.0), Err("no contracts/ dir".into()), ];
(dims, vec![40.0, 60.0, 90.0, 100.0])
}
#[test]
fn unmeasured_dimensions_are_excluded_from_the_composite() {
let (dims, measured) = unmeasured_subject();
let score = assemble_score("sha".into(), "ts".into(), dims, HashMap::new(), 0, 0);
let expected = geometric_mean(&measured);
let composite = score.composite.expect("four dimensions were measured");
assert!(
(composite - expected).abs() < 1e-9,
"composite {composite} must be the geometric mean of the {} measured \
dimensions ({expected}), not of a slate padded with 50.0",
measured.len()
);
let padded = geometric_mean(&[40.0, 60.0, 50.0, 90.0, 50.0, 50.0, 100.0]);
assert!(
(composite - padded).abs() > 1.0,
"composite {composite} is indistinguishable from the 50.0-padded \
mean {padded}"
);
assert_eq!(score.dimensions_measured, 4);
assert_eq!(score.dimensions_total, 8);
}
#[test]
fn unmeasured_dimensions_are_null_and_disclosed() {
let (dims, _) = unmeasured_subject();
let score = assemble_score("sha".into(), "ts".into(), dims, HashMap::new(), 0, 0);
assert_eq!(score.sub_scores.coverage, None);
assert_eq!(score.sub_scores.evoscore, None);
assert_eq!(score.sub_scores.dbc, None);
assert_eq!(score.sub_scores.pv_lint, None);
assert_eq!(score.sub_scores.rps, Some(40.0));
let disclosed: Vec<&str> = score
.not_measured
.iter()
.map(|n| n.dimension.as_str())
.collect();
assert_eq!(disclosed, ["coverage", "evoscore", "dbc", "pv_lint"]);
for n in &score.not_measured {
assert!(
!n.reason.trim().is_empty(),
"{} is undisclosed: no reason given",
n.dimension
);
}
let json = serde_json::to_value(&score).unwrap();
assert!(json["sub_scores"]["coverage"].is_null(), "{json}");
assert_eq!(json["dimensions_measured"], 4);
}
#[test]
fn nothing_measured_yields_no_composite_and_no_grade() {
let dims: [Dimension; 8] = std::array::from_fn(|i| Err(format!("dim {i} unmeasurable")));
let score = assemble_score("sha".into(), "ts".into(), dims, HashMap::new(), 0, 0);
assert_eq!(score.composite, None);
assert_eq!(score.grade, "n/a");
assert_eq!(score.dimensions_measured, 0);
assert_eq!(score.not_measured.len(), 8);
}
#[test]
fn test_coverage_without_cache_is_not_measured_and_with_cache_is() {
let tmp = TempDir::new().unwrap();
let reason = read_coverage_cache(tmp.path()).expect_err("no cache ⇒ no coverage");
assert!(reason.contains("coverage.result"), "reason: {reason}");
mkdir(&tmp.path().join(".pmat-metrics"));
write(
&tmp.path().join(".pmat-metrics/coverage.result"),
r#"{"coverage_pct": 12.5}"#,
);
assert_eq!(read_coverage_cache(tmp.path()), Ok(12.5));
}
#[test]
fn test_evoscore_without_history_is_not_measured_and_with_history_is() {
let tmp = TempDir::new().unwrap();
let reason = compute_evoscore(tmp.path()).expect_err("no records ⇒ no test history");
assert!(reason.contains("test-record"), "reason: {reason}");
mkdir(&tmp.path().join(".pmat-metrics"));
write(
&tmp.path().join(".pmat-metrics/commit-deadbee-tests.json"),
r#"{"commit":"deadbee","pass":7,"total":10}"#,
);
assert_eq!(compute_evoscore(tmp.path()), Ok(70.0));
}
#[test]
fn test_dbc_without_work_contracts_is_not_measured() {
let tmp = TempDir::new().unwrap();
let reason = compute_dbc(tmp.path()).expect_err("no .pmat-work ⇒ no contracts");
assert!(reason.contains("contract.json"), "reason: {reason}");
}
#[test]
fn test_evoscore_prefers_the_recorded_commit_over_directory_order() {
let tmp = TempDir::new().unwrap();
mkdir(&tmp.path().join(".pmat-metrics"));
for (sha, pass) in [("aaaaaaa", 1u32), ("bbbbbbb", 5), ("ccccccc", 9)] {
write(
&tmp.path()
.join(format!(".pmat-metrics/commit-{sha}-tests.json")),
&format!(r#"{{"commit":"{sha}","pass":{pass},"total":10}}"#),
);
}
assert_eq!(compute_evoscore(tmp.path()), Ok(90.0));
}
#[test]
fn test_renderers_say_not_measured_instead_of_a_number() {
let (dims, _) = unmeasured_subject();
let score = assemble_score("sha".into(), "ts".into(), dims, HashMap::new(), 0, 0);
let text = render_score(&score, &RepoScoreOutputFormat::Text).unwrap();
assert!(text.contains("not measured"), "text: {text}");
assert!(text.contains("no coverage run"), "text: {text}");
assert!(text.contains("4/8"), "text must state the coverage: {text}");
let md = render_score(&score, &RepoScoreOutputFormat::Markdown).unwrap();
assert!(md.contains("| Coverage | not measured |"), "md: {md}");
assert!(md.contains("Not Measured"), "md: {md}");
assert!(md.contains("**Dimensions measured**: 4/8"), "md: {md}");
}
#[test]
fn test_cross_validate_xv011_high_composite_on_partial_measurement() {
let (dims, _) = unmeasured_subject();
let mut score = assemble_score("sha".into(), "ts".into(), dims, HashMap::new(), 1, 0);
score.composite = Some(85.0);
let violations = cross_validate(&score);
assert!(
violations.iter().any(|v| v.id == "XV-011"),
"expected XV-011 when a composite of 85 covers only 4/8 dimensions"
);
}
#[test]
fn test_cross_validate_xv010_needs_a_measured_coverage() {
let mut subs = zero_subs();
subs.coverage = None;
let score = dummy_score(subs, 85.0, 5);
let violations = cross_validate(&score);
assert!(
!violations.iter().any(|v| v.id == "XV-010"),
"XV-010 claims 'coverage < 50' about a coverage that was never measured"
);
}
#[test]
fn no_sub_score_uses_50_as_an_unmeasured_sentinel() {
const HANDLER: &str = include_str!("score_handler.rs");
const COMPUTE: &str = include_str!("score_handler_compute.rs");
for (name, source) in [
("score_handler.rs", HANDLER),
("score_handler_compute.rs", COMPUTE),
] {
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
assert!(
!production.contains("!= 50.0"),
"{name} excludes a dimension from the composite by comparing it \
against the magic float 50.0; unmeasured must be representable"
);
for line in production.lines() {
let code = line.split("//").next().unwrap_or(line);
assert!(
!code.contains("return 50.0"),
"{name} returns the literal 50.0 as a sub-score fallback, \
which is indistinguishable from a measurement: {line:?}"
);
}
}
}
#[test]
fn test_render_score_yaml_and_markdown_are_not_the_text_banner() {
let score = dummy_score(zero_subs(), 58.3, 2);
let text = render_score(&score, &RepoScoreOutputFormat::Text).unwrap();
let yaml = render_score(&score, &RepoScoreOutputFormat::Yaml).unwrap();
let markdown = render_score(&score, &RepoScoreOutputFormat::Markdown).unwrap();
assert_ne!(
yaml, text,
"-f yaml must not fall through to the text renderer"
);
assert_ne!(
markdown, text,
"-f markdown must not fall through to the text renderer"
);
let parsed: serde_yaml_ng::Value = serde_yaml_ng::from_str(&yaml).expect("valid YAML");
assert!(parsed.get("composite").is_some(), "yaml: {yaml}");
assert!(
markdown.starts_with("# PMAT Unified Score"),
"md: {markdown}"
);
assert!(markdown.contains("| RPS |"), "md: {markdown}");
}
}