#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[tokio::test]
async fn test_incremental_churn_cache() {
let analyzer = IncrementalChurnAnalyzer::new(PathBuf::from("."));
let (size, _) = analyzer.cache_stats();
assert_eq!(size, 0);
}
#[tokio::test]
async fn test_cache_stats() {
let temp_dir = TempDir::new().unwrap();
let analyzer = IncrementalChurnAnalyzer::new(temp_dir.path().to_path_buf());
let (size, memory) = analyzer.cache_stats();
assert_eq!(size, 0);
assert_eq!(memory, 0);
analyzer.clear_cache();
let (size, _) = analyzer.cache_stats();
assert_eq!(size, 0);
}
#[tokio::test]
async fn test_parse_commit_line() {
let line = "abc123|John Doe|2024-01-01T12:00:00Z";
let result = IncrementalChurnAnalyzer::parse_commit_line(line);
assert!(result.is_some());
let (hash, author, date) = result.unwrap();
assert_eq!(hash, "abc123");
assert_eq!(author, "John Doe");
assert_eq!(date, "2024-01-01T12:00:00Z");
let invalid = "invalid line";
assert!(IncrementalChurnAnalyzer::parse_commit_line(invalid).is_none());
}
#[tokio::test]
async fn test_parse_numstat_line() {
let line = "10\t20\tsrc/main.rs";
let result = IncrementalChurnAnalyzer::parse_numstat_line(line);
assert!(result.is_some());
let (additions, deletions, path) = result.unwrap();
assert_eq!(additions, 10);
assert_eq!(deletions, 20);
assert_eq!(path, "src/main.rs");
let line_with_spaces = "5\t15\tsrc/my file.rs";
let result = IncrementalChurnAnalyzer::parse_numstat_line(line_with_spaces);
assert!(result.is_some());
let (_, _, path) = result.unwrap();
assert_eq!(path, "src/my file.rs");
assert!(IncrementalChurnAnalyzer::parse_numstat_line("invalid").is_none());
}
#[tokio::test]
async fn test_generate_summary() {
let analyzer = IncrementalChurnAnalyzer::new(PathBuf::from("."));
let mut metrics = vec![];
for i in 0..5 {
let mut m = FileChurnMetrics {
path: PathBuf::from(format!("file{}.rs", i)),
relative_path: format!("file{}.rs", i),
commit_count: 10 - i * 2,
unique_authors: vec![format!("Author{}", i)],
additions: 100,
deletions: 50,
churn_score: (0.9 - (i as f64 * 0.2)) as f32,
last_modified: Utc::now(),
first_seen: Utc::now(),
};
m.calculate_churn_score(10, 150);
metrics.push(m);
}
let summary = analyzer.generate_summary(&metrics);
assert_eq!(summary.total_files_changed, 5);
assert_eq!(summary.total_commits, 30); assert_eq!(summary.author_contributions.len(), 5);
assert!(summary.hotspot_files.len() <= 10);
assert!(summary.stable_files.len() <= 10);
}
#[tokio::test]
async fn test_get_file_churn_no_git() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(&test_file, "test content").unwrap();
let analyzer = IncrementalChurnAnalyzer::new(temp_dir.path().to_path_buf());
let result = analyzer.get_file_churn(&test_file).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_analyze_incremental_empty() {
let temp_dir = TempDir::new().unwrap();
let analyzer = IncrementalChurnAnalyzer::new(temp_dir.path().to_path_buf());
let result = analyzer.analyze_incremental(vec![], 30).await;
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.files.len(), 0);
assert_eq!(analysis.period_days, 30);
}
#[tokio::test]
async fn test_batch_compute_churn_no_git() {
let temp_dir = TempDir::new().unwrap();
let analyzer = IncrementalChurnAnalyzer::new(temp_dir.path().to_path_buf());
let files = vec![
temp_dir.path().join("file1.rs"),
temp_dir.path().join("file2.rs"),
];
let result = analyzer.batch_compute_churn(&files, 30).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_get_current_commit_hash_no_git() {
let temp_dir = TempDir::new().unwrap();
let analyzer = IncrementalChurnAnalyzer::new(temp_dir.path().to_path_buf());
let result = analyzer.get_current_commit_hash().await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_get_file_last_commit_hash_no_git() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(&test_file, "test content").unwrap();
let analyzer = IncrementalChurnAnalyzer::new(temp_dir.path().to_path_buf());
let result = analyzer.get_file_last_commit_hash(&test_file).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "");
}
#[tokio::test]
async fn test_is_cache_valid() {
let temp_dir = TempDir::new().unwrap();
let analyzer = IncrementalChurnAnalyzer::new(temp_dir.path().to_path_buf());
let entry = ChurnCacheEntry {
metrics: FileChurnMetrics {
path: temp_dir.path().join("test.rs"),
relative_path: "test.rs".to_string(),
commit_count: 5,
unique_authors: vec!["Test Author".to_string()],
additions: 100,
deletions: 50,
churn_score: 0.5,
last_modified: Utc::now(),
first_seen: Utc::now(),
},
last_modified: Utc::now(),
git_commit_hash: "abc123".to_string(),
};
let result = analyzer.is_cache_valid(&entry, &entry.metrics.path).await;
assert!(result.is_ok());
assert!(!result.unwrap()); }
#[tokio::test]
async fn test_compute_file_churn_parsing() {
let output = r#"abc123|John Doe|2024-01-01T12:00:00Z
10 20 src/main.rs
def456|Jane Smith|2024-01-02T12:00:00Z
5 10 src/main.rs"#;
let lines: Vec<&str> = output.lines().collect();
let mut commits = Vec::new();
let mut authors = std::collections::HashSet::new();
let mut total_additions = 0;
let mut total_deletions = 0;
let mut i = 0;
while i < lines.len() {
if let Some((hash, author, _date)) =
IncrementalChurnAnalyzer::parse_commit_line(lines[i])
{
commits.push(hash);
authors.insert(author);
if i + 1 < lines.len() {
if let Some((additions, deletions, _)) =
IncrementalChurnAnalyzer::parse_numstat_line(lines[i + 1])
{
total_additions += additions;
total_deletions += deletions;
i += 1;
}
}
}
i += 1;
}
assert_eq!(commits.len(), 2);
assert_eq!(authors.len(), 2);
assert_eq!(total_additions, 15);
assert_eq!(total_deletions, 30);
}
}
#[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);
}
}
}