use super::{RingContract, Violation};
fn full_cycle(contract: &mut RingContract, user_data: usize) {
contract.observe_push(user_data);
contract.observe_completion(user_data);
contract.observe_claim(user_data);
}
#[test]
fn a_complete_lifecycle_is_quiescent() {
let mut contract = RingContract::new();
full_cycle(&mut contract, 1);
full_cycle(&mut contract, 2);
full_cycle(&mut contract, 3);
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn nothing_at_all_is_quiescent() {
let contract = RingContract::new();
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn a_push_that_never_completes_is_reported() {
let mut contract = RingContract::new();
full_cycle(&mut contract, 1);
contract.observe_push(2);
assert_eq!(
contract.check_quiescent(),
vec![Violation::Outstanding { user_data: 2 }]
);
}
#[test]
fn a_completion_claimed_by_nothing_is_reported_as_a_leak() {
let mut contract = RingContract::new();
contract.observe_push(7);
contract.observe_completion(7);
assert_eq!(
contract.check_quiescent(),
vec![Violation::LeakedToken { user_data: 7 }]
);
}
#[test]
fn a_deliberate_leak_is_not_reported() {
let mut contract = RingContract::new();
contract.observe_push(9);
contract.observe_completion(9);
contract.observe_deliberate_leak(9);
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn a_deliberate_leak_before_any_completion_is_also_accepted() {
let mut contract = RingContract::new();
contract.observe_push(11);
contract.observe_deliberate_leak(11);
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn a_second_completion_for_the_same_operation_is_reported() {
let mut contract = RingContract::new();
contract.observe_push(4);
contract.observe_completion(4);
contract.observe_completion(4);
assert!(
contract
.violations()
.contains(&Violation::DuplicateCompletion { user_data: 4 }),
"a duplicate completion must be reported: {:?}",
contract.violations()
);
}
#[test]
fn a_completion_for_an_operation_never_pushed_is_reported() {
let mut contract = RingContract::new();
contract.observe_completion(0xDEAD);
assert!(
contract
.violations()
.contains(&Violation::UnexpectedCompletion { user_data: 0xDEAD }),
"an unrecognised completion must be reported: {:?}",
contract.violations()
);
}
#[test]
fn a_completion_after_a_claim_is_a_duplicate_not_a_fresh_operation() {
let mut contract = RingContract::new();
full_cycle(&mut contract, 5);
contract.observe_completion(5);
assert!(
contract
.violations()
.contains(&Violation::DuplicateCompletion { user_data: 5 }),
"a completion after a full cycle is a duplicate: {:?}",
contract.violations()
);
}
#[test]
fn a_busy_registered_buffer_is_reported_at_quiescence() {
let mut contract = RingContract::new();
contract.observe_buffer(0, 0);
contract.observe_buffer(1, 2);
assert_eq!(
contract.check_quiescent(),
vec![Violation::BufferStillInUse {
index: 1,
outstanding: 2
}]
);
}
#[test]
fn a_quiet_registered_buffer_is_not_reported() {
let mut contract = RingContract::new();
for index in 0..8 {
contract.observe_buffer(index, 0);
}
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn a_buffer_reported_busy_then_quiet_is_not_reported() {
let mut contract = RingContract::new();
contract.observe_buffer(3, 1);
contract.observe_buffer(3, 0);
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn violations_are_reported_in_a_stable_order() {
let mut contract = RingContract::new();
for user_data in [50_usize, 10, 30, 20, 40] {
contract.observe_push(user_data);
}
let first = contract.check_quiescent();
let second = contract.check_quiescent();
assert_eq!(first, second, "repeated checks must agree");
assert_eq!(
first,
vec![
Violation::Outstanding { user_data: 10 },
Violation::Outstanding { user_data: 20 },
Violation::Outstanding { user_data: 30 },
Violation::Outstanding { user_data: 40 },
Violation::Outstanding { user_data: 50 },
]
);
}
#[test]
fn every_violation_is_reported_rather_than_only_the_first() {
let mut contract = RingContract::new();
contract.observe_push(1); contract.observe_push(2);
contract.observe_completion(2); contract.observe_buffer(0, 3);
let violations = contract.check_quiescent();
assert_eq!(violations.len(), 3, "got {violations:?}");
}
#[test]
fn assert_quiescent_names_every_violation() {
let mut contract = RingContract::new();
contract.observe_push(1);
contract.observe_push(2);
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
contract.assert_quiescent();
}));
let payload = panicked.expect_err("assert_quiescent must panic when the contract is broken");
let message = payload
.downcast_ref::<String>()
.expect("a string panic payload");
assert!(
message.contains("0x1"),
"should name operation 1: {message}"
);
assert!(
message.contains("0x2"),
"should name operation 2: {message}"
);
}
#[test]
fn assert_quiescent_is_silent_when_the_contract_holds() {
let mut contract = RingContract::new();
full_cycle(&mut contract, 1);
contract.assert_quiescent();
}
#[test]
fn a_tokenless_operation_is_complete_once_it_completes() {
let mut contract = RingContract::new();
contract.observe_tokenless_push(1);
contract.observe_completion(1);
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn a_tokenless_operation_that_never_completes_is_still_reported() {
let mut contract = RingContract::new();
contract.observe_tokenless_push(2);
assert_eq!(
contract.check_quiescent(),
vec![Violation::Outstanding { user_data: 2 }]
);
}
#[test]
fn a_tokenless_operation_completing_twice_is_still_a_duplicate() {
let mut contract = RingContract::new();
contract.observe_tokenless_push(3);
contract.observe_completion(3);
contract.observe_completion(3);
assert!(
contract
.violations()
.contains(&Violation::DuplicateCompletion { user_data: 3 }),
"got {:?}",
contract.violations()
);
}
#[test]
fn a_push_that_was_rejected_is_simply_never_observed() {
let mut contract = RingContract::new();
contract.observe_tokenless_push(1);
contract.observe_completion(1);
assert_eq!(contract.check_quiescent(), Vec::new());
}
#[test]
fn busy_registered_buffers_are_reported_in_index_order() {
const BUSY: u32 = 8;
let mut contract = RingContract::new();
contract.observe_buffer(99, 0); for index in (0..BUSY).rev() {
contract.observe_buffer(index, (index + 1) as usize);
}
let reported = contract.check_quiescent();
let expected: Vec<_> = (0..BUSY)
.map(|index| Violation::BufferStillInUse {
index,
outstanding: (index + 1) as usize,
})
.collect();
assert_eq!(
reported, expected,
"busy buffers must be reported by ascending index, and the quiet one omitted"
);
}