use crate::types::{classify_file, FileCategory, FileCoupling};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoverageDirection {
TestsForSource,
SourcesForTest,
}
impl CoverageDirection {
pub fn link_kind(self) -> &'static str {
match self {
CoverageDirection::TestsForSource => "test",
CoverageDirection::SourcesForTest => "source",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CoverageLink {
pub path: String,
pub score: f32,
pub co_changes: u32,
}
pub fn derive_coverage(
target: &str,
couplings: Vec<FileCoupling>,
) -> (CoverageDirection, Vec<CoverageLink>) {
let target_is_test = matches!(classify_file(target), FileCategory::Test);
let direction = if target_is_test {
CoverageDirection::SourcesForTest
} else {
CoverageDirection::TestsForSource
};
let want_test = !target_is_test;
let links = couplings
.into_iter()
.filter_map(|c| {
let other = if c.file_a == target {
c.file_b
} else {
c.file_a
};
let other_is_test = matches!(classify_file(&other), FileCategory::Test);
if other_is_test == want_test {
Some(CoverageLink {
path: other,
score: c.score,
co_changes: c.co_changes,
})
} else {
None
}
})
.collect();
(direction, links)
}
#[cfg(test)]
mod tests {
use super::*;
fn coupling(a: &str, b: &str, score: f32, n: u32) -> FileCoupling {
FileCoupling {
file_a: a.to_string(),
file_b: b.to_string(),
score,
co_changes: n,
last_co_change: 0,
}
}
#[test]
fn source_target_returns_only_test_links() {
let target = "src/auth.rs";
let couplings = vec![
coupling("src/auth.rs", "tests/test_auth.rs", 0.9, 12),
coupling("src/auth.rs", "src/session.rs", 0.5, 6), coupling("src/db.rs", "src/auth.rs", 0.4, 4), ];
let (dir, links) = derive_coverage(target, couplings);
assert_eq!(dir, CoverageDirection::TestsForSource);
assert_eq!(links.len(), 1);
assert_eq!(links[0].path, "tests/test_auth.rs");
assert_eq!(links[0].co_changes, 12);
}
#[test]
fn test_target_returns_only_source_links() {
let target = "tests/test_auth.rs";
let couplings = vec![
coupling("tests/test_auth.rs", "src/auth.rs", 0.9, 12),
coupling("tests/test_auth.rs", "tests/helpers.rs", 0.7, 8), ];
let (dir, links) = derive_coverage(target, couplings);
assert_eq!(dir, CoverageDirection::SourcesForTest);
assert_eq!(links.len(), 1);
assert_eq!(links[0].path, "src/auth.rs");
}
#[test]
fn handles_target_on_either_side_of_pair() {
let target = "src/parser.rs";
let couplings = vec![
coupling("src/parser.rs", "parser_test.go", 0.8, 5), coupling("parser_spec.rb", "src/parser.rs", 0.6, 3), ];
let (_dir, links) = derive_coverage(target, couplings);
let paths: Vec<&str> = links.iter().map(|l| l.path.as_str()).collect();
assert_eq!(paths, vec!["parser_test.go", "parser_spec.rb"]);
}
#[test]
fn empty_couplings_yield_no_links() {
let (dir, links) = derive_coverage("src/main.rs", vec![]);
assert_eq!(dir, CoverageDirection::TestsForSource);
assert!(links.is_empty());
}
}