use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
include!("duplicates_detection.rs");
include!("duplicates_extraction.rs");
include!("duplicates_output.rs");
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn strip_ansi(s: &str) -> String {
let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap();
re.replace_all(s, "").to_string()
}
#[test]
fn test_normalize_block() {
let lines = vec![" fn test() {", " // comment", " let x = 1;", " }"];
let normalized = normalize_block(&lines);
assert!(!normalized.contains("// comment"));
assert!(normalized.contains("fn test()"));
assert_eq!(normalized, "fn test() {\nlet x = 1;\n}");
}
#[test]
fn test_count_tokens() {
assert_eq!(count_tokens("fn test() { }"), 4);
assert_eq!(count_tokens("let x = 1;"), 4);
assert_eq!(count_tokens(""), 0);
assert_eq!(count_tokens(" \n \t "), 0);
}
#[test]
fn test_is_function_declaration() {
assert!(is_function_declaration("fn main() {"));
assert!(is_function_declaration("function test() {"));
assert!(is_function_declaration("def calculate():"));
assert!(!is_function_declaration("let x = 1;"));
}
#[test]
fn test_is_type_declaration() {
assert!(is_type_declaration("class Foo {"));
assert!(is_type_declaration("struct Bar {"));
assert!(is_type_declaration("impl Display for Foo {"));
assert!(!is_type_declaration("let x = 1;"));
}
#[test]
fn test_is_block_opening() {
assert!(is_block_opening("fn main() {"));
assert!(is_block_opening("if true {"));
assert!(!is_block_opening("{ x: 1 }"));
assert!(!is_block_opening("let x = 1;"));
}
#[test]
fn test_is_block_start() {
assert!(is_block_start("fn main() {"));
assert!(is_block_start("function test() {"));
assert!(is_block_start("def calculate():"));
assert!(is_block_start("class Foo {"));
assert!(is_block_start("struct Bar {"));
assert!(is_block_start("impl Display for Foo {"));
assert!(is_block_start("if condition {"));
assert!(!is_block_start("let x = 1;"));
assert!(!is_block_start("{ x: 1 }"));
}
#[test]
fn test_is_source_file() {
assert!(is_source_file(Path::new("test.rs")));
assert!(is_source_file(Path::new("test.js")));
assert!(is_source_file(Path::new("test.ts")));
assert!(is_source_file(Path::new("test.py")));
assert!(is_source_file(Path::new("test.java")));
assert!(is_source_file(Path::new("test.cpp")));
assert!(is_source_file(Path::new("test.c")));
assert!(is_source_file(Path::new("test.kt")));
assert!(is_source_file(Path::new("test.kts")));
assert!(!is_source_file(Path::new("test.txt")));
assert!(!is_source_file(Path::new("README.md")));
}
#[test]
fn test_should_process_file() {
let path = Path::new("src/main.rs");
assert!(should_process_file(path, &None, &None));
assert!(should_process_file(path, &Some("src".to_string()), &None));
assert!(!should_process_file(
path,
&Some("tests".to_string()),
&None
));
assert!(!should_process_file(path, &None, &Some("src".to_string())));
assert!(should_process_file(path, &None, &Some("tests".to_string())));
assert!(!should_process_file(
path,
&Some("src".to_string()),
&Some("src".to_string())
));
}
#[test]
fn test_find_block_end() {
let lines = vec![
"fn test() {",
" let x = 1;",
" if true {",
" println!(\"hello\");",
" }",
"}",
];
assert_eq!(find_block_end(&lines), Some(6));
let lines2 = vec!["fn test() {", " let x = 1;"];
assert_eq!(find_block_end(&lines2), None);
}
#[test]
fn test_extract_exact_blocks() {
let lines = vec![
"fn test1() {",
" let x = 1;",
" println!(\"x = {}\", x);",
"}",
"",
"fn test2() {",
" let y = 2;",
" println!(\"y = {}\", y);",
"}",
];
let mut blocks = Vec::new();
extract_exact_blocks(&mut blocks, &lines, "test.rs", 3, 100);
assert!(!blocks.is_empty());
assert!(blocks.iter().all(|(_, file, _, _, _)| file == "test.rs"));
}
#[test]
fn test_find_duplicate_blocks_no_duplicates() {
let blocks = vec![
(
"hash1".to_string(),
"file1.rs".to_string(),
1,
10,
"content1".to_string(),
),
(
"hash2".to_string(),
"file2.rs".to_string(),
1,
10,
"content2".to_string(),
),
];
let duplicates = find_duplicate_blocks(blocks, 0.8);
assert!(duplicates.is_empty());
}
#[test]
fn test_find_duplicate_blocks_with_duplicates() {
let blocks = vec![
(
"hash1".to_string(),
"file1.rs".to_string(),
1,
10,
"content1".to_string(),
),
(
"hash1".to_string(),
"file2.rs".to_string(),
20,
29,
"content1".to_string(),
),
(
"hash2".to_string(),
"file3.rs".to_string(),
1,
5,
"content2".to_string(),
),
];
let duplicates = find_duplicate_blocks(blocks, 0.8);
assert_eq!(duplicates.len(), 1);
assert_eq!(duplicates[0].hash, "hash1");
assert_eq!(duplicates[0].locations.len(), 2);
assert_eq!(duplicates[0].lines, 10);
}
#[test]
fn test_file_stats_calculation() {
let mut stats = FileStats {
duplicate_lines: 20,
total_lines: 100,
duplication_percentage: 0.0,
};
stats.duplication_percentage =
(stats.duplicate_lines as f32 / stats.total_lines as f32) * 100.0;
assert_eq!(stats.duplication_percentage, 20.0);
}
#[tokio::test]
async fn test_detect_duplicates_empty_project() {
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let result = detect_duplicates(
temp_dir.path(),
crate::cli::DuplicateType::Exact,
0.8,
5,
100,
&None,
&None,
)
.await;
assert!(result.is_ok());
let report = result.unwrap();
assert_eq!(report.total_duplicates, 0);
assert_eq!(report.duplicate_lines, 0);
assert_eq!(report.total_lines, 0);
assert_eq!(report.duplication_percentage, 0.0);
}
#[test]
fn test_format_json_output() {
let report = DuplicateReport {
total_duplicates: 1,
duplicate_lines: 10,
total_lines: 100,
duplication_percentage: 10.0,
duplicate_blocks: vec![],
file_statistics: HashMap::new(),
};
let result = format_json_output(&report);
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.contains("\"total_duplicates\": 1"));
assert!(json.contains("\"duplication_percentage\": 10.0"));
}
#[test]
fn test_format_human_output() {
let report = DuplicateReport {
total_duplicates: 2,
duplicate_lines: 20,
total_lines: 100,
duplication_percentage: 20.0,
duplicate_blocks: vec![DuplicateBlock {
hash: "hash1".to_string(),
locations: vec![
DuplicateLocation {
file: "file1.rs".to_string(),
start_line: 10,
end_line: 20,
content_preview: "fn test() {".to_string(),
},
DuplicateLocation {
file: "file2.rs".to_string(),
start_line: 30,
end_line: 40,
content_preview: "fn test() {".to_string(),
},
],
lines: 10,
tokens: 20,
similarity: 1.0,
}],
file_statistics: HashMap::new(),
};
let result = format_human_output(&report);
assert!(result.is_ok());
let output = strip_ansi(&result.unwrap());
assert!(output.contains("Duplicate Code Analysis"));
assert!(output.contains("Total duplicate blocks:"));
assert!(output.contains("2"));
assert!(output.contains("Block 1"));
assert!(output.contains("10 lines, 2 locations"));
}
}
#[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);
}
}
}