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");
a.record_completion();
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();
for _ in 0..32 {
a.reserve_user_data().expect("space remains");
}
for expected in (0..32usize).rev() {
a.record_completion();
assert_eq!(a.outstanding(), expected);
}
}
#[test]
fn a_cancelled_reservation_lowers_it_too() {
let mut a = Accounting::new();
a.reserve_user_data().expect("space remains");
a.reserve_user_data().expect("space remains");
a.cancel_reservation();
assert_eq!(a.outstanding(), 1);
}
#[test]
fn the_outstanding_count_saturates_rather_than_wrapping() {
let mut a = Accounting::new();
a.record_completion();
assert_eq!(a.outstanding(), 0, "recording against an empty ledger");
a.cancel_reservation();
assert_eq!(a.outstanding(), 0, "cancelling against an empty ledger");
let mut b = Accounting::new();
b.reserve_user_data().expect("space remains");
b.record_completion();
b.record_completion();
b.cancel_reservation();
assert_eq!(b.outstanding(), 0, "over-releasing a single reservation");
}
#[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();
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();
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();
a.reserve_user_data().expect("space remains");
a.record_completion();
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"
);
}