use codelore_lib::Options;
use codelore_lib::facts::FactsDb;
use codelore_lib::repo::GixRepo;
#[test]
fn kamei_features_populated_for_tiny_repo() {
let tiny = codelore_lib::test_support::tiny_repo::build();
let repo = GixRepo::open(tiny.dir.path()).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: tiny.dir.path().to_path_buf(),
min_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let min_nf: String = db
.query_one_value("SELECT CAST(MIN(nf) AS TEXT) FROM commits WHERE nf IS NOT NULL")
.expect("nf query");
assert!(min_nf.parse::<u32>().unwrap() >= 1);
let min_ns_val: String = db
.query_one_value("SELECT CAST(MIN(ns) AS TEXT) FROM commits WHERE ns IS NOT NULL")
.expect("ns query");
assert!(
min_ns_val.parse::<u32>().unwrap() >= 1,
"expected NS >= 1 (files in src/)"
);
let la_non_null: String = db
.query_one_value("SELECT CAST(COUNT(*) AS TEXT) FROM commits WHERE la IS NOT NULL")
.expect("la non-null query");
assert_eq!(
la_non_null.parse::<u32>().unwrap(),
5,
"expected all 5 commits to have la populated (even if 0)"
);
let max_exp: String = db
.query_one_value("SELECT CAST(MAX(exp) AS TEXT) FROM commits")
.expect("exp query");
let max_exp_val: u32 = max_exp.parse().unwrap();
assert!(
max_exp_val >= 4,
"expected max EXP >= 4 for 5-commit single-author repo, got {max_exp_val}"
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn windowed_history_matches_legacy_semantics_on_hot_path() {
use std::process::Command;
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path();
let git = |args: &[&str]| {
let ok = Command::new("git")
.arg("-C")
.arg(path)
.args(args)
.status()
.expect("spawn git")
.success();
assert!(ok, "git {args:?} failed");
};
let git_commit = |msg: &str, date: &str| {
let ok = Command::new("git")
.arg("-C")
.arg(path)
.args(["commit", "-m", msg, "--quiet"])
.env("GIT_AUTHOR_DATE", date)
.env("GIT_COMMITTER_DATE", date)
.status()
.expect("spawn git commit")
.success();
assert!(ok, "git commit {msg:?} failed");
};
git(&["init", "-b", "main", "--quiet"]);
git(&["config", "user.email", "alice@example.com"]);
git(&["config", "user.name", "Alice"]);
std::fs::write(path.join("hot.rs"), "v1\n").unwrap();
git(&["add", "."]);
git_commit("c1: alice creates hot.rs", "2026-01-01T10:00:00");
std::fs::write(path.join("hot.rs"), "v2\n").unwrap();
git(&["add", "."]);
git_commit("c2: alice modifies hot.rs", "2026-01-02T10:00:00");
git(&["config", "user.email", "bob@example.com"]);
git(&["config", "user.name", "Bob"]);
std::fs::write(path.join("hot.rs"), "v3\n").unwrap();
git(&["add", "."]);
git_commit("c3: bob modifies hot.rs", "2026-01-03T10:00:00");
git(&["config", "user.email", "alice@example.com"]);
git(&["config", "user.name", "Alice"]);
std::fs::write(path.join("cool.rs"), "vA\n").unwrap();
git(&["add", "."]);
git_commit("c4: alice creates cool.rs", "2026-01-04T10:00:00");
git(&["config", "user.email", "bob@example.com"]);
git(&["config", "user.name", "Bob"]);
std::fs::write(path.join("hot.rs"), "v4\n").unwrap();
std::fs::write(path.join("cool.rs"), "vB\n").unwrap();
git(&["add", "."]);
git_commit("c5: bob modifies both", "2026-01-05T10:00:00");
let repo = GixRepo::open(path).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: path.to_path_buf(),
min_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let c5_ndev: String = db
.query_one_value(
"SELECT CAST(ndev AS TEXT) FROM commits \
WHERE message LIKE 'c5%'",
)
.expect("c5 ndev");
let c5_nuc: String = db
.query_one_value(
"SELECT CAST(nuc AS TEXT) FROM commits \
WHERE message LIKE 'c5%'",
)
.expect("c5 nuc");
assert_eq!(
c5_ndev.parse::<u32>().unwrap(),
2,
"c5 prior history spans both hot.rs (alice, bob) and cool.rs \
(alice); ndev should be 2 (distinct authors). Got {c5_ndev}",
);
assert_eq!(
c5_nuc.parse::<u32>().unwrap(),
4,
"c5 prior history union spans 4 distinct revs (c1, c2, c3, c4); \
got {c5_nuc}",
);
let c5_sexp: String = db
.query_one_value(
"SELECT CAST(sexp AS TEXT) FROM commits \
WHERE message LIKE 'c5%'",
)
.expect("c5 sexp");
assert_eq!(
c5_sexp.parse::<u32>().unwrap(),
1,
"c5 by bob: prior bob-commits in the same top-level dir = c3 only \
(c4 was by alice, c5 is current); sexp should be 1. Got {c5_sexp}",
);
let c1_ndev: String = db
.query_one_value(
"SELECT CAST(ndev AS TEXT) FROM commits \
WHERE message LIKE 'c1%'",
)
.expect("c1 ndev");
assert_eq!(
c1_ndev.parse::<u32>().unwrap(),
0,
"c1 is the first commit; ndev should be 0"
);
}
#[test]
fn kamei_fix_flag_detects_bug_keywords() {
let tiny = codelore_lib::test_support::tiny_repo::build();
let repo = GixRepo::open(tiny.dir.path()).expect("open");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: tiny.dir.path().to_path_buf(),
min_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let fix_count: String = db
.query_one_value("SELECT CAST(COUNT(*) AS TEXT) FROM commits WHERE fix = TRUE")
.expect("fix count");
assert!(
fix_count.parse::<u32>().unwrap() >= 1,
"tiny repo has 'fix typo' commit; expected >=1 FIX"
);
}
#[test]
fn kamei_entropy_per_commit_distribution() {
use std::fmt::Write as _;
use std::process::Command;
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path();
let mut s = String::new();
let write_lines = |buf: &mut String, prefix: char, n: usize| {
buf.clear();
for i in 0..n {
writeln!(buf, "{prefix}{i}").unwrap();
}
};
let git = |args: &[&str]| {
let ok = Command::new("git")
.arg("-C")
.arg(path)
.args(args)
.status()
.expect("spawn git")
.success();
assert!(ok, "git {args:?} failed");
};
let commit_at = |msg: &str, date: &str| {
let ok = Command::new("git")
.arg("-C")
.arg(path)
.args(["commit", "-m", msg, "--quiet"])
.env("GIT_AUTHOR_DATE", date)
.env("GIT_COMMITTER_DATE", date)
.status()
.expect("spawn git commit")
.success();
assert!(ok);
};
git(&["init", "-b", "main", "--quiet"]);
git(&["config", "user.email", "entropy@example.com"]);
git(&["config", "user.name", "Entropy"]);
write_lines(&mut s, 'a', 10);
std::fs::write(path.join("a.txt"), &s).unwrap();
git(&["add", "."]);
commit_at("c1: single file 10 lines", "2026-06-01T10:00:00");
write_lines(&mut s, 'b', 4);
std::fs::write(path.join("b.txt"), &s).unwrap();
write_lines(&mut s, 'c', 4);
std::fs::write(path.join("c.txt"), &s).unwrap();
git(&["add", "."]);
commit_at("c2: two files even", "2026-06-02T10:00:00");
std::fs::write(path.join("d.txt"), "d0\n").unwrap();
std::fs::write(path.join("e.txt"), "e0\ne1\n").unwrap();
write_lines(&mut s, 'f', 5);
std::fs::write(path.join("f.txt"), &s).unwrap();
git(&["add", "."]);
commit_at("c3: three files uneven", "2026-06-03T10:00:00");
let repo = codelore_lib::repo::GixRepo::open(path).expect("gix open");
let db = codelore_lib::facts::FactsDb::new_in_memory().expect("db");
let opts = codelore_lib::Options {
repo_path: path.to_path_buf(),
min_revs: 1,
..codelore_lib::Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let entropies: Vec<f64> = (1..=3)
.map(|i| {
let s: String = db
.query_one_value(&format!(
"SELECT CAST(entropy AS TEXT) FROM commits WHERE message LIKE 'c{i}:%'",
))
.unwrap_or_else(|e| panic!("entropy query c{i}: {e}"));
s.parse::<f64>()
.unwrap_or_else(|e| panic!("entropy parse c{i}={s:?}: {e}"))
})
.collect();
assert!(
(entropies[0] - 0.0).abs() < 1e-9,
"C1 single-file entropy must be 0.0; got {}",
entropies[0]
);
assert!(
(entropies[1] - 1.0).abs() < 1e-9,
"C2 even 2-file entropy must be 1.0; got {}",
entropies[1]
);
let expected_c3 = 1.298_794_940_695_398_4_f64;
assert!(
(entropies[2] - expected_c3).abs() < 1e-9,
"C3 uneven 3-file entropy must be {expected_c3}; got {}",
entropies[2]
);
}
#[test]
fn dir_skew_does_not_oom_or_timeout() {
use std::process::Command;
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path();
let git = |args: &[&str]| {
let ok = Command::new("git")
.arg("-C")
.arg(path)
.args(args)
.status()
.expect("spawn git")
.success();
assert!(ok, "git {args:?} failed");
};
let commit_at = |msg: &str, date: &str| {
let ok = Command::new("git")
.arg("-C")
.arg(path)
.args(["commit", "-m", msg, "--quiet"])
.env("GIT_AUTHOR_DATE", date)
.env("GIT_COMMITTER_DATE", date)
.status()
.expect("spawn git commit")
.success();
assert!(ok);
};
git(&["init", "-b", "main", "--quiet"]);
git(&["config", "user.email", "skew@example.com"]);
git(&["config", "user.name", "Skew"]);
std::fs::create_dir_all(path.join("src")).unwrap();
for i in 0..60 {
std::fs::write(path.join(format!("src/f{i}.rs")), "v\n").unwrap();
git(&["add", "."]);
commit_at(
&format!("commit {i}"),
&format!("2026-01-{:02}T10:00:00", (i % 28) + 1),
);
}
let started = std::time::Instant::now();
let repo = codelore_lib::repo::GixRepo::open(path).expect("gix open");
let db = codelore_lib::facts::FactsDb::new_in_memory().expect("db");
let opts = codelore_lib::Options {
repo_path: path.to_path_buf(),
min_revs: 1,
..codelore_lib::Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let elapsed = started.elapsed();
assert!(
elapsed.as_secs() < 30,
"kamei ingest took {elapsed:?} on a 60-commit fixture — likely a partition-materialization regression"
);
let last_sexp: String = db
.query_one_value("SELECT CAST(sexp AS TEXT) FROM commits ORDER BY date DESC LIMIT 1")
.expect("last sexp");
assert!(
last_sexp.parse::<u32>().unwrap() >= 50,
"60th commit in skewed (src, Skew) partition should see ~59 prior revs; got {last_sexp}"
);
}