use super::{IncarnationState, pid_file_path, read, remove_if_matches};
use crate::control::test_records::{TestResult, dead_record, own_serving_record, plant};
#[test]
fn a_zero_twenty_five_shaped_record_reads_as_serving() -> TestResult {
let home = tempfile::tempdir()?;
let path = pid_file_path(home.path());
std::fs::create_dir_all(path.parent().ok_or("pid path must have a parent")?)?;
let old_shape = serde_json::json!({
"pid": 93400,
"started_at_unix_secs": 1_000_000,
"binary_sha256": "0".repeat(64),
"version": "0.25.1",
"commit": "older-build",
"http_address": "127.0.0.1:8080",
"grpc_address": "127.0.0.1:50051",
"drain_timeout_seconds": 45,
});
std::fs::write(&path, format!("{old_shape}\n"))?;
let record = read(home.path())?.ok_or("the 0.25.1-shaped record must read")?;
assert_eq!(
record.state,
IncarnationState::Serving,
"a record from a build that claimed only at bind was, by construction, \
serving when it was written"
);
assert_eq!(
record.http_address,
Some("127.0.0.1:8080".parse()?),
"the recorded addresses must survive the Option widening"
);
assert_eq!(record.grpc_address, Some("127.0.0.1:50051".parse()?));
assert_eq!(record.drain_timeout_seconds, 45);
assert_eq!(record.stage, None, "an older record reports no stage");
assert_eq!(record.stage_seq, 0);
assert_eq!(record.stage_updated_at_unix_secs, 0);
Ok(())
}
#[test]
fn a_record_predating_the_drain_window_field_still_reads() -> TestResult {
let home = tempfile::tempdir()?;
let path = pid_file_path(home.path());
std::fs::create_dir_all(path.parent().ok_or("pid path must have a parent")?)?;
let old_shape = serde_json::json!({
"pid": 93400,
"started_at_unix_secs": 1_000_000,
"binary_sha256": "0".repeat(64),
"version": "0.22.0",
"commit": "older-build",
"http_address": "127.0.0.1:8080",
"grpc_address": "127.0.0.1:50051",
});
std::fs::write(&path, format!("{old_shape}\n"))?;
let record = read(home.path())?.ok_or("the old-shape record must read")?;
assert_eq!(record.drain_timeout_seconds, 0);
Ok(())
}
#[test]
fn read_distinguishes_absent_from_unreadable() -> TestResult {
let home = tempfile::tempdir()?;
assert_eq!(read(home.path())?, None, "no file is a state, not an error");
let path = pid_file_path(home.path());
std::fs::create_dir_all(path.parent().ok_or("pid path must have a parent")?)?;
std::fs::write(&path, "not json")?;
let error = match read(home.path()) {
Err(error) => error,
Ok(record) => return Err(format!("corrupt file must refuse, got {record:?}").into()),
};
assert!(
error.to_string().contains("does not parse"),
"the refusal must say why: {error}"
);
Ok(())
}
#[test]
fn remove_if_matches_compares_incarnation_identity_not_the_whole_record() -> TestResult {
let home = tempfile::tempdir()?;
let before_signal = own_serving_record(None, None)?;
let mut churned = before_signal.clone();
churned.stage = Some("wal-recovery".to_owned());
churned.stage_detail = Some("materializing shard 17 of 64".to_owned());
churned.stage_seq = 41;
churned.stage_updated_at_unix_secs = 1_700_000_000;
churned.state = IncarnationState::Draining;
plant(home.path(), &churned)?;
assert_ne!(
churned, before_signal,
"the specimen only means something if the two records genuinely differ"
);
assert!(
remove_if_matches(home.path(), &before_signal)?,
"a record that names the SAME incarnation must reconcile, however far its \
stage has moved since the caller read it"
);
assert_eq!(read(home.path())?, None);
Ok(())
}
#[test]
fn remove_if_matches_refuses_a_foreign_incarnation() -> TestResult {
let home = tempfile::tempdir()?;
let planted = own_serving_record(None, None)?;
plant(home.path(), &planted)?;
let stranger = dead_record()?;
assert!(
!remove_if_matches(home.path(), &stranger)?,
"a foreign incarnation must not remove this home's record"
);
assert_eq!(
read(home.path())?.as_ref(),
Some(&planted),
"the planted record must survive untouched"
);
assert!(remove_if_matches(home.path(), &planted)?);
assert!(
!remove_if_matches(home.path(), &planted)?,
"an absent file removes nothing"
);
Ok(())
}