use super::{Accounting, RingId};
#[test]
fn a_fresh_ledger_is_empty() {
let a = Accounting::new();
assert_eq!(a.outstanding(), 0);
assert_eq!(a.registered_file_count(), 0);
assert_eq!(a.registered_buffer_count(), 0);
}
#[test]
fn identities_start_at_zero_and_increase_by_one() {
let mut a = Accounting::new();
for expected in 0..64usize {
assert_eq!(a.reserve_user_data().expect("space remains"), expected);
}
}
#[test]
fn an_identity_is_never_handed_out_twice() {
let mut a = Accounting::new();
let mut seen = std::collections::HashSet::new();
for _ in 0..4096 {
let id = a.reserve_user_data().expect("space remains");
assert!(seen.insert(id), "identity {id} was handed out twice");
}
}
#[test]
fn completing_an_operation_does_not_recycle_its_identity() {
let mut a = Accounting::new();
let first = a.reserve_user_data().expect("space remains");
assert!(a.record_completion(first));
assert_eq!(a.outstanding(), 0);
let second = a.reserve_user_data().expect("space remains");
assert_ne!(
first, second,
"an identity must not be reused after completion"
);
}
#[test]
fn reserving_raises_the_outstanding_count() {
let mut a = Accounting::new();
for expected in 1..=32usize {
a.reserve_user_data().expect("space remains");
assert_eq!(a.outstanding(), expected);
}
}
#[test]
fn a_recorded_completion_lowers_it() {
let mut a = Accounting::new();
let ids: Vec<usize> = (0..32)
.map(|_| a.reserve_user_data().expect("space remains"))
.collect();
for (done, id) in ids.iter().enumerate() {
assert!(a.record_completion(*id), "{id} was minted and in flight");
assert_eq!(a.outstanding(), ids.len() - done - 1);
}
assert!(a.is_quiescent());
}
#[test]
fn completions_retire_their_own_identity_in_any_order() {
let mut a = Accounting::new();
let first = a.reserve_user_data().expect("space remains");
let second = a.reserve_user_data().expect("space remains");
assert!(a.record_completion(second));
assert!(!a.is_quiescent(), "the first operation is still owed");
assert!(
!a.record_completion(second),
"the second is already retired"
);
assert!(
!a.is_quiescent(),
"a repeat of the second retired the first"
);
assert!(a.record_completion(first));
assert!(a.is_quiescent());
}
#[test]
fn a_cancelled_reservation_lowers_it_too() {
let mut a = Accounting::new();
let kept = a.reserve_user_data().expect("space remains");
let failed = a.reserve_user_data().expect("space remains");
a.cancel_reservation(failed);
assert_eq!(a.outstanding(), 1);
assert!(
!a.record_completion(failed),
"a cancelled reservation can never be retired again"
);
assert!(a.record_completion(kept), "the surviving one is still owed");
assert!(a.is_quiescent());
}
#[test]
fn a_completion_against_an_empty_ledger_retires_nothing() {
let mut a = Accounting::new();
assert!(!a.record_completion(0), "identity 0 was never minted");
assert_eq!(a.outstanding(), 0);
assert!(a.is_quiescent());
}
#[test]
fn a_foreign_or_duplicate_completion_cannot_fake_quiescence() {
let mut a = Accounting::new();
let first = a.reserve_user_data().expect("space remains");
let second = a.reserve_user_data().expect("space remains");
assert!(
!a.record_completion(usize::MAX),
"a foreign identity was never minted"
);
assert!(a.record_completion(first));
assert!(
!a.record_completion(first),
"a duplicate of a retired identity"
);
assert_eq!(a.outstanding(), 1, "only the second is still owed");
assert!(!a.is_quiescent(), "the second operation is still in flight");
assert!(a.record_completion(second));
assert!(a.is_quiescent());
}
#[test]
fn an_identity_still_in_flight_is_refused_rather_than_aliased() {
let mut a = Accounting::new();
let live = a.reserve_user_data().expect("space remains");
a.set_next_user_data_for_test(live);
let error = a
.reserve_user_data()
.expect_err("the identity is still in flight");
assert!(
error.to_string().contains("already in flight"),
"unexpected error: {error}"
);
assert_eq!(a.outstanding(), 1, "the refusal minted nothing");
assert!(a.record_completion(live));
a.set_next_user_data_for_test(live);
assert_eq!(a.reserve_user_data().expect("not in flight"), live);
}
#[test]
fn identity_exhaustion_is_an_error_rather_than_a_reuse() {
let mut a = Accounting::new();
a.set_next_user_data_for_test(usize::MAX - 1);
let last = a
.reserve_user_data()
.expect("the final identity is available");
assert_eq!(last, usize::MAX - 1);
let error = a
.reserve_user_data()
.expect_err("there is no identity after the last one");
assert!(
error.to_string().contains("exhausted"),
"the error says what happened: {error}"
);
}
#[test]
fn the_last_identity_is_max_minus_one_not_max() {
let mut a = Accounting::new();
a.set_next_user_data_for_test(usize::MAX);
let _ = a
.reserve_user_data()
.expect_err("usize::MAX is never handed out");
}
#[test]
fn an_exhausted_ledger_does_not_charge_the_failed_reservation() {
let mut a = Accounting::new();
a.set_next_user_data_for_test(usize::MAX);
let before = a.outstanding();
let _ = a.reserve_user_data().expect_err("exhausted");
assert_eq!(
a.outstanding(),
before,
"a refused reservation costs nothing"
);
}
#[test]
fn registration_counts_advance_by_the_amount_reserved() {
let mut a = Accounting::new();
a.reserve_registered_files(4);
assert_eq!(a.registered_file_count(), 4);
a.reserve_registered_files(3);
assert_eq!(a.registered_file_count(), 7);
a.reserve_registered_buffers(8);
assert_eq!(a.registered_buffer_count(), 8);
}
#[test]
fn the_two_registration_counts_are_independent() {
let mut a = Accounting::new();
a.reserve_registered_files(5);
assert_eq!(
a.registered_buffer_count(),
0,
"files must not move buffers"
);
a.reserve_registered_buffers(9);
assert_eq!(
a.registered_file_count(),
5,
"and buffers must not move files"
);
}
#[test]
fn registration_counts_saturate_rather_than_wrapping() {
let mut a = Accounting::new();
a.reserve_registered_files(u32::MAX);
a.reserve_registered_files(1);
assert_eq!(a.registered_file_count(), u32::MAX);
a.reserve_registered_buffers(u32::MAX);
a.reserve_registered_buffers(7);
assert_eq!(a.registered_buffer_count(), u32::MAX);
}
#[test]
fn reserving_zero_registrations_changes_nothing() {
let mut a = Accounting::new();
a.reserve_registered_files(3);
a.reserve_registered_files(0);
assert_eq!(a.registered_file_count(), 3);
}
#[test]
fn registrations_and_operations_do_not_interfere() {
let mut a = Accounting::new();
let op = a.reserve_user_data().expect("space remains");
a.reserve_registered_files(2);
a.reserve_registered_buffers(2);
assert_eq!(a.outstanding(), 1, "registering does not mint an operation");
a.record_completion(op);
assert_eq!(
a.registered_file_count(),
2,
"completing does not unregister"
);
assert_eq!(a.registered_buffer_count(), 2);
}
#[test]
fn every_ledger_gets_its_own_identity() {
let ids: Vec<RingId> = (0..256).map(|_| Accounting::new().ring_id()).collect();
let unique: std::collections::HashSet<RingId> = ids.iter().copied().collect();
assert_eq!(unique.len(), ids.len(), "two ledgers shared an identity");
}
#[test]
fn a_ledgers_identity_does_not_change_over_its_life() {
let mut a = Accounting::new();
let id = a.ring_id();
let op = a.reserve_user_data().expect("space remains");
a.record_completion(op);
a.reserve_registered_files(4);
assert_eq!(a.ring_id(), id, "identity is fixed for the ledger's life");
}
#[test]
fn identities_from_separate_ledgers_do_not_collide_even_though_both_start_at_zero() {
let mut a = Accounting::new();
let mut b = Accounting::new();
assert_eq!(a.reserve_user_data().expect("space"), 0);
assert_eq!(b.reserve_user_data().expect("space"), 0);
assert_ne!(
a.ring_id(),
b.ring_id(),
"the ring id is what disambiguates the shared user data"
);
}