use super::*;
#[test]
fn get_default_branch_returns_none_for_non_git() {
let tmp = std::env::temp_dir();
assert!(get_default_branch(&tmp).is_none());
}
#[test]
fn run_hygiene_skips_missing_dir() {
let tmp = std::env::temp_dir();
let result = run_hygiene_for_base(&tmp);
assert!(
result.is_ok(),
"should skip non-git dir cleanly: {result:?}"
);
}
#[test]
fn run_hygiene_for_all_bases_skips_missing_root() {
let missing = std::path::Path::new("/tmp/trusty-nonexistent-repos-root-hygiene-test");
run_hygiene_for_all_bases(missing); }
#[test]
fn hygiene_opt_out_marker_detected() {
let tmp = tempfile::TempDir::new().expect("temp dir");
std::fs::create_dir(tmp.path().join(".git")).expect("mkdir .git");
std::fs::write(tmp.path().join(HYGIENE_OPT_OUT_MARKER), "").expect("write marker");
assert!(run_hygiene_for_base(tmp.path()).is_ok());
}
#[test]
fn decide_update_ahead_skips() {
match decide_update(Some("main"), "main", Some(1), Some(false)) {
UpdateDecision::Skip(reason) => assert!(reason.contains("ahead")),
UpdateDecision::Update => panic!("an ahead branch must never be updated"),
}
}
#[test]
fn decide_update_dirty_skips() {
match decide_update(Some("main"), "main", Some(0), Some(true)) {
UpdateDecision::Skip(reason) => assert!(reason.contains("uncommitted")),
UpdateDecision::Update => panic!("a dirty tree must never be updated"),
}
}
#[test]
fn decide_update_unknown_ahead_skips() {
match decide_update(Some("main"), "main", None, Some(false)) {
UpdateDecision::Skip(_) => {}
UpdateDecision::Update => panic!("unknown ahead-count must never be updated"),
}
}
#[test]
fn decide_update_unknown_dirty_skips() {
match decide_update(Some("main"), "main", Some(0), None) {
UpdateDecision::Skip(_) => {}
UpdateDecision::Update => panic!("unknown dirty-state must never be updated"),
}
}
#[test]
fn decide_update_detached_head_skips() {
match decide_update(None, "main", Some(0), Some(false)) {
UpdateDecision::Skip(reason) => assert!(reason.contains("detached")),
UpdateDecision::Update => panic!("a detached HEAD must never be updated"),
}
}
#[test]
fn decide_update_non_default_branch_skips() {
match decide_update(Some("feature"), "main", Some(0), Some(false)) {
UpdateDecision::Skip(reason) => {
assert!(
reason.contains("feature"),
"reason names the branch: {reason}"
);
assert!(
reason.contains("main"),
"reason names the default: {reason}"
);
}
UpdateDecision::Update => panic!("a non-default branch must never be moved to origin/main"),
}
}
#[test]
fn decide_update_clean_and_even_updates() {
assert_eq!(
decide_update(Some("main"), "main", Some(0), Some(false)),
UpdateDecision::Update
);
}
fn base_with_fetch_head(dir: &std::path::Path, age: Option<Duration>) -> std::path::PathBuf {
let base = dir.join("owner").join("repo");
std::fs::create_dir_all(base.join(".git")).expect("base clone");
if let Some(age) = age {
set_fetch_head_mtime(&base, std::time::SystemTime::now() - age);
}
base
}
fn set_fetch_head_mtime(base: &std::path::Path, when: std::time::SystemTime) {
let head = base.join(".git").join("FETCH_HEAD");
let file = std::fs::File::options()
.write(true)
.create(true)
.truncate(true)
.open(&head)
.expect("FETCH_HEAD");
file.set_times(std::fs::FileTimes::new().set_modified(when))
.expect("mtime");
}
#[test]
fn fetch_is_due_without_a_fetch_head() {
let tmp = tempfile::tempdir().expect("tempdir");
let base = base_with_fetch_head(tmp.path(), None);
assert!(
fetch_is_due(&base, Duration::from_secs(6 * 60 * 60)),
"a base with no FETCH_HEAD has never been fetched"
);
assert!(fetch_is_due(tmp.path(), Duration::from_secs(6 * 60 * 60)));
}
#[test]
fn fetch_is_due_on_both_sides_of_the_interval() {
let tmp = tempfile::tempdir().expect("tempdir");
let interval = Duration::from_secs(6 * 60 * 60);
let fresh = base_with_fetch_head(&tmp.path().join("fresh"), Some(Duration::from_secs(60)));
assert!(
!fetch_is_due(&fresh, interval),
"a base fetched a minute ago is not due"
);
let stale = base_with_fetch_head(
&tmp.path().join("stale"),
Some(Duration::from_secs(7 * 60 * 60)),
);
assert!(
fetch_is_due(&stale, interval),
"a base fetched seven hours ago is due"
);
}
#[test]
fn fetch_is_due_when_the_mtime_is_in_the_future() {
let tmp = tempfile::tempdir().expect("tempdir");
let base = base_with_fetch_head(tmp.path(), Some(Duration::ZERO));
set_fetch_head_mtime(
&base,
std::time::SystemTime::now() + Duration::from_secs(48 * 60 * 60),
);
assert!(
fetch_is_due(&base, Duration::from_secs(6 * 60 * 60)),
"a FETCH_HEAD dated in the future must fetch, not skip"
);
}
fn base_with_a_blackhole_remote(repos_root: &std::path::Path, name: &str) -> std::path::PathBuf {
let base = repos_root.join("owner").join(name);
std::fs::create_dir_all(&base).expect("base");
for args in [
vec!["init", "-q"],
vec!["remote", "add", "origin", "git://10.255.255.1:9418/x.git"],
] {
let out = trusty_common::git::command_in(&base)
.args(&args)
.output()
.expect("git must be available");
assert!(out.status.success(), "git {args:?}: {out:?}");
}
base
}
#[test]
fn hygiene_skips_a_recently_fetched_base() {
let tmp = tempfile::tempdir().expect("tempdir");
let repos_root = tmp.path().join("repos");
let base = base_with_a_blackhole_remote(&repos_root, "repo");
set_fetch_head_mtime(
&base,
std::time::SystemTime::now() - Duration::from_secs(60),
);
let started = std::time::Instant::now();
run_hygiene_for_base_within(&base, Duration::from_secs(3)).expect("pass completes");
let elapsed = started.elapsed();
assert!(
elapsed < Duration::from_secs(2),
"a base fetched a minute ago must not pay for a fetch at all; took {elapsed:?}"
);
}
#[test]
fn hygiene_pass_budget_stops_the_sweep() {
let tmp = tempfile::tempdir().expect("tempdir");
let repos_root = tmp.path().join("repos");
for name in ["a", "b", "c"] {
base_with_a_blackhole_remote(&repos_root, name);
}
let ceiling = Duration::from_secs(1);
let started = std::time::Instant::now();
let skipped = run_hygiene_for_all_bases_within(&repos_root, Duration::ZERO, ceiling);
let skipped_pass = started.elapsed();
assert_eq!(
skipped, 3,
"every base past the budget must be counted as skipped — this is the number \
the `warn!` line reports"
);
assert!(
skipped_pass < Duration::from_secs(1),
"an expired budget must abandon every base immediately; took {skipped_pass:?}"
);
let started = std::time::Instant::now();
let skipped = run_hygiene_for_all_bases_within(&repos_root, Duration::from_secs(120), ceiling);
let full_pass = started.elapsed();
assert_eq!(skipped, 0, "a live budget skips nothing");
assert!(
full_pass >= ceiling,
"a live budget must actually visit the bases, paying the ceiling on each \
blocked fetch; took {full_pass:?} — if this is fast, the zero-budget result \
above proves nothing"
);
}