use codelore_lib::analyses::communities::run_communities;
use codelore_lib::analyses::coupling::run_coupling;
use codelore_lib::facts::FactsDb;
use codelore_lib::repo::GixRepo;
use codelore_lib::test_support::permissive_coupling_opts;
use std::collections::{HashMap, HashSet};
#[test]
fn communities_partition_is_a_valid_dense_cover() {
let fixture = codelore_lib::test_support::differential_repo::build();
let repo = GixRepo::open(fixture.dir.path()).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = permissive_coupling_opts(fixture.dir.path().to_path_buf());
db.ingest(&repo, &opts).expect("ingest");
let pairs = run_coupling(&db, &opts).expect("coupling oracle");
assert!(
!pairs.is_empty(),
"differential fixture should yield ≥1 coupling pair under permissive options"
);
let result = run_communities(&db, &opts).expect("run communities");
assert!(
!result.rows.is_empty(),
"a non-empty coupling graph must produce ≥1 community row"
);
let oracle_paths: HashSet<&str> = pairs
.iter()
.flat_map(|p| [p.entity_a.as_str(), p.entity_b.as_str()])
.collect();
let row_paths: HashSet<&str> = result.rows.iter().map(|r| r.path.as_str()).collect();
assert_eq!(
oracle_paths, row_paths,
"community node set must equal the set of files in coupling pairs"
);
assert_eq!(
row_paths.len(),
result.rows.len(),
"each file must appear in exactly one community row (no duplicate paths)"
);
let distinct_ids: HashSet<u32> = result.rows.iter().map(|r| r.community_id).collect();
assert_eq!(
u32::try_from(distinct_ids.len()).expect("id count fits u32"),
result.community_count,
"community_count must equal the number of distinct community IDs"
);
let mut sorted_ids: Vec<u32> = distinct_ids.into_iter().collect();
sorted_ids.sort_unstable();
let expected_dense: Vec<u32> = (0..result.community_count).collect();
assert_eq!(
sorted_ids, expected_dense,
"community IDs must form a dense 0..k range after renumbering"
);
let mut size_of: HashMap<u32, u32> = HashMap::new();
for row in &result.rows {
*size_of.entry(row.community_id).or_default() += 1;
}
for row in &result.rows {
assert_eq!(
row.community_size, size_of[&row.community_id],
"community_size mismatch for {} (community {})",
row.path, row.community_id
);
}
assert!(
(-1.0..=1.0).contains(&result.modularity) && result.modularity.is_finite(),
"modularity out of range: {}",
result.modularity
);
for w in result.rows.windows(2) {
let ordered = w[0].community_id < w[1].community_id
|| (w[0].community_id == w[1].community_id && w[0].path <= w[1].path);
assert!(
ordered,
"rows must be sorted by (community_id, path): ({}, {}) before ({}, {})",
w[0].community_id, w[0].path, w[1].community_id, w[1].path
);
}
}
#[test]
fn communities_are_deterministic_across_runs() {
let fixture = codelore_lib::test_support::differential_repo::build();
let repo = GixRepo::open(fixture.dir.path()).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = permissive_coupling_opts(fixture.dir.path().to_path_buf());
db.ingest(&repo, &opts).expect("ingest");
let first = run_communities(&db, &opts).expect("first run");
let second = run_communities(&db, &opts).expect("second run");
assert_eq!(
first.community_count, second.community_count,
"community_count drifted across runs"
);
assert!((first.modularity - second.modularity).abs() < 1e-12);
let map = |r: &codelore_lib::analyses::communities::CommunitiesResult| {
r.rows
.iter()
.map(|row| (row.path.clone(), row.community_id))
.collect::<HashMap<_, _>>()
};
assert_eq!(
map(&first),
map(&second),
"seeded Leiden must produce identical community assignments across runs"
);
}