#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_comprehensive_handler_params() {
assert_eq!(
ComprehensiveOutputFormat::Json as i32,
ComprehensiveOutputFormat::Json as i32
);
}
#[test]
fn test_find_project_root() {
use std::fs;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let project_root = temp_dir.path();
let src_dir = project_root.join("src");
let sub_dir = src_dir.join("module");
fs::create_dir_all(&sub_dir).unwrap();
fs::write(
project_root.join("Cargo.toml"),
"[package]\nname = \"test\"",
)
.unwrap();
let test_file = sub_dir.join("test.rs");
fs::write(&test_file, "// test file").unwrap();
let found_root = find_project_root(&test_file).unwrap();
assert_eq!(found_root, project_root);
let found_root = find_project_root(&sub_dir).unwrap();
assert_eq!(found_root, project_root);
let isolated_dir = TempDir::new().unwrap();
let isolated_file = isolated_dir.path().join("isolated.rs");
fs::write(&isolated_file, "// isolated file").unwrap();
let found_root = find_project_root(&isolated_file).unwrap();
assert_eq!(found_root, isolated_dir.path());
}
#[tokio::test]
async fn test_comprehensive_single_file_filter() {
use crate::models::defect_report::{Defect, DefectCategory, Severity};
use std::collections::HashMap;
let defects = [
Defect {
id: "1".to_string(),
category: DefectCategory::Complexity,
severity: Severity::High,
file_path: PathBuf::from("src/main.rs"),
line_start: 10,
line_end: Some(20),
column_start: Some(5),
column_end: Some(10),
message: "High complexity in main".to_string(),
rule_id: "complexity".to_string(),
fix_suggestion: Some("Refactor".to_string()),
metrics: HashMap::from([("confidence".to_string(), 0.8)]),
},
Defect {
id: "2".to_string(),
category: DefectCategory::Complexity,
severity: Severity::Medium,
file_path: PathBuf::from("src/lib.rs"),
line_start: 15,
line_end: Some(25),
column_start: Some(3),
column_end: Some(8),
message: "Medium complexity in lib".to_string(),
rule_id: "complexity".to_string(),
fix_suggestion: Some("Consider refactoring".to_string()),
metrics: HashMap::from([("confidence".to_string(), 0.7)]),
},
];
let target_file = Some(PathBuf::from("src/main.rs"));
let filtered: Vec<_> = defects
.iter()
.filter(|d| {
if let Some(ref tf) = target_file {
d.file_path == *tf
} else {
true
}
})
.collect();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].id, "1");
assert_eq!(filtered[0].file_path, PathBuf::from("src/main.rs"));
}
fn build_empty_config() -> ComprehensiveConfig {
ComprehensiveConfig {
project_path: PathBuf::from("."),
file: None,
files: vec![],
format: ComprehensiveOutputFormat::Json,
include_duplicates: false,
include_dead_code: false,
include_defects: false,
include_complexity: false,
include_tdg: false,
confidence_threshold: 0.0,
min_lines: 0,
include: None,
exclude: None,
output: None,
perf: false,
executive_summary: false,
}
}
fn build_defect(id: &str, file: &str, confidence: f64) -> crate::models::defect_report::Defect {
use crate::models::defect_report::{Defect, DefectCategory, Severity};
use std::collections::HashMap;
Defect {
id: id.to_string(),
severity: Severity::Medium,
category: DefectCategory::Complexity,
file_path: PathBuf::from(file),
line_start: 1,
line_end: None,
column_start: None,
column_end: None,
message: String::new(),
rule_id: "rule".to_string(),
fix_suggestion: None,
metrics: HashMap::from([("confidence".to_string(), confidence)]),
}
}
fn build_empty_report() -> crate::models::defect_report::DefectReport {
use crate::models::defect_report::{DefectReport, DefectSummary, ReportMetadata};
use chrono::Utc;
use std::collections::BTreeMap;
DefectReport {
metadata: ReportMetadata {
tool: "pmat".to_string(),
version: "test".to_string(),
generated_at: Utc::now(),
project_root: PathBuf::from("."),
total_files_analyzed: 0,
analysis_duration_ms: 0,
},
defects: vec![],
summary: DefectSummary {
total_defects: 0,
by_severity: BTreeMap::new(),
by_category: BTreeMap::new(),
hotspot_files: vec![],
},
file_index: BTreeMap::new(),
}
}
#[test]
fn test_is_system_root_tmp() {
assert!(is_system_root(Path::new("/tmp")));
}
#[test]
fn test_is_system_root_root() {
assert!(is_system_root(Path::new("/")));
}
#[test]
fn test_is_system_root_home() {
assert!(is_system_root(Path::new("/home")));
}
#[test]
fn test_is_system_root_other_path_false() {
assert!(!is_system_root(Path::new("/usr/local")));
assert!(!is_system_root(Path::new("/home/user")));
assert!(!is_system_root(Path::new("./relative")));
}
#[test]
fn test_walk_up_to_cargo_toml_finds_self() {
use std::fs;
use tempfile::TempDir;
let tmp = TempDir::new().unwrap();
fs::write(tmp.path().join("Cargo.toml"), "[package]\nname=\"x\"").unwrap();
let found = walk_up_to_cargo_toml(tmp.path());
assert_eq!(found, Some(tmp.path().to_path_buf()));
}
#[test]
fn test_walk_up_to_cargo_toml_finds_ancestor() {
use std::fs;
use tempfile::TempDir;
let tmp = TempDir::new().unwrap();
let nested = tmp.path().join("a").join("b").join("c");
fs::create_dir_all(&nested).unwrap();
fs::write(tmp.path().join("Cargo.toml"), "[package]\nname=\"x\"").unwrap();
let found = walk_up_to_cargo_toml(&nested);
assert_eq!(found, Some(tmp.path().to_path_buf()));
}
#[test]
fn test_walk_up_to_cargo_toml_stops_at_system_root() {
let found = walk_up_to_cargo_toml(Path::new("/tmp/definitely-not-a-real-project-xyz"));
assert!(found.is_none());
}
#[test]
fn test_get_enabled_analyses_none() {
let cfg = build_empty_config();
assert!(get_enabled_analyses(&cfg).is_empty());
}
#[test]
fn test_get_enabled_analyses_complexity_only() {
let mut cfg = build_empty_config();
cfg.include_complexity = true;
assert_eq!(get_enabled_analyses(&cfg), vec!["Complexity"]);
}
#[test]
fn test_get_enabled_analyses_tdg_only() {
let mut cfg = build_empty_config();
cfg.include_tdg = true;
assert_eq!(get_enabled_analyses(&cfg), vec!["TDG"]);
}
#[test]
fn test_get_enabled_analyses_defects_only() {
let mut cfg = build_empty_config();
cfg.include_defects = true;
assert_eq!(get_enabled_analyses(&cfg), vec!["Defects"]);
}
#[test]
fn test_get_enabled_analyses_dead_code_only() {
let mut cfg = build_empty_config();
cfg.include_dead_code = true;
assert_eq!(get_enabled_analyses(&cfg), vec!["Dead Code"]);
}
#[test]
fn test_get_enabled_analyses_duplicates_only() {
let mut cfg = build_empty_config();
cfg.include_duplicates = true;
assert_eq!(get_enabled_analyses(&cfg), vec!["Duplicates"]);
}
#[test]
fn test_get_enabled_analyses_all_in_order() {
let mut cfg = build_empty_config();
cfg.include_complexity = true;
cfg.include_tdg = true;
cfg.include_defects = true;
cfg.include_dead_code = true;
cfg.include_duplicates = true;
assert_eq!(
get_enabled_analyses(&cfg),
vec!["Complexity", "TDG", "Defects", "Dead Code", "Duplicates"]
);
}
#[test]
fn test_filter_defects_threshold_keeps_high_confidence() {
let defects = vec![
build_defect("a", "src/x.rs", 0.9),
build_defect("b", "src/y.rs", 0.4),
];
let kept = filter_defects(&defects, false, &[], 0.5);
assert_eq!(kept.len(), 1);
assert_eq!(kept[0].id, "a");
}
#[test]
fn test_filter_defects_threshold_default_confidence_when_missing() {
let mut defect = build_defect("a", "src/x.rs", 0.0);
defect.metrics.clear();
let kept = filter_defects(&[defect], false, &[], 0.99);
assert_eq!(kept.len(), 1);
}
#[test]
fn test_filter_defects_single_file_mode_keeps_target() {
let defects = vec![
build_defect("a", "src/main.rs", 1.0),
build_defect("b", "src/lib.rs", 1.0),
];
let targets = vec![PathBuf::from("src/main.rs")];
let kept = filter_defects(&defects, true, &targets, 0.0);
assert_eq!(kept.len(), 1);
assert_eq!(kept[0].id, "a");
}
#[test]
fn test_filter_defects_single_file_mode_empty_targets_keeps_all() {
let defects = vec![
build_defect("a", "src/x.rs", 1.0),
build_defect("b", "src/y.rs", 1.0),
];
let kept = filter_defects(&defects, true, &[], 0.0);
assert_eq!(kept.len(), 2);
}
#[test]
fn test_filter_defects_project_mode_ignores_targets() {
let defects = vec![
build_defect("a", "src/main.rs", 1.0),
build_defect("b", "src/lib.rs", 1.0),
];
let targets = vec![PathBuf::from("src/main.rs")];
let kept = filter_defects(&defects, false, &targets, 0.0);
assert_eq!(kept.len(), 2);
}
#[test]
fn test_determine_analysis_mode_project_path() {
let mut cfg = build_empty_config();
cfg.project_path = PathBuf::from("/my/project");
let (path, single, targets) = determine_analysis_mode(&cfg).unwrap();
assert_eq!(path, PathBuf::from("/my/project"));
assert!(!single);
assert!(targets.is_empty());
}
#[test]
fn test_determine_analysis_mode_single_file_promotes_to_target() {
use std::fs;
use tempfile::TempDir;
let tmp = TempDir::new().unwrap();
fs::write(tmp.path().join("Cargo.toml"), "[package]\nname=\"x\"").unwrap();
let file = tmp.path().join("src.rs");
fs::write(&file, "// src").unwrap();
let mut cfg = build_empty_config();
cfg.file = Some(file.clone());
let (path, single, targets) = determine_analysis_mode(&cfg).unwrap();
assert_eq!(path, tmp.path());
assert!(single);
assert_eq!(targets, vec![file]);
}
#[test]
fn test_determine_analysis_mode_files_override_takes_precedence() {
let mut cfg = build_empty_config();
cfg.files = vec![PathBuf::from("a.rs"), PathBuf::from("b.rs")];
cfg.file = Some(PathBuf::from("ignored.rs"));
cfg.file = None;
let (_path, single, targets) = determine_analysis_mode(&cfg).unwrap();
assert!(!single);
assert_eq!(targets, vec![PathBuf::from("a.rs"), PathBuf::from("b.rs")]);
}
#[test]
fn test_format_report_json() {
let svc = crate::services::defect_report_service::DefectReportService::new();
let report = build_empty_report();
let mut cfg = build_empty_config();
cfg.format = ComprehensiveOutputFormat::Json;
let out = format_report(&svc, &report, vec![], &cfg).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&out).unwrap();
assert!(parsed.is_object());
assert!(parsed.get("defects").is_some());
}
#[test]
fn test_format_report_summary_yields_text() {
let svc = crate::services::defect_report_service::DefectReportService::new();
let report = build_empty_report();
let mut cfg = build_empty_config();
cfg.format = ComprehensiveOutputFormat::Summary;
let out = format_report(&svc, &report, vec![], &cfg).unwrap();
assert!(!out.is_empty());
}
#[test]
fn test_format_report_detailed_yields_text() {
let svc = crate::services::defect_report_service::DefectReportService::new();
let report = build_empty_report();
let mut cfg = build_empty_config();
cfg.format = ComprehensiveOutputFormat::Detailed;
let out = format_report(&svc, &report, vec![], &cfg).unwrap();
assert!(!out.is_empty());
}
#[test]
fn test_format_report_markdown_yields_text() {
let svc = crate::services::defect_report_service::DefectReportService::new();
let report = build_empty_report();
let mut cfg = build_empty_config();
cfg.format = ComprehensiveOutputFormat::Markdown;
let out = format_report(&svc, &report, vec![], &cfg).unwrap();
assert!(!out.is_empty());
}
#[test]
fn test_format_report_sarif_yields_json() {
let svc = crate::services::defect_report_service::DefectReportService::new();
let report = build_empty_report();
let mut cfg = build_empty_config();
cfg.format = ComprehensiveOutputFormat::Sarif;
let out = format_report(&svc, &report, vec![], &cfg).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&out).unwrap();
assert!(parsed.is_object());
}
#[test]
fn test_format_report_overrides_defects_with_filtered() {
let svc = crate::services::defect_report_service::DefectReportService::new();
let report = build_empty_report();
let mut cfg = build_empty_config();
cfg.format = ComprehensiveOutputFormat::Json;
let filtered = vec![build_defect("seen", "src/x.rs", 1.0)];
let out = format_report(&svc, &report, filtered, &cfg).unwrap();
assert!(out.contains("\"seen\""));
}
#[test]
fn test_warn_ignored_parameters_is_noop() {
warn_ignored_parameters(&build_empty_config());
}
#[test]
fn test_print_performance_metrics_runs() {
let report = build_empty_report();
print_performance_metrics(std::time::Duration::from_millis(1), &report);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}