use super::super::*;
use crate::cli::{DuplicateOutputFormat, DuplicateType};
use crate::services::similarity::{
CloneType, ComprehensiveReport, EntropyBlock, EntropyReport, Location, Metrics, Priority,
RefactoringHint, SimilarBlock,
};
use std::path::PathBuf;
use tempfile::TempDir;
fn create_test_location(file: &str, start: usize, end: usize) -> Location {
Location {
file: PathBuf::from(file),
start_line: start,
end_line: end,
start_column: None,
end_column: None,
}
}
fn create_test_similar_block(id: &str, locations: Vec<Location>) -> SimilarBlock {
SimilarBlock {
id: id.to_string(),
locations,
similarity: 0.95,
clone_type: CloneType::Type1,
lines: 10,
tokens: 50,
content_preview: "fn example() {\n let x = 1;\n}".to_string(),
}
}
fn create_test_entropy_block(file: &str, entropy: f64) -> EntropyBlock {
EntropyBlock {
location: create_test_location(file, 1, 10),
entropy,
category: if entropy > 4.0 {
"Complex".to_string()
} else {
"Repetitive".to_string()
},
suggestion: "Consider refactoring".to_string(),
}
}
fn create_test_refactoring_hint(pattern: &str, priority: Priority) -> RefactoringHint {
RefactoringHint {
locations: vec![
create_test_location("test1.rs", 1, 10),
create_test_location("test2.rs", 5, 15),
],
pattern: pattern.to_string(),
suggestion: "Extract to shared function".to_string(),
priority,
}
}
fn create_test_metrics(dup_pct: f64, avg_entropy: f64, total_clones: usize) -> Metrics {
Metrics {
duplication_percentage: dup_pct,
average_entropy: avg_entropy,
total_clones,
}
}
fn create_empty_report() -> ComprehensiveReport {
ComprehensiveReport {
exact_duplicates: vec![],
structural_similarities: vec![],
semantic_similarities: vec![],
entropy_analysis: None,
refactoring_opportunities: vec![],
metrics: create_test_metrics(0.0, 0.0, 0),
}
}
fn create_populated_report() -> ComprehensiveReport {
let locations1 = vec![
create_test_location("src/file1.rs", 10, 20),
create_test_location("src/file2.rs", 30, 40),
];
let locations2 = vec![
create_test_location("src/file3.rs", 50, 60),
create_test_location("src/file4.rs", 70, 80),
];
ComprehensiveReport {
exact_duplicates: vec![
create_test_similar_block("block1", locations1.clone()),
create_test_similar_block("block2", locations2.clone()),
],
structural_similarities: vec![SimilarBlock {
id: "struct1".to_string(),
locations: locations1.clone(),
similarity: 0.85,
clone_type: CloneType::Type2,
lines: 15,
tokens: 75,
content_preview: "struct Example {}".to_string(),
}],
semantic_similarities: vec![SimilarBlock {
id: "sem1".to_string(),
locations: locations2.clone(),
similarity: 0.78,
clone_type: CloneType::Type4,
lines: 20,
tokens: 100,
content_preview: "impl Trait for Example {}".to_string(),
}],
entropy_analysis: Some(EntropyReport {
average_entropy: 3.5,
high_entropy_blocks: vec![
create_test_entropy_block("complex1.rs", 4.5),
create_test_entropy_block("complex2.rs", 4.2),
],
low_entropy_patterns: vec![
create_test_entropy_block("repetitive1.rs", 1.5),
create_test_entropy_block("repetitive2.rs", 1.8),
],
recommendations: vec![
"Consider breaking down complex functions".to_string(),
"Extract repeated patterns".to_string(),
],
}),
refactoring_opportunities: vec![
create_test_refactoring_hint("Repeated structure", Priority::High),
create_test_refactoring_hint("Similar logic", Priority::Medium),
create_test_refactoring_hint("Minor duplication", Priority::Low),
],
metrics: create_test_metrics(15.5, 3.5, 4),
}
}
#[test]
fn test_build_config_fuzzy() {
let config = build_config(DuplicateType::Fuzzy, 0.7, 5, 50);
assert!(config.enable_ast);
assert!(!config.enable_semantic);
assert_eq!(config.min_lines, 5);
}
#[test]
fn test_build_config_renamed() {
let config = build_config(DuplicateType::Renamed, 0.75, 8, 80);
assert!(config.enable_ast);
assert!(!config.enable_semantic);
}
#[test]
fn test_build_config_semantic() {
let config = build_config(DuplicateType::Semantic, 0.6, 6, 60);
assert!(config.enable_ast);
assert!(config.enable_semantic);
}
#[test]
fn test_build_config_gapped() {
let config = build_config(DuplicateType::Gapped, 0.65, 7, 70);
assert!(config.enable_ast);
assert!(config.enable_semantic);
}
#[test]
fn test_build_config_all() {
let config = build_config(DuplicateType::All, 0.5, 4, 40);
assert!(config.enable_ast);
assert!(config.enable_semantic);
assert!(config.enable_entropy);
}
#[test]
fn test_is_source_file_rust() {
assert!(is_source_file(std::path::Path::new("main.rs")));
assert!(is_source_file(std::path::Path::new("lib.rs")));
}
#[test]
fn test_is_source_file_typescript() {
assert!(is_source_file(std::path::Path::new("app.ts")));
assert!(is_source_file(std::path::Path::new("component.tsx")));
}
#[test]
fn test_is_source_file_javascript() {
assert!(is_source_file(std::path::Path::new("script.js")));
assert!(is_source_file(std::path::Path::new("component.jsx")));
}
#[test]
fn test_is_source_file_python() {
assert!(is_source_file(std::path::Path::new("main.py")));
}
#[test]
fn test_is_source_file_c_cpp() {
assert!(is_source_file(std::path::Path::new("main.c")));
assert!(is_source_file(std::path::Path::new("main.cpp")));
assert!(is_source_file(std::path::Path::new("main.cc")));
assert!(is_source_file(std::path::Path::new("header.h")));
assert!(is_source_file(std::path::Path::new("header.hpp")));
}
#[test]
fn test_is_source_file_other_languages() {
assert!(is_source_file(std::path::Path::new("Main.kt")));
assert!(is_source_file(std::path::Path::new("Main.java")));
assert!(is_source_file(std::path::Path::new("main.go")));
}
#[test]
fn test_is_source_file_non_source() {
assert!(!is_source_file(std::path::Path::new("README.md")));
assert!(!is_source_file(std::path::Path::new("config.toml")));
assert!(!is_source_file(std::path::Path::new("data.json")));
assert!(!is_source_file(std::path::Path::new("script.sh")));
}
#[test]
fn test_is_source_file_no_extension() {
assert!(!is_source_file(std::path::Path::new("Makefile")));
assert!(!is_source_file(std::path::Path::new("README")));
}
#[test]
fn test_should_include_file_no_filters() {
let path = std::path::Path::new("/project/src/main.rs");
assert!(should_include_file(path, &None, &None));
}
#[test]
fn test_should_include_file_with_include_pattern_match() {
let path = std::path::Path::new("/project/src/main.rs");
assert!(should_include_file(path, &Some("src".to_string()), &None));
}
#[test]
fn test_should_include_file_with_include_pattern_no_match() {
let path = std::path::Path::new("/project/tests/test.rs");
assert!(!should_include_file(path, &Some("src".to_string()), &None));
}
#[test]
fn test_should_include_file_with_exclude_pattern_match() {
let path = std::path::Path::new("/project/target/debug/main.rs");
assert!(!should_include_file(
path,
&None,
&Some("target".to_string())
));
}
#[test]
fn test_should_include_file_with_exclude_pattern_no_match() {
let path = std::path::Path::new("/project/src/main.rs");
assert!(should_include_file(
path,
&None,
&Some("target".to_string())
));
}
#[test]
fn test_should_include_file_exclude_takes_precedence() {
let path = std::path::Path::new("/project/src/target/file.rs");
assert!(!should_include_file(
path,
&Some("src".to_string()),
&Some("target".to_string())
));
}
#[test]
fn test_filter_top_files_zero() {
let report = create_empty_report();
let filtered = filter_top_files(report.clone(), 0);
assert_eq!(filtered.metrics.total_clones, report.metrics.total_clones);
}
#[test]
fn test_filter_top_files_nonzero() {
let report = create_populated_report();
let filtered = filter_top_files(report.clone(), 5);
assert_eq!(
filtered.exact_duplicates.len(),
report.exact_duplicates.len()
);
}
#[test]
fn test_format_report_json() {
let report = create_populated_report();
let result = format_report(&report, DuplicateOutputFormat::Json);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("exact_duplicates"));
assert!(output.contains("metrics"));
}
#[test]
fn test_format_report_summary() {
let report = create_populated_report();
let result = format_report(&report, DuplicateOutputFormat::Summary);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Code Similarity Analysis Summary"));
assert!(output.contains("Metrics"));
}
#[test]
fn test_format_report_human() {
let report = create_populated_report();
let result = format_report(&report, DuplicateOutputFormat::Human);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Metrics"));
}
#[test]
fn test_format_report_detailed() {
let report = create_populated_report();
let result = format_report(&report, DuplicateOutputFormat::Detailed);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Comprehensive Code Similarity Report"));
}
#[test]
fn test_format_report_csv() {
let report = create_populated_report();
let result = format_report(&report, DuplicateOutputFormat::Csv);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Type,File1,Start1,End1,File2,Start2,End2,Similarity"));
}
#[test]
fn test_format_report_sarif() {
let report = create_populated_report();
let result = format_report(&report, DuplicateOutputFormat::Sarif);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("$schema"));
assert!(output.contains("pmat-similarity"));
}
#[test]
fn test_format_summary_report_empty() {
let report = create_empty_report();
let result = format_summary_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("# Code Similarity Analysis Summary"));
assert!(output.contains("Duplication: 0.0%"));
assert!(output.contains("Total Clones: 0"));
}
#[test]
fn test_format_summary_report_with_refactoring() {
let report = create_populated_report();
let result = format_summary_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Top Refactoring Opportunities"));
}
#[test]
fn test_format_summary_metrics() {
let metrics = create_test_metrics(12.5, 3.2, 42);
let mut output = String::new();
let result = format_summary_metrics(&mut output, &metrics);
assert!(result.is_ok());
assert!(output.contains("12.5%"));
assert!(output.contains("3.20"));
assert!(output.contains("42"));
}
#[test]
fn test_format_summary_clone_types() {
let report = create_populated_report();
let mut output = String::new();
let result = format_summary_clone_types(&mut output, &report);
assert!(result.is_ok());
assert!(output.contains("Clone Types"));
assert!(output.contains("Exact Duplicates: 2"));
assert!(output.contains("Structural Similarities: 1"));
assert!(output.contains("Semantic Similarities: 1"));
}
#[test]
fn test_format_summary_refactoring_opportunities_empty() {
let opportunities: Vec<RefactoringHint> = vec![];
let mut output = String::new();
let result = format_summary_refactoring_opportunities(&mut output, &opportunities);
assert!(result.is_ok());
assert!(!output.contains("Refactoring Opportunities"));
}
#[test]
fn test_format_summary_refactoring_opportunities_nonempty() {
let opportunities = vec![
create_test_refactoring_hint("Pattern1", Priority::High),
create_test_refactoring_hint("Pattern2", Priority::Medium),
];
let mut output = String::new();
let result = format_summary_refactoring_opportunities(&mut output, &opportunities);
assert!(result.is_ok());
assert!(output.contains("Top Refactoring Opportunities"));
assert!(output.contains("Pattern1"));
assert!(output.contains("Pattern2"));
}
#[test]
fn test_format_summary_refactoring_opportunities_more_than_five() {
let opportunities: Vec<_> = (0..10)
.map(|i| create_test_refactoring_hint(&format!("Pattern{}", i), Priority::High))
.collect();
let mut output = String::new();
let result = format_summary_refactoring_opportunities(&mut output, &opportunities);
assert!(result.is_ok());
assert!(output.contains("Pattern0"));
assert!(output.contains("Pattern4"));
assert!(!output.contains("Pattern5"));
}
#[test]
fn test_format_detailed_report_empty() {
let report = create_empty_report();
let result = format_detailed_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Comprehensive Code Similarity Report"));
assert!(output.contains("Overall Metrics"));
}
#[test]
fn test_format_detailed_report_populated() {
let report = create_populated_report();
let result = format_detailed_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Exact Duplicates"));
assert!(output.contains("Structural Similarities"));
assert!(output.contains("Entropy Analysis"));
assert!(output.contains("Refactoring Opportunities"));
}
#[test]
fn test_format_metrics_section() {
let metrics = create_test_metrics(25.0, 4.5, 100);
let mut output = String::new();
let result = format_metrics_section(&mut output, &metrics);
assert!(result.is_ok());
assert!(output.contains("Overall Metrics"));
assert!(output.contains("25.0%"));
assert!(output.contains("4.50"));
assert!(output.contains("100"));
}
#[test]
fn test_format_exact_duplicates_section_empty() {
let duplicates: Vec<SimilarBlock> = vec![];
let mut output = String::new();
let result = format_exact_duplicates_section(&mut output, &duplicates);
assert!(result.is_ok());
assert!(output.is_empty());
}
#[test]
fn test_format_exact_duplicates_section_nonempty() {
let duplicates = vec![create_test_similar_block(
"test_block",
vec![
create_test_location("file1.rs", 1, 10),
create_test_location("file2.rs", 20, 30),
],
)];
let mut output = String::new();
let result = format_exact_duplicates_section(&mut output, &duplicates);
assert!(result.is_ok());
assert!(output.contains("Exact Duplicates"));
assert!(output.contains("test_block"));
}
#[test]
fn test_format_single_duplicate_block() {
let block = create_test_similar_block(
"dup123",
vec![
create_test_location("src/a.rs", 5, 15),
create_test_location("src/b.rs", 25, 35),
],
);
let mut output = String::new();
let result = format_single_duplicate_block(&mut output, &block);
assert!(result.is_ok());
assert!(output.contains("Block dup123"));
assert!(output.contains("Lines: 10"));
assert!(output.contains("Tokens: 50"));
assert!(output.contains("src/a.rs:5-15"));
assert!(output.contains("src/b.rs:25-35"));
assert!(output.contains("Preview:"));
}
#[test]
fn test_format_structural_similarities_section_empty() {
let similarities: Vec<SimilarBlock> = vec![];
let mut output = String::new();
let result = format_structural_similarities_section(&mut output, &similarities);
assert!(result.is_ok());
assert!(output.is_empty());
}
#[test]
fn test_format_structural_similarities_section_nonempty() {
let similarities = vec![SimilarBlock {
id: "struct_sim".to_string(),
locations: vec![
create_test_location("a.rs", 1, 10),
create_test_location("b.rs", 5, 15),
],
similarity: 0.9,
clone_type: CloneType::Type2,
lines: 10,
tokens: 50,
content_preview: "fn test()".to_string(),
}];
let mut output = String::new();
let result = format_structural_similarities_section(&mut output, &similarities);
assert!(result.is_ok());
assert!(output.contains("Structural Similarities"));
}
#[test]
fn test_format_structural_similarities_section_limits_to_ten() {
let similarities: Vec<_> = (0..20)
.map(|i| SimilarBlock {
id: format!("sim{}", i),
locations: vec![create_test_location("a.rs", i, i + 10)],
similarity: 0.8,
clone_type: CloneType::Type2,
lines: 10,
tokens: 50,
content_preview: "test".to_string(),
})
.collect();
let mut output = String::new();
let result = format_structural_similarities_section(&mut output, &similarities);
assert!(result.is_ok());
assert!(output.contains("sim0"));
assert!(output.contains("sim9"));
assert!(!output.contains("sim10"));
}
#[test]
fn test_format_single_structural_block() {
let block = SimilarBlock {
id: "struct_block".to_string(),
locations: vec![
create_test_location("x.rs", 100, 120),
create_test_location("y.rs", 200, 220),
],
similarity: 0.875,
clone_type: CloneType::Type3,
lines: 20,
tokens: 100,
content_preview: "let x = 1;".to_string(),
};
let mut output = String::new();
let result = format_single_structural_block(&mut output, &block);
assert!(result.is_ok());
assert!(output.contains("Similarity struct_block"));
assert!(output.contains("87.5%"));
assert!(output.contains("Type3"));
assert!(output.contains("x.rs:100-120"));
assert!(output.contains("y.rs:200-220"));
}
#[test]
fn test_format_entropy_analysis_section_none() {
let entropy_analysis: Option<EntropyReport> = None;
let mut output = String::new();
let result = format_entropy_analysis_section(&mut output, &entropy_analysis);
assert!(result.is_ok());
assert!(output.is_empty());
}
#[test]
fn test_format_entropy_analysis_section_some() {
let entropy_analysis = Some(EntropyReport {
average_entropy: 3.5,
high_entropy_blocks: vec![create_test_entropy_block("high.rs", 4.8)],
low_entropy_patterns: vec![create_test_entropy_block("low.rs", 1.2)],
recommendations: vec!["Refactor complex code".to_string()],
});
let mut output = String::new();
let result = format_entropy_analysis_section(&mut output, &entropy_analysis);
assert!(result.is_ok());
assert!(output.contains("Entropy Analysis"));
assert!(output.contains("3.50"));
}
#[test]
fn test_format_entropy_analysis_section_empty_blocks() {
let entropy_analysis = Some(EntropyReport {
average_entropy: 2.5,
high_entropy_blocks: vec![],
low_entropy_patterns: vec![],
recommendations: vec![],
});
let mut output = String::new();
let result = format_entropy_analysis_section(&mut output, &entropy_analysis);
assert!(result.is_ok());
assert!(output.contains("Entropy Analysis"));
assert!(!output.contains("High Complexity"));
assert!(!output.contains("Repetitive Patterns"));
}
#[test]
fn test_format_high_entropy_blocks_empty() {
let entropy = EntropyReport {
average_entropy: 3.0,
high_entropy_blocks: vec![],
low_entropy_patterns: vec![],
recommendations: vec![],
};
let mut output = String::new();
let result = format_high_entropy_blocks(&mut output, &entropy);
assert!(result.is_ok());
assert!(output.is_empty());
}
#[test]
fn test_format_high_entropy_blocks_nonempty() {
let entropy = EntropyReport {
average_entropy: 4.0,
high_entropy_blocks: vec![
create_test_entropy_block("complex1.rs", 4.5),
create_test_entropy_block("complex2.rs", 4.8),
],
low_entropy_patterns: vec![],
recommendations: vec![],
};
let mut output = String::new();
let result = format_high_entropy_blocks(&mut output, &entropy);
assert!(result.is_ok());
assert!(output.contains("High Complexity Code"));
assert!(output.contains("complex1.rs"));
assert!(output.contains("4.50"));
}
#[test]
fn test_format_high_entropy_blocks_limits_to_five() {
let entropy = EntropyReport {
average_entropy: 4.0,
high_entropy_blocks: (0..10)
.map(|i| create_test_entropy_block(&format!("file{}.rs", i), 4.0 + i as f64 * 0.1))
.collect(),
low_entropy_patterns: vec![],
recommendations: vec![],
};
let mut output = String::new();
let result = format_high_entropy_blocks(&mut output, &entropy);
assert!(result.is_ok());
assert!(output.contains("file0.rs"));
assert!(output.contains("file4.rs"));
assert!(!output.contains("file5.rs"));
}
#[test]
fn test_format_low_entropy_patterns_empty() {
let entropy = EntropyReport {
average_entropy: 3.0,
high_entropy_blocks: vec![],
low_entropy_patterns: vec![],
recommendations: vec![],
};
let mut output = String::new();
let result = format_low_entropy_patterns(&mut output, &entropy);
assert!(result.is_ok());
assert!(output.is_empty());
}
#[test]
fn test_format_low_entropy_patterns_nonempty() {
let entropy = EntropyReport {
average_entropy: 2.0,
high_entropy_blocks: vec![],
low_entropy_patterns: vec![
create_test_entropy_block("repetitive1.rs", 1.2),
create_test_entropy_block("repetitive2.rs", 1.5),
],
recommendations: vec![],
};
let mut output = String::new();
let result = format_low_entropy_patterns(&mut output, &entropy);
assert!(result.is_ok());
assert!(output.contains("Repetitive Patterns"));
assert!(output.contains("repetitive1.rs"));
}
#[test]
fn test_format_entropy_block_item() {
let block = create_test_entropy_block("entropy_test.rs", 3.75);
let mut output = String::new();
let result = format_entropy_block_item(&mut output, &block);
assert!(result.is_ok());
assert!(output.contains("entropy_test.rs:1"));
assert!(output.contains("3.75"));
assert!(output.contains("Suggestion:"));
}
#[test]
fn test_format_refactoring_opportunities_section_empty() {
let opportunities: Vec<RefactoringHint> = vec![];
let mut output = String::new();
let result = format_refactoring_opportunities_section(&mut output, &opportunities);
assert!(result.is_ok());
assert!(output.is_empty());
}
#[test]
fn test_format_refactoring_opportunities_section_nonempty() {
let opportunities = vec![
create_test_refactoring_hint("Extract method", Priority::High),
create_test_refactoring_hint("Consolidate", Priority::Low),
];
let mut output = String::new();
let result = format_refactoring_opportunities_section(&mut output, &opportunities);
assert!(result.is_ok());
assert!(output.contains("Refactoring Opportunities"));
assert!(output.contains("Extract method"));
assert!(output.contains("High"));
}
#[test]
fn test_format_single_refactoring_hint() {
let hint = create_test_refactoring_hint("DRY violation", Priority::Medium);
let mut output = String::new();
let result = format_single_refactoring_hint(&mut output, &hint);
assert!(result.is_ok());
assert!(output.contains("### DRY violation"));
assert!(output.contains("Priority: Medium"));
assert!(output.contains("Suggestion:"));
assert!(output.contains("test1.rs:1-10"));
assert!(output.contains("test2.rs:5-15"));
}
#[test]
fn test_print_performance_metrics() {
let report = create_populated_report();
let elapsed = std::time::Duration::from_millis(500);
print_performance_metrics(&report, elapsed);
}
#[test]
fn test_print_performance_metrics_empty_report() {
let report = create_empty_report();
let elapsed = std::time::Duration::from_millis(100);
print_performance_metrics(&report, elapsed);
}
#[test]
fn test_format_csv_report_empty() {
let report = create_empty_report();
let result = format_csv_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.starts_with("Type,File1,Start1,End1,File2,Start2,End2,Similarity"));
assert_eq!(output.lines().count(), 1);
}
#[test]
fn test_format_csv_report_with_duplicates() {
let report = create_populated_report();
let result = format_csv_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Exact,"));
assert!(output.contains(",100.0"));
assert!(output.contains("Structural,"));
}
#[test]
fn test_format_csv_report_single_location_blocks_skipped() {
let mut report = create_empty_report();
report.exact_duplicates.push(SimilarBlock {
id: "single".to_string(),
locations: vec![create_test_location("only.rs", 1, 10)],
similarity: 1.0,
clone_type: CloneType::Type1,
lines: 10,
tokens: 50,
content_preview: "test".to_string(),
});
let result = format_csv_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert_eq!(output.lines().count(), 1);
}
#[test]
fn test_format_sarif_report_empty() {
let report = create_empty_report();
let result = format_sarif_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("\"$schema\""));
assert!(output.contains("\"version\": \"2.1.0\""));
assert!(output.contains("\"results\": []"));
}
#[test]
fn test_format_sarif_report_with_duplicates() {
let report = create_populated_report();
let result = format_sarif_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("duplicate-code"));
assert!(output.contains("warning"));
assert!(output.contains("physicalLocation"));
assert!(output.contains("startLine"));
}
#[test]
fn test_format_sarif_report_contains_tool_info() {
let report = create_empty_report();
let result = format_sarif_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("pmat-similarity"));
assert!(output.contains("paiml-mcp-agent-toolkit"));
}
#[test]
fn test_print_summary() {
let report = create_populated_report();
print_summary(&report);
}
#[test]
fn test_print_summary_empty() {
let report = create_empty_report();
print_summary(&report);
}
#[test]
fn test_print_summary_no_refactoring() {
let mut report = create_populated_report();
report.refactoring_opportunities.clear();
print_summary(&report);
}
#[tokio::test]
async fn test_collect_files_empty_directory() {
let temp_dir = TempDir::new().unwrap();
let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[tokio::test]
async fn test_collect_files_with_source_files() {
let temp_dir = TempDir::new().unwrap();
let rust_file = temp_dir.path().join("test.rs");
std::fs::write(&rust_file, "fn main() {}").unwrap();
let py_file = temp_dir.path().join("test.py");
std::fs::write(&py_file, "def main(): pass").unwrap();
let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
assert!(result.is_ok());
let files = result.unwrap();
assert_eq!(files.len(), 2);
}
#[tokio::test]
async fn test_collect_files_with_include_filter() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
std::fs::create_dir(&src_dir).unwrap();
let src_file = src_dir.join("main.rs");
std::fs::write(&src_file, "fn main() {}").unwrap();
let other_file = temp_dir.path().join("other.rs");
std::fs::write(&other_file, "fn other() {}").unwrap();
let result = collect_files(
&temp_dir.path().to_path_buf(),
&Some("src".to_string()),
&None,
)
.await;
assert!(result.is_ok());
let files = result.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].0.to_string_lossy().contains("src"));
}
#[tokio::test]
async fn test_collect_files_with_exclude_filter() {
let temp_dir = TempDir::new().unwrap();
let target_dir = temp_dir.path().join("target");
std::fs::create_dir(&target_dir).unwrap();
let target_file = target_dir.join("build.rs");
std::fs::write(&target_file, "fn build() {}").unwrap();
let src_file = temp_dir.path().join("main.rs");
std::fs::write(&src_file, "fn main() {}").unwrap();
let result = collect_files(
&temp_dir.path().to_path_buf(),
&None,
&Some("target".to_string()),
)
.await;
assert!(result.is_ok());
let files = result.unwrap();
assert_eq!(files.len(), 1);
assert!(!files[0].0.to_string_lossy().contains("target"));
}
#[tokio::test]
async fn test_collect_files_ignores_non_source_files() {
let temp_dir = TempDir::new().unwrap();
std::fs::write(temp_dir.path().join("README.md"), "# Readme").unwrap();
std::fs::write(temp_dir.path().join("config.toml"), "[config]").unwrap();
std::fs::write(temp_dir.path().join("main.rs"), "fn main() {}").unwrap();
let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
assert!(result.is_ok());
let files = result.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].0.to_string_lossy().ends_with(".rs"));
}
#[tokio::test]
async fn test_handle_analyze_similarity_empty_project() {
let temp_dir = TempDir::new().unwrap();
let result = handle_analyze_similarity(
temp_dir.path().to_path_buf(),
DuplicateType::Exact,
0.8,
6,
50,
DuplicateOutputFormat::Summary,
false,
None,
None,
None,
0,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_analyze_similarity_with_files() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("test1.rs");
let file2 = temp_dir.path().join("test2.rs");
let content = r#"
fn example_function() {
let x = 1;
let y = 2;
let z = x + y;
println!("{}", z);
}
"#;
std::fs::write(&file1, content).unwrap();
std::fs::write(&file2, content).unwrap();
let result = handle_analyze_similarity(
temp_dir.path().to_path_buf(),
DuplicateType::All,
0.7,
3,
10,
DuplicateOutputFormat::Json,
true, None,
None,
None,
5,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_analyze_similarity_with_output_file() {
let temp_dir = TempDir::new().unwrap();
let output_file = temp_dir.path().join("output.json");
let file1 = temp_dir.path().join("test.rs");
std::fs::write(&file1, "fn main() {}").unwrap();
let result = handle_analyze_similarity(
temp_dir.path().to_path_buf(),
DuplicateType::Exact,
0.8,
1,
5,
DuplicateOutputFormat::Json,
false,
None,
None,
Some(output_file.clone()),
0,
)
.await;
assert!(result.is_ok());
assert!(output_file.exists());
}
#[tokio::test]
async fn test_handle_analyze_similarity_all_formats() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("test.rs");
std::fs::write(&file1, "fn main() { let x = 1; }").unwrap();
let formats = vec![
DuplicateOutputFormat::Summary,
DuplicateOutputFormat::Human,
DuplicateOutputFormat::Detailed,
DuplicateOutputFormat::Json,
DuplicateOutputFormat::Csv,
DuplicateOutputFormat::Sarif,
];
for format in formats {
let result = handle_analyze_similarity(
temp_dir.path().to_path_buf(),
DuplicateType::Exact,
0.8,
1,
5,
format,
false,
None,
None,
None,
0,
)
.await;
assert!(result.is_ok(), "Failed for format: {:?}", format);
}
}
#[tokio::test]
async fn test_handle_analyze_similarity_all_detection_types() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("test.rs");
std::fs::write(&file1, "fn main() { let x = 1; }").unwrap();
let types = vec![
DuplicateType::Exact,
DuplicateType::Fuzzy,
DuplicateType::Renamed,
DuplicateType::Semantic,
DuplicateType::Gapped,
DuplicateType::All,
];
for detection_type in types {
let result = handle_analyze_similarity(
temp_dir.path().to_path_buf(),
detection_type.clone(),
0.7,
1,
5,
DuplicateOutputFormat::Summary,
false,
None,
None,
None,
0,
)
.await;
assert!(result.is_ok(), "Failed for type: {:?}", detection_type);
}
}
#[test]
fn test_is_source_file_with_double_extension() {
assert!(is_source_file(std::path::Path::new("file.test.rs")));
assert!(is_source_file(std::path::Path::new("file.spec.ts")));
}
#[test]
fn test_is_source_file_with_hidden_file() {
assert!(is_source_file(std::path::Path::new(".hidden.rs")));
}
#[test]
fn test_should_include_file_with_unicode_path() {
let path = std::path::Path::new("/project/src/файл.rs");
assert!(should_include_file(path, &None, &None));
}
#[test]
fn test_format_csv_escaping() {
let mut report = create_empty_report();
report.exact_duplicates.push(SimilarBlock {
id: "test".to_string(),
locations: vec![
create_test_location("file,with,commas.rs", 1, 10),
create_test_location("normal.rs", 5, 15),
],
similarity: 1.0,
clone_type: CloneType::Type1,
lines: 10,
tokens: 50,
content_preview: "test".to_string(),
});
let result = format_csv_report(&report);
assert!(result.is_ok());
}
#[test]
fn test_format_sarif_with_many_locations() {
let mut report = create_empty_report();
let locations: Vec<_> = (0..100)
.map(|i| create_test_location(&format!("file{}.rs", i), i, i + 10))
.collect();
report.exact_duplicates.push(SimilarBlock {
id: "many_locs".to_string(),
locations,
similarity: 1.0,
clone_type: CloneType::Type1,
lines: 10,
tokens: 50,
content_preview: "test".to_string(),
});
let result = format_sarif_report(&report);
assert!(result.is_ok());
}
#[test]
fn test_metrics_with_extreme_values() {
let metrics = create_test_metrics(100.0, 10.0, 1_000_000);
let mut output = String::new();
let result = format_summary_metrics(&mut output, &metrics);
assert!(result.is_ok());
assert!(output.contains("100.0%"));
assert!(output.contains("10.00"));
assert!(output.contains("1000000"));
}
#[test]
fn test_metrics_with_zero_values() {
let metrics = create_test_metrics(0.0, 0.0, 0);
let mut output = String::new();
let result = format_summary_metrics(&mut output, &metrics);
assert!(result.is_ok());
assert!(output.contains("0.0%"));
assert!(output.contains("0.00"));
assert!(output.contains(": 0"));
}
#[test]
fn test_location_with_columns() {
let location = Location {
file: PathBuf::from("test.rs"),
start_line: 10,
end_line: 20,
start_column: Some(5),
end_column: Some(50),
};
let block = SimilarBlock {
id: "col_test".to_string(),
locations: vec![location],
similarity: 1.0,
clone_type: CloneType::Type1,
lines: 10,
tokens: 50,
content_preview: "test".to_string(),
};
let mut output = String::new();
let result = format_single_duplicate_block(&mut output, &block);
assert!(result.is_ok());
}
#[test]
fn test_all_clone_types_in_detailed_report() {
let mut report = create_empty_report();
for clone_type in [
CloneType::Type1,
CloneType::Type2,
CloneType::Type3,
CloneType::Type4,
] {
report.structural_similarities.push(SimilarBlock {
id: format!("{:?}", clone_type),
locations: vec![
create_test_location("a.rs", 1, 10),
create_test_location("b.rs", 5, 15),
],
similarity: 0.9,
clone_type,
lines: 10,
tokens: 50,
content_preview: "test".to_string(),
});
}
let result = format_detailed_report(&report);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("Type1"));
assert!(output.contains("Type2"));
assert!(output.contains("Type3"));
assert!(output.contains("Type4"));
}
#[test]
fn test_all_priority_levels() {
let mut output = String::new();
for priority in [Priority::High, Priority::Medium, Priority::Low] {
let hint = create_test_refactoring_hint(&format!("{:?}_pattern", priority), priority);
format_single_refactoring_hint(&mut output, &hint).unwrap();
}
assert!(output.contains("High"));
assert!(output.contains("Medium"));
assert!(output.contains("Low"));
}
#[tokio::test]
async fn test_collect_files_nested_directories() {
let temp_dir = TempDir::new().unwrap();
let deep_dir = temp_dir.path().join("a").join("b").join("c");
std::fs::create_dir_all(&deep_dir).unwrap();
std::fs::write(deep_dir.join("deep.rs"), "fn deep() {}").unwrap();
let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
assert!(result.is_ok());
let files = result.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].0.to_string_lossy().contains("deep.rs"));
}