use super::RecordUpdate;
use crate::control::claim_at_birth;
use crate::control::pid_file::{IncarnationState, read};
use crate::control::test_records::{
GRPC, HTTP, TestResult, intended, own_birth_record, own_serving_record, plant,
};
#[test]
fn the_bind_fill_records_the_addresses_and_moves_to_serving() -> TestResult {
let home = tempfile::tempdir()?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
guard
.stage_reporter()
.report("store-open", "opening the store".to_owned());
let update = guard.update_own(|record| {
record.state = IncarnationState::Serving;
record.http_address = HTTP.parse().ok();
record.grpc_address = GRPC.parse().ok();
record.drain_timeout_seconds = 30;
record.stage = None;
record.stage_detail = None;
})?;
assert_eq!(update, RecordUpdate::Written);
let written = read(home.path())?.ok_or("the filled record must be readable")?;
assert_eq!(written.state, IncarnationState::Serving);
assert_eq!(written.http_address, Some(HTTP.parse()?));
assert_eq!(written.grpc_address, Some(GRPC.parse()?));
assert_eq!(written.drain_timeout_seconds, 30);
assert_eq!(written.stage, None, "a serving server is not mid-boot");
assert_eq!(guard.record()?, written);
Ok(())
}
#[test]
fn an_update_never_overwrites_a_successors_record() -> TestResult {
let home = tempfile::tempdir()?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
let mut successor = own_serving_record(None, None)?;
successor.started_at_unix_secs = successor.started_at_unix_secs.wrapping_add(97);
successor.commit = "successor".to_owned();
plant(home.path(), &successor)?;
let update = guard.update_own(|record| {
record.state = IncarnationState::Draining;
})?;
assert_eq!(
update,
RecordUpdate::NotOurs,
"a foreign record must be reported, not written over"
);
assert_eq!(
read(home.path())?.as_ref(),
Some(&successor),
"the successor's record must survive untouched"
);
drop(guard);
assert_eq!(read(home.path())?.as_ref(), Some(&successor));
Ok(())
}
#[test]
fn the_guard_removes_its_own_record_despite_stage_churn() -> TestResult {
let home = tempfile::tempdir()?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
let claimed = guard.record()?;
let reporter = guard.stage_reporter();
for shard in 1..=4 {
reporter.report("wal-recovery", format!("materializing shard {shard} of 4"));
}
let churned = read(home.path())?.ok_or("the churned record must be readable")?;
assert_ne!(
churned, claimed,
"the specimen only means something if the record genuinely moved"
);
assert!(churned.is_same_incarnation(&claimed));
drop(guard);
assert_eq!(
read(home.path())?,
None,
"a guard must remove its own record however far its stage has moved"
);
Ok(())
}
#[test]
fn an_unclaimed_guard_writes_and_removes_nothing() -> TestResult {
let home = tempfile::tempdir()?;
let holder = own_serving_record(
Some("127.0.0.1:19090".parse()?),
Some("127.0.0.1:19091".parse()?),
)?;
plant(home.path(), &holder)?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
assert!(!guard.holds_claim());
assert_eq!(
guard.update_own(|record| record.state = IncarnationState::Serving)?,
RecordUpdate::Unclaimed
);
assert_eq!(read(home.path())?.as_ref(), Some(&holder));
drop(guard);
assert_eq!(read(home.path())?.as_ref(), Some(&holder));
Ok(())
}
#[test]
fn an_update_after_the_record_disappears_reports_rather_than_recreating() -> TestResult {
let home = tempfile::tempdir()?;
let guard = claim_at_birth(home.path(), &own_birth_record()?, intended()?)?;
std::fs::remove_file(crate::control::pid_file::pid_file_path(home.path()))?;
assert_eq!(
guard.update_own(|record| record.state = IncarnationState::Serving)?,
RecordUpdate::NotOurs
);
assert_eq!(
read(home.path())?,
None,
"the disappearance must stay visible"
);
Ok(())
}