use std::collections::BTreeMap;
use tokmd_context_git::GitScores;
#[test]
fn empty_git_scores_both_maps_empty() {
let scores = GitScores {
hotspots: BTreeMap::new(),
commit_counts: BTreeMap::new(),
};
assert!(scores.hotspots.is_empty());
assert!(scores.commit_counts.is_empty());
}
#[test]
fn git_scores_with_single_entry() {
let mut hotspots = BTreeMap::new();
hotspots.insert("src/main.rs".to_string(), 42);
let mut commit_counts = BTreeMap::new();
commit_counts.insert("src/main.rs".to_string(), 7);
let scores = GitScores {
hotspots,
commit_counts,
};
assert_eq!(scores.hotspots.len(), 1);
assert_eq!(scores.commit_counts.len(), 1);
assert_eq!(scores.hotspots["src/main.rs"], 42);
assert_eq!(scores.commit_counts["src/main.rs"], 7);
}
#[test]
fn git_scores_multiple_entries() {
let mut hotspots = BTreeMap::new();
hotspots.insert("a.rs".to_string(), 10);
hotspots.insert("b.rs".to_string(), 20);
hotspots.insert("c.rs".to_string(), 30);
let scores = GitScores {
hotspots,
commit_counts: BTreeMap::new(),
};
assert_eq!(scores.hotspots.len(), 3);
}
#[test]
fn hotspots_keys_sorted_alphabetically() {
let mut hotspots = BTreeMap::new();
hotspots.insert("z/file.rs".to_string(), 1);
hotspots.insert("a/file.rs".to_string(), 2);
hotspots.insert("m/file.rs".to_string(), 3);
let scores = GitScores {
hotspots,
commit_counts: BTreeMap::new(),
};
let keys: Vec<&String> = scores.hotspots.keys().collect();
assert_eq!(keys, vec!["a/file.rs", "m/file.rs", "z/file.rs"]);
}
#[test]
fn commit_counts_keys_sorted() {
let mut commit_counts = BTreeMap::new();
commit_counts.insert("util.rs".to_string(), 1);
commit_counts.insert("api.rs".to_string(), 5);
commit_counts.insert("main.rs".to_string(), 3);
let scores = GitScores {
hotspots: BTreeMap::new(),
commit_counts,
};
let keys: Vec<&String> = scores.commit_counts.keys().collect();
assert_eq!(keys, vec!["api.rs", "main.rs", "util.rs"]);
}
fn simulate_hotspot(lines: usize, commits: usize) -> usize {
lines * commits
}
#[test]
fn hotspot_formula_lines_times_commits() {
assert_eq!(simulate_hotspot(100, 5), 500);
assert_eq!(simulate_hotspot(0, 10), 0);
assert_eq!(simulate_hotspot(50, 0), 0);
}
#[test]
fn hotspot_ordering_highest_first() {
let scores = vec![
("lib.rs", simulate_hotspot(200, 3)), ("main.rs", simulate_hotspot(100, 10)), ("util.rs", simulate_hotspot(50, 2)), ];
let mut sorted = scores.clone();
sorted.sort_by(|a, b| b.1.cmp(&a.1));
assert_eq!(sorted[0].0, "main.rs");
assert_eq!(sorted[1].0, "lib.rs");
assert_eq!(sorted[2].0, "util.rs");
}
#[test]
fn hotspot_keys_subset_of_commit_counts() {
let mut commit_counts = BTreeMap::new();
commit_counts.insert("a.rs".to_string(), 3);
commit_counts.insert("b.rs".to_string(), 2);
commit_counts.insert("c.rs".to_string(), 1);
let file_lines: BTreeMap<String, usize> = [("a.rs".to_string(), 100), ("b.rs".to_string(), 50)]
.into_iter()
.collect();
let hotspots: BTreeMap<String, usize> = commit_counts
.iter()
.filter_map(|(path, commits)| {
let lines = file_lines.get(path)?;
Some((path.clone(), lines * commits))
})
.collect();
assert!(hotspots.contains_key("a.rs"));
assert!(hotspots.contains_key("b.rs"));
assert!(!hotspots.contains_key("c.rs"));
for key in hotspots.keys() {
assert!(commit_counts.contains_key(key));
}
}
#[test]
fn empty_file_lines_means_empty_hotspots() {
let commit_counts: BTreeMap<String, usize> = [("x.rs".to_string(), 5)].into_iter().collect();
let file_lines: BTreeMap<String, usize> = BTreeMap::new();
let hotspots: BTreeMap<String, usize> = commit_counts
.iter()
.filter_map(|(path, commits)| {
let lines = file_lines.get(path)?;
Some((path.clone(), lines * commits))
})
.collect();
assert!(hotspots.is_empty());
}
#[test]
fn zero_commits_produces_zero_hotspot() {
assert_eq!(simulate_hotspot(1000, 0), 0);
}
#[test]
fn zero_lines_produces_zero_hotspot() {
assert_eq!(simulate_hotspot(0, 100), 0);
}
#[test]
fn large_values_no_overflow_panic() {
let _ = simulate_hotspot(100_000, 10_000);
}
#[cfg(feature = "git")]
mod git_integration {
use std::process::Command;
use tokmd_context_git::compute_git_scores;
use tokmd_types::{FileKind, FileRow};
fn make_row(path: &str, lines: usize) -> FileRow {
FileRow {
path: path.to_string(),
module: "(root)".to_string(),
lang: "Rust".to_string(),
kind: FileKind::Parent,
code: lines,
comments: 0,
blanks: 0,
lines,
bytes: lines * 10,
tokens: lines * 5,
}
}
fn create_test_repo() -> Option<tempfile::TempDir> {
let dir = tempfile::tempdir().ok()?;
let root = dir.path();
Command::new("git")
.args(["init"])
.current_dir(root)
.output()
.ok()?;
Command::new("git")
.args(["config", "user.email", "test@w67.com"])
.current_dir(root)
.output()
.ok()?;
Command::new("git")
.args(["config", "user.name", "W67Test"])
.current_dir(root)
.output()
.ok()?;
std::fs::write(root.join("main.rs"), "fn main() {}\n").ok()?;
Command::new("git")
.args(["add", "."])
.current_dir(root)
.output()
.ok()?;
Command::new("git")
.args(["commit", "-m", "init"])
.current_dir(root)
.output()
.ok()?;
std::fs::write(
root.join("main.rs"),
"fn main() {\n println!(\"hi\");\n}\n",
)
.ok()?;
Command::new("git")
.args(["add", "."])
.current_dir(root)
.output()
.ok()?;
Command::new("git")
.args(["commit", "-m", "update"])
.current_dir(root)
.output()
.ok()?;
std::fs::write(root.join("lib.rs"), "pub fn hello() {}\n").ok()?;
Command::new("git")
.args(["add", "."])
.current_dir(root)
.output()
.ok()?;
Command::new("git")
.args(["commit", "-m", "add lib"])
.current_dir(root)
.output()
.ok()?;
Some(dir)
}
#[test]
fn non_repo_returns_none() {
let dir = tempfile::tempdir().unwrap();
let rows = vec![make_row("main.rs", 10)];
assert!(compute_git_scores(dir.path(), &rows, 100, 100).is_none());
}
#[test]
fn valid_repo_returns_some() {
let repo = match create_test_repo() {
Some(r) => r,
None => return,
};
let rows = vec![make_row("main.rs", 3)];
assert!(compute_git_scores(repo.path(), &rows, 100, 100).is_some());
}
#[test]
fn commit_counts_match_expected() {
let repo = match create_test_repo() {
Some(r) => r,
None => return,
};
let rows = vec![make_row("main.rs", 3), make_row("lib.rs", 1)];
let Some(scores) = compute_git_scores(repo.path(), &rows, 100, 100) else {
return;
};
assert_eq!(scores.commit_counts.get("main.rs"), Some(&2));
assert_eq!(scores.commit_counts.get("lib.rs"), Some(&1));
}
#[test]
fn hotspot_equals_lines_times_commits() {
let repo = match create_test_repo() {
Some(r) => r,
None => return,
};
let rows = vec![make_row("main.rs", 3), make_row("lib.rs", 1)];
let Some(scores) = compute_git_scores(repo.path(), &rows, 100, 100) else {
return;
};
assert_eq!(scores.hotspots.get("main.rs"), Some(&6));
assert_eq!(scores.hotspots.get("lib.rs"), Some(&1));
}
#[test]
fn child_rows_are_filtered_out() {
let repo = match create_test_repo() {
Some(r) => r,
None => return,
};
let rows = vec![FileRow {
path: "main.rs".to_string(),
module: "(root)".to_string(),
lang: "Rust".to_string(),
kind: FileKind::Child,
code: 3,
comments: 0,
blanks: 0,
lines: 3,
bytes: 30,
tokens: 15,
}];
let Some(scores) = compute_git_scores(repo.path(), &rows, 100, 100) else {
return;
};
assert!(scores.commit_counts.is_empty());
assert!(scores.hotspots.is_empty());
}
#[test]
fn empty_rows_returns_empty_scores() {
let repo = match create_test_repo() {
Some(r) => r,
None => return,
};
let Some(scores) = compute_git_scores(repo.path(), &[], 100, 100) else {
return;
};
assert!(scores.commit_counts.is_empty());
assert!(scores.hotspots.is_empty());
}
#[test]
fn untracked_file_not_in_scores() {
let repo = match create_test_repo() {
Some(r) => r,
None => return,
};
let rows = vec![make_row("nonexistent.rs", 10)];
let Some(scores) = compute_git_scores(repo.path(), &rows, 100, 100) else {
return;
};
assert!(!scores.commit_counts.contains_key("nonexistent.rs"));
}
}