use super::{STAGE_WAL_RECOVERY, StageReporter};
use crate::control::claim_at_birth;
use crate::control::pid_file::read;
use crate::control::test_records::{TestResult, intended, own_birth_record};
#[test]
fn every_stage_write_advances_the_sequence() -> TestResult {
let home = tempfile::tempdir()?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
let reporter = guard.stage_reporter();
reporter.report(STAGE_WAL_RECOVERY, "materializing shard 1 of 3".to_owned());
let first = read(home.path())?.ok_or("the stage write must land")?;
assert_eq!(first.stage.as_deref(), Some(STAGE_WAL_RECOVERY));
assert_eq!(
first.stage_detail.as_deref(),
Some("materializing shard 1 of 3")
);
assert_eq!(first.stage_seq, 1);
assert!(
first.stage_updated_at_unix_secs > 0,
"a stage write must stamp when it happened"
);
reporter.report(STAGE_WAL_RECOVERY, "materializing shard 2 of 3".to_owned());
let second = read(home.path())?.ok_or("the second stage write must land")?;
assert_eq!(
second.stage.as_deref(),
first.stage.as_deref(),
"the specimen exercises an UNCHANGED token on purpose"
);
assert_eq!(
second.stage_seq, 2,
"the sequence must advance even when the token does not — that is the \
whole discriminator between progressing and stuck"
);
Ok(())
}
#[test]
fn the_stage_line_joins_the_token_and_the_detail() -> TestResult {
let home = tempfile::tempdir()?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
guard.stage_reporter().report(
STAGE_WAL_RECOVERY,
"materializing shard 17 of 64".to_owned(),
);
let record = read(home.path())?.ok_or("the stage write must land")?;
assert_eq!(
record.stage_line().as_deref(),
Some("wal-recovery — materializing shard 17 of 64")
);
assert!(
record.stage_age_secs().is_some(),
"a written stage has a measurable age"
);
Ok(())
}
#[test]
fn a_cloned_reporter_writes_the_same_record_from_another_thread() -> TestResult {
let home = tempfile::tempdir()?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
let reporter = guard.stage_reporter();
let worker = std::thread::spawn(move || {
reporter.report(STAGE_WAL_RECOVERY, "materializing shard 9 of 9".to_owned());
});
worker.join().map_err(|_panicked| "stage thread panicked")?;
let on_disk = read(home.path())?.ok_or("the cross-thread stage write must land")?;
assert_eq!(
on_disk.stage_detail.as_deref(),
Some("materializing shard 9 of 9")
);
assert_eq!(
guard.record()?,
on_disk,
"the guard's own copy must follow a stage written from another thread, \
so its exit compare and every reader see one truth"
);
Ok(())
}
#[test]
fn a_detached_reporter_is_a_silent_no_op() -> TestResult {
let home = tempfile::tempdir()?;
StageReporter::detached().report(STAGE_WAL_RECOVERY, "nowhere to go".to_owned());
assert_eq!(
read(home.path())?,
None,
"a detached reporter must not mint a record"
);
Ok(())
}