use std::time::Duration;
use super::{HYGIENE, RECLAIM, RECLAIM_NAME, SweepStatus, lane, rows};
use crate::core::doctor::CheckStatus;
struct SweepSwitchesOn(Vec<(&'static str, Option<String>)>);
impl SweepSwitchesOn {
fn new() -> Self {
let keys = [
super::super::merged_pr_reclaim::ENV_ENABLED,
crate::daemon::managed_routes::inproject_hygiene_sweep::ENV_ENABLED,
];
let saved = keys
.into_iter()
.map(|key| {
let prev = std::env::var(key).ok();
unsafe { std::env::remove_var(key) };
(key, prev)
})
.collect();
Self(saved)
}
}
impl Drop for SweepSwitchesOn {
fn drop(&mut self) {
for (key, prev) in self.0.drain(..) {
unsafe {
match prev {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
}
}
}
}
#[test]
fn sweep_status_records_the_last_pass_duration() {
static S: SweepStatus = SweepStatus::new("local-test-sweep");
assert_eq!(S.last_pass(), None, "no pass has run");
assert!(!S.is_running());
{
let _guard = S.begin(None);
assert!(S.is_running(), "a pass in flight must be visible");
}
assert!(!S.is_running(), "the guard must clear the flag");
assert!(S.last_pass().is_some(), "the guard must record a duration");
}
#[test]
fn sweep_status_guard_records_even_on_panic() {
static S: SweepStatus = SweepStatus::new("panicking-test-sweep");
let caught = std::panic::catch_unwind(|| {
let _guard = S.begin(None);
panic!("a pass that blew up");
});
assert!(caught.is_err(), "the panic must have happened");
assert!(!S.is_running(), "a panicked pass must not look in-flight");
assert!(S.last_pass().is_some());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_lane_serialises_two_sweeps() {
let first = lane().acquire().await.expect("lane is open");
let blocked = tokio::time::timeout(Duration::from_millis(200), lane().acquire()).await;
assert!(
blocked.is_err(),
"a second sweep must WAIT while the first holds the lane"
);
drop(first);
let second = tokio::time::timeout(Duration::from_millis(500), lane().acquire()).await;
assert!(
second.is_ok(),
"the lane must be released when the first pass ends"
);
}
#[test]
fn sweep_rows_report_each_sweeps_switch_and_last_pass() {
let root = tempfile::TempDir::new().expect("tempdir");
let rows = rows(root.path());
assert_eq!(rows.len(), 2, "one row per sweep");
let names: Vec<&str> = rows.iter().map(|r| r.name).collect();
assert!(names.contains(&"worktree-reclaim"), "{names:?}");
assert!(names.contains(&"inproject-hygiene"), "{names:?}");
for r in &rows {
assert!(
r.env.starts_with("TRUSTY_MPM_"),
"every sweep must name the variable that switches it off: {}",
r.env
);
}
}
#[test]
#[serial_test::serial]
fn background_sweeps_row_warns_on_a_slow_pass() {
let _on = SweepSwitchesOn::new();
let root = tempfile::TempDir::new().expect("tempdir");
let check = {
let _reclaim = RECLAIM.begin(None);
super::check_background_sweeps(root.path())
};
assert_eq!(check.status, CheckStatus::Warn, "{}", check.message);
assert!(check.message.contains("IN FLIGHT"), "{}", check.message);
assert_eq!(check.name, "background_sweeps");
let _ = &HYGIENE;
}
#[test]
#[serial_test::serial]
fn background_sweeps_row_is_ok_when_both_sweeps_are_idle() {
let _on = SweepSwitchesOn::new();
assert!(
!RECLAIM.is_running() && !HYGIENE.is_running(),
"the serial guard must give this test an idle daemon"
);
let root = tempfile::TempDir::new().expect("tempdir");
let check = super::check_background_sweeps(root.path());
assert_eq!(check.name, "background_sweeps");
assert_eq!(check.status, CheckStatus::Ok, "{}", check.message);
assert!(
!check.message.contains("IN FLIGHT"),
"an idle daemon must not report a pass in flight: {}",
check.message
);
assert!(
check.message.contains("worktree-reclaim") && check.message.contains("inproject-hygiene"),
"both sweeps must appear even when idle: {}",
check.message
);
}
#[test]
#[serial_test::serial]
fn a_running_sweep_is_reported_by_a_process_that_did_not_run_it() {
let _on = SweepSwitchesOn::new();
let root = tempfile::TempDir::new().expect("tempdir");
let marker = super::super::sweep_state_file::marker_path(root.path(), RECLAIM_NAME);
super::super::sweep_state_file::record_start(&marker);
assert!(
!RECLAIM.is_running(),
"the reader's own atomics stay untouched — that is the whole bug"
);
let reclaim = rows(root.path())
.into_iter()
.find(|r| r.name == RECLAIM_NAME)
.expect("the reclaim row exists");
assert!(
reclaim.running,
"a started, unfinished sweep must read as running in another process"
);
let check = super::check_background_sweeps(root.path());
assert_eq!(check.status, CheckStatus::Warn, "{}", check.message);
assert!(
check
.message
.contains("worktree-reclaim: ON, pass IN FLIGHT"),
"the row must name the sweep that is running: {}",
check.message
);
}
#[test]
#[serial_test::serial]
fn a_row_with_no_marker_still_reports_this_processs_own_pass() {
let _on = SweepSwitchesOn::new();
let root = tempfile::TempDir::new().expect("tempdir");
let check = {
let _hygiene = HYGIENE.begin(None);
super::check_background_sweeps(root.path())
};
assert_eq!(check.status, CheckStatus::Warn, "{}", check.message);
assert!(
check
.message
.contains("inproject-hygiene: ON, pass IN FLIGHT"),
"the in-memory pass must still be reported: {}",
check.message
);
}