use super::*;
use crate::test_support::{fixture, remove};
use crate::transaction_owner::{current_transaction_owner, encode_transaction_owner};
use crate::transaction_registry::{OWNER_FILENAME, TRANSACTION_ROOT_NAME, ensure_transaction_root};
const FIRST: &str = "0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10";
const SECOND: &str = "1c7d3d2f-7a6c-4e1f-8b66-4a6e9f3f7b21";
fn transaction(registry: &Path, id: &str, created_at: &str, live: bool) -> PathBuf {
let directory: PathBuf = registry.join(id);
std::fs::create_dir_all(&directory).expect("transaction directory");
let mut owner = current_transaction_owner(id, created_at).expect("current owner");
if !live {
owner.owner_identity = String::from("linux:1");
}
std::fs::write(directory.join(OWNER_FILENAME), encode_transaction_owner(&owner)).expect("owner record");
return directory;
}
fn registry_of(name: &str) -> (PathBuf, PathBuf) {
let root: PathBuf = fixture(name);
let registry: PathBuf = root.join(TRANSACTION_ROOT_NAME);
ensure_transaction_root(registry.as_path()).expect("registry root");
return (root, registry);
}
#[test]
fn request_bytes_match_the_incumbent_object() {
assert_eq!(
reservation_request_bytes(2),
b"{\"schemaVersion\":2,\"state\":\"reservation-request\",\"lostRaces\":2}\n".to_vec()
);
}
#[test]
fn the_reservation_is_earned_at_the_configured_count() {
assert!(!reservation_earned(0, 1));
assert!(reservation_earned(1, 1));
assert!(!reservation_earned(2, 3));
assert!(reservation_earned(3, 3));
assert!(reservation_earned(4, 3));
}
#[test]
fn requesters_are_live_transactions_with_a_request() {
let (root, registry) = registry_of("replay-reservation-requesters");
assert!(live_requesters(registry.as_path()).expect("empty").is_empty());
let live: PathBuf = transaction(registry.as_path(), FIRST, "2026-10-10T00:00:00.000Z", true);
assert!(live_requesters(registry.as_path()).expect("no request").is_empty());
std::fs::write(live.join(RESERVATION_REQUEST_FILENAME), reservation_request_bytes(1)).expect("request");
assert_eq!(live_requesters(registry.as_path()).expect("requester"), vec![String::from(FIRST)]);
let dead: PathBuf = transaction(registry.as_path(), SECOND, "2026-10-09T00:00:00.000Z", false);
std::fs::write(dead.join(RESERVATION_REQUEST_FILENAME), reservation_request_bytes(1)).expect("request");
assert_eq!(live_requesters(registry.as_path()).expect("dead excluded"), vec![String::from(FIRST)]);
remove(root.as_path());
}
#[test]
fn the_oldest_requester_is_granted_first() {
let (root, registry) = registry_of("replay-reservation-order");
for (id, created) in [(FIRST, "2026-10-10T00:00:05.000Z"), (SECOND, "2026-10-10T00:00:01.000Z")] {
let directory: PathBuf = transaction(registry.as_path(), id, created, true);
std::fs::write(directory.join(RESERVATION_REQUEST_FILENAME), reservation_request_bytes(1)).expect("request");
}
assert_eq!(
live_requesters(registry.as_path()).expect("order"),
vec![String::from(SECOND), String::from(FIRST)]
);
remove(root.as_path());
}
#[test]
fn a_held_reservation_is_seen_by_others_until_released() {
let (root, registry) = registry_of("replay-reservation-holder");
let first_directory: PathBuf = transaction(registry.as_path(), FIRST, "2026-10-10T00:00:00.000Z", true);
let second_directory: PathBuf = transaction(registry.as_path(), SECOND, "2026-10-10T00:00:01.000Z", true);
let mut first: LandingReservation = LandingReservation::open(registry.as_path(), first_directory.as_path(), FIRST, false);
let second: LandingReservation = LandingReservation::open(registry.as_path(), second_directory.as_path(), SECOND, false);
assert!(first.reserve(1).expect("granted"));
assert!(first_directory.join(RESERVATION_REQUEST_FILENAME).exists());
assert!(!first.held_by_another().expect("own view"));
assert!(second.held_by_another().expect("other view"));
first.release().expect("released");
assert!(!first_directory.join(RESERVATION_REQUEST_FILENAME).exists());
assert!(!second.held_by_another().expect("free"));
remove(root.as_path());
}
#[test]
fn a_nested_landing_neither_requests_nor_waits() {
let (root, registry) = registry_of("replay-reservation-nested");
let directory: PathBuf = transaction(registry.as_path(), FIRST, "2026-10-10T00:00:00.000Z", true);
let mut nested: LandingReservation = LandingReservation::open(registry.as_path(), directory.as_path(), FIRST, true);
assert!(!nested.reserve(1).expect("not applicable"));
assert!(!directory.join(RESERVATION_REQUEST_FILENAME).exists());
assert!(!nested.held_by_another().expect("nested never waits"));
nested.await_slot().expect("returns at once");
remove(root.as_path());
}
#[test]
fn the_grant_follows_the_configured_lost_races() {
let (root, registry) = registry_of("replay-reservation-threshold");
let directory: PathBuf = transaction(registry.as_path(), FIRST, "2026-10-10T00:00:00.000Z", true);
let configured: u64 = 3;
let mut reservation: LandingReservation = LandingReservation::open(registry.as_path(), directory.as_path(), FIRST, false);
let mut granted_at: Option<u64> = None;
for lost in 1..=configured {
if reservation_earned(lost, configured) && reservation.reserve(lost).expect("reserve") {
granted_at = Some(lost);
break;
}
}
assert_eq!(granted_at, Some(configured));
let request: Vec<u8> = std::fs::read(directory.join(RESERVATION_REQUEST_FILENAME)).expect("request");
assert_eq!(request, reservation_request_bytes(configured));
reservation.release().expect("released");
remove(root.as_path());
}
#[test]
fn a_landing_takes_the_landing_lock_once_the_reservation_is_free() {
let (root, registry) = registry_of("replay-reservation-yield");
let holder_directory: PathBuf = transaction(registry.as_path(), FIRST, "2026-10-10T00:00:00.000Z", true);
let mut holder: LandingReservation = LandingReservation::open(registry.as_path(), holder_directory.as_path(), FIRST, false);
assert!(holder.reserve(1).expect("granted"));
holder.release().expect("released");
let waiter_directory: PathBuf = transaction(registry.as_path(), SECOND, "2026-10-10T00:00:01.000Z", true);
let waiter: LandingReservation = LandingReservation::open(registry.as_path(), waiter_directory.as_path(), SECOND, false);
let landing_lock: PathBuf = registry.join("landing.lock");
let lock: OwnerLock = acquire_reserved_landing_lock(&waiter, || {
let acquired = try_acquire_owner_lock(landing_lock.as_path(), Some(SECOND))
.map_err(ReservationError::Lock)?;
return acquired.ok_or_else(|| { return ReservationError::Registry(RecoveryError(String::from("landing lock busy"))); });
})
.expect("landing lock after the reservation is free");
lock.release().expect("released landing lock");
remove(root.as_path());
}