use super::assessment::{assess_project, phase_score};
use super::config::EnforcementConfig;
use super::enforcement::{execute_main_loop, handle_special_modes, run_enforcement_step};
use super::output::format_violations_output;
use super::states::handle_analyzing_state;
use super::types::{EnforcementState, QualityProfile, QualityViolation};
use crate::cli::EnforceOutputFormat;
use std::path::{Path, PathBuf};
fn empty_but_valid_crate(dir: &Path) {
std::fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.expect("manifest");
std::fs::create_dir_all(dir.join("src")).expect("mkdir src");
}
fn types_of(violations: &[serde_json::Value]) -> Vec<String> {
let mut types: Vec<String> = violations
.iter()
.map(|v| v["violation_type"].as_str().unwrap_or("?").to_string())
.collect();
types.sort();
types
}
async fn special_mode_json(
list_violations: bool,
validate_only: bool,
project: &Path,
report: &Path,
) -> serde_json::Value {
handle_special_modes(
list_violations,
validate_only,
&project.to_path_buf(),
&QualityProfile::default(),
EnforceOutputFormat::Json,
false, None,
Some(report),
None,
None,
)
.await
.expect("the mode runs")
.expect("a special mode was requested")
.expect("the report is emitted");
serde_json::from_str(&std::fs::read_to_string(report).expect("-o wrote the report"))
.expect("the report is JSON")
}
#[tokio::test]
async fn list_violations_and_the_state_machine_report_the_same_run() {
let dir = tempfile::tempdir().expect("tempdir");
empty_but_valid_crate(dir.path());
let source = assess_project(dir.path(), &QualityProfile::default(), None, None, None)
.await
.expect("the assessment runs");
assert!(
source.any_unmeasured(),
"fixture must have something that cannot be measured, or this test proves nothing: \
{}/{} dimensions measured",
source.measured_phases,
source.total_phases
);
let listed = special_mode_json(true, false, dir.path(), &dir.path().join("listed.json")).await;
let listed_violations = listed["violations"]
.as_array()
.expect("the listing carries violations")
.clone();
let validated =
special_mode_json(false, true, dir.path(), &dir.path().join("validated.json")).await;
let validated_violations = validated["violations"]
.as_array()
.expect("the result carries violations")
.clone();
let analyzed = handle_analyzing_state(
dir.path(),
&QualityProfile::default(),
false,
true,
None,
None,
None,
)
.await
.expect("the state machine runs");
assert_eq!(
types_of(&listed_violations),
types_of(&validated_violations),
"--list-violations and --validate-only described different runs of the same directory: \
{listed_violations:?} vs {validated_violations:?}"
);
assert_eq!(
listed_violations.len(),
analyzed.violations.len(),
"--list-violations disagreed with the state machine: {} vs {}",
listed_violations.len(),
analyzed.violations.len()
);
assert_eq!(
listed_violations.len(),
source.violations.len(),
"a surface invented or dropped violations on the way out of the assessment"
);
let disclosed = types_of(&listed_violations)
.into_iter()
.filter(|t| t == "not_measured")
.count();
assert!(
disclosed > 0,
"the not_measured disclosures did not survive into --list-violations: {listed_violations:?}"
);
assert_eq!(
disclosed,
source.total_phases - source.measured_phases,
"one disclosure per unmeasured dimension"
);
assert_eq!(validated["state"], "VIOLATING");
assert_ne!(analyzed.state, EnforcementState::Complete);
assert_eq!(source.verdict_state(), EnforcementState::Violating);
let by_type: usize = listed["summary"]["by_type"]
.as_object()
.expect("by_type is a map")
.values()
.map(|v| v.as_u64().unwrap_or(0) as usize)
.sum();
assert_eq!(
by_type,
listed_violations.len(),
"the summary breakdown does not add up to the violations it summarises: {}",
listed["summary"]
);
}
#[tokio::test]
async fn list_violations_refuses_a_path_it_cannot_read() {
let missing = PathBuf::from("/nonexistent/pmat/enforce/r04");
let err = handle_special_modes(
true,
false,
&missing,
&QualityProfile::default(),
EnforceOutputFormat::Json,
false,
None,
None,
None,
None,
)
.await
.expect("the mode runs")
.expect("a special mode was requested")
.expect_err("a path that cannot be read must not be listed as clean");
assert!(
err.to_string().contains("path not found"),
"the refusal must name the cause: {err}"
);
}
fn measured_violating_crate(dir: &Path, lines_hit: u32, lines_found: u32) {
std::fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.expect("manifest");
std::fs::create_dir_all(dir.join("src")).expect("mkdir src");
std::fs::write(
dir.join("src/lib.rs"),
"// TODO: fix this later\npub fn add(a: i32, b: i32) -> i32 { a + b }\n",
)
.expect("source");
std::fs::write(
dir.join("lcov.info"),
format!(
"SF:src/lib.rs\nDA:1,{lines_hit}\nLF:{lines_found}\nLH:{lines_hit}\nend_of_record\n"
),
)
.expect("lcov");
}
fn coverage_violation(current: f64, target: f64) -> QualityViolation {
QualityViolation {
violation_type: "coverage".to_string(),
severity: "high".to_string(),
location: "project".to_string(),
current,
target,
suggestion: "Increase test coverage".to_string(),
}
}
#[test]
fn a_coverage_breach_is_scored_from_below_not_above() {
assert!(
(phase_score(&[coverage_violation(0.0, 80.0)]) - 0.0).abs() < 1e-9,
"0% against an 80% floor is a total breach, not a clean phase: {}",
phase_score(&[coverage_violation(0.0, 80.0)])
);
assert!(
(phase_score(&[coverage_violation(40.0, 80.0)]) - 0.5).abs() < 1e-9,
"40% against an 80% floor is half the required evidence: {}",
phase_score(&[coverage_violation(40.0, 80.0)])
);
assert!(
(phase_score(&[QualityViolation {
violation_type: "complexity".to_string(),
severity: "high".to_string(),
location: "a.rs:1:f".to_string(),
current: 40.0,
target: 10.0,
suggestion: String::new(),
}]) - 0.25)
.abs()
< 1e-9
);
}
#[tokio::test]
async fn a_project_with_no_coverage_cannot_score_a_full_mark() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.expect("manifest");
std::fs::create_dir_all(dir.path().join("src")).expect("mkdir src");
std::fs::write(
dir.path().join("src/lib.rs"),
"pub fn add(a: i32, b: i32) -> i32 { a + b }\n",
)
.expect("source");
std::fs::write(
dir.path().join("lcov.info"),
"SF:src/lib.rs\nDA:1,0\nLF:10\nLH:0\nend_of_record\n",
)
.expect("lcov");
let assessment = assess_project(dir.path(), &QualityProfile::default(), None, None, None)
.await
.expect("the assessment runs");
assert!(
assessment
.violations
.iter()
.any(|v| v.violation_type == "coverage"),
"fixture must breach the coverage floor, or this test proves nothing: {:?}",
assessment.violations
);
assert!(
assessment.score < 1.0,
"a run holding a coverage violation reported a perfect score of {}",
assessment.score
);
assert_eq!(assessment.verdict_state(), EnforcementState::Violating);
}
#[tokio::test]
async fn the_refactoring_state_reports_the_run_it_did_not_change() {
let dir = tempfile::tempdir().expect("tempdir");
measured_violating_crate(dir.path(), 1, 1);
let profile = QualityProfile::default();
let source = assess_project(dir.path(), &profile, None, None, None)
.await
.expect("the assessment runs");
assert!(
!source.violations.is_empty(),
"fixture must violate something, or this test proves nothing"
);
let refactored = run_enforcement_step(
&dir.path().to_path_buf(),
&profile,
EnforcementState::Refactoring,
false,
false,
true,
None,
None,
None,
)
.await
.expect("the refactoring step runs");
assert_eq!(
refactored.violations.len(),
source.violations.len(),
"the refactoring step cleared a violation list it never fixed: {:?}",
refactored.violations
);
assert!(
(refactored.score - source.score).abs() < f64::EPSILON,
"the refactoring step invented an improvement: {} vs the measured {}",
refactored.score,
source.score
);
}
#[tokio::test]
async fn apply_suggestions_agrees_with_every_other_surface_and_terminates() {
let dir = tempfile::tempdir().expect("tempdir");
measured_violating_crate(dir.path(), 1, 1);
let profile = QualityProfile::default();
let report = dir.path().join("iteration.json");
let source = assess_project(dir.path(), &profile, None, None, None)
.await
.expect("the assessment runs");
assert!(
!source.violations.is_empty(),
"fixture must violate something"
);
let config = EnforcementConfig {
max_iterations: 100,
target_improvement: None,
max_time: None,
apply_suggestions: true,
specific_file: None,
include_pattern: None,
exclude_pattern: None,
single_file_mode: false,
dry_run: false,
show_progress: false,
format: EnforceOutputFormat::Json,
ci_mode: false,
};
let result = execute_main_loop(
&dir.path().to_path_buf(),
&profile,
&config,
std::time::Instant::now(),
Some(report.as_path()),
)
.await
.expect("the loop reports");
assert!(
result.final_iteration <= 6,
"no automated refactoring exists, so re-measuring the same tree {} times measures \
nothing new",
result.final_iteration
);
assert!(
(result.final_score - source.score).abs() < f64::EPSILON,
"the loop finished on {} for a run the assessment scores {}",
result.final_score,
source.score
);
assert_ne!(result.final_state, EnforcementState::Complete);
let last: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&report).expect("-o wrote the report"))
.expect("the report is JSON");
assert_eq!(
last["violations"]
.as_array()
.expect("the document carries violations")
.len(),
source.violations.len(),
"the final --apply-suggestions document contradicts the assessment: {last}"
);
}
#[test]
fn list_violations_honours_sarif_like_every_other_surface() {
let violations = vec![
coverage_violation(0.0, 80.0),
QualityViolation {
violation_type: "not_measured".to_string(),
severity: "error".to_string(),
location: "/tmp/x".to_string(),
current: 0.0,
target: 0.0,
suggestion: "dead code could not be measured".to_string(),
},
];
let rendered = format_violations_output(
&violations,
&QualityProfile::default(),
EnforceOutputFormat::Sarif,
)
.expect("the listing renders");
let sarif: serde_json::Value = serde_json::from_str(&rendered)
.expect("--list-violations --format sarif emitted something a SARIF consumer cannot parse");
assert_eq!(sarif["version"], "2.1.0");
let results = sarif["runs"][0]["results"]
.as_array()
.expect("the run carries results");
assert_eq!(results.len(), violations.len());
assert_eq!(
results[1]["level"], "error",
"the not_measured disclosure must not be downgraded on this surface either: {rendered}"
);
}