use super::region_table::RegionTable;
use crate::record::region::RegionState;
use crate::types::{Budget, Time};
use proptest::prelude::*;
fn arb_budget() -> impl Strategy<Value = Budget> {
(1u64..=3600, 1u32..=10000, 1u32..=1000, 1u8..=255).prop_map(|(secs, polls, cost, prio)| {
Budget::new()
.with_deadline(Time::from_secs(secs))
.with_poll_quota(polls)
.with_cost_quota(cost.into())
.with_priority(prio)
})
}
fn arb_close_sequence() -> impl Strategy<Value = Vec<&'static str>> {
prop::collection::vec(
prop::sample::select(&[
"begin_close",
"begin_drain",
"begin_finalize",
"complete_close",
]),
1..=10,
)
}
#[test]
fn mr_close_operation_idempotence() {
proptest!(|(budget in arb_budget())| {
let mut table = RegionTable::new();
let region_id = table.create_root(budget, Time::ZERO);
let region = table.get(region_id.arena_index()).unwrap();
let result1 = region.begin_close(None);
prop_assert!(result1, "First close should transition Open -> Closing");
let result2 = region.begin_close(None);
prop_assert!(!result2, "Second close should return false (idempotent)");
let result3 = region.begin_close(None);
prop_assert!(!result3, "Third close should return false (idempotent)");
let drain_result = region.begin_drain();
let finalize_result = region.begin_finalize();
let complete_result = region.complete_close();
prop_assert!(drain_result, "Closing region should transition into Draining");
prop_assert!(finalize_result, "Draining region should transition into Finalizing");
prop_assert!(complete_result, "Finalizing empty region should transition into Closed");
prop_assert_eq!(
table.state(region_id),
Some(RegionState::Closed),
"Complete idempotence chain should close an empty region"
);
});
}
#[test]
fn mr_state_transition_monotonicity() {
proptest!(|(budget in arb_budget(), ops in arb_close_sequence())| {
let mut table = RegionTable::new();
let region_id = table.create_root(budget, Time::ZERO);
let region = table.get(region_id.arena_index()).unwrap();
let mut prev_state = table.state(region_id).unwrap();
for op in ops {
let current_state = table.state(region_id).unwrap();
prop_assert!(state_ordering(current_state) >= state_ordering(prev_state),
"State regression detected: {:?} -> {:?}", prev_state, current_state);
match op {
"begin_close" => { region.begin_close(None); },
"begin_drain" => { region.begin_drain(); },
"begin_finalize" => { region.begin_finalize(); },
"complete_close" => { region.complete_close(); },
_ => unreachable!(),
}
prev_state = current_state;
}
});
}
fn state_ordering(state: RegionState) -> u8 {
match state {
RegionState::Open => 0,
RegionState::Closing => 1,
RegionState::Draining => 2,
RegionState::Finalizing => 3,
RegionState::Closed => 4,
}
}
fn state_sequence_is_monotonic(states: &[RegionState]) -> bool {
states
.windows(2)
.all(|pair| state_ordering(pair[1]) >= state_ordering(pair[0]))
}
#[test]
fn mr_return_value_consistency() {
proptest!(|(budget in arb_budget())| {
let mut table = RegionTable::new();
let region_id = table.create_root(budget, Time::ZERO);
let region = table.get(region_id.arena_index()).unwrap();
let close1 = region.begin_close(None);
let close2 = region.begin_close(None);
let close3 = region.begin_close(None);
if close1 {
prop_assert!(!close2 && !close3, "Subsequent closes should return false after successful first close");
} else {
prop_assert!(!close2 && !close3, "All closes should return false if first failed");
}
let drain1 = region.begin_drain();
let drain2 = region.begin_drain();
prop_assert!(drain1 >= drain2, "Second drain call should not be more successful than first");
let fin1 = region.begin_finalize();
let fin2 = region.begin_finalize();
prop_assert!(fin1 >= fin2, "Second finalize call should not be more successful than first");
});
}
#[test]
fn mr_state_preservation_on_failure() {
proptest!(|(budget in arb_budget())| {
let mut table = RegionTable::new();
let region_id = table.create_root(budget, Time::ZERO);
let region = table.get(region_id.arena_index()).unwrap();
let _initial_state = table.state(region_id).unwrap();
let state_before = table.state(region_id).unwrap();
let drain_result = region.begin_drain(); let state_after = table.state(region_id).unwrap();
prop_assert_eq!(state_before, state_after,
"Failed begin_drain should not change state from {:?}", state_before);
prop_assert!(!drain_result, "begin_drain should fail on Open region");
let state_before = table.state(region_id).unwrap();
let complete_result = region.complete_close(); let state_after = table.state(region_id).unwrap();
prop_assert_eq!(state_before, state_after,
"Failed complete_close should not change state from {:?}", state_before);
prop_assert!(!complete_result, "complete_close should fail on non-Finalizing region");
});
}
#[test]
fn mr_full_close_chain_idempotence() {
proptest!(|(budget in arb_budget())| {
let mut table = RegionTable::new();
let region_id = table.create_root(budget, Time::ZERO);
let region = table.get(region_id.arena_index()).unwrap();
let close_sequence = || {
region.begin_close(None);
region.begin_finalize(); region.complete_close();
};
close_sequence();
let _state_after_first = table.state(region_id);
close_sequence();
let state_after_second = table.state(region_id);
close_sequence();
let state_after_third = table.state(region_id);
prop_assert_eq!(state_after_second, state_after_third,
"State should stabilize after complete close sequence");
if let Some(final_state) = state_after_third {
prop_assert!(matches!(final_state, RegionState::Closed | RegionState::Finalizing),
"Final state should be Closed or Finalizing, got {:?}", final_state);
}
});
}
#[test]
fn mr_cross_operation_safety() {
proptest!(|(budget in arb_budget(),
ops1 in arb_close_sequence(),
ops2 in arb_close_sequence())| {
let mut table1 = RegionTable::new();
let region1_id = table1.create_root(budget, Time::ZERO);
let region1 = table1.get(region1_id.arena_index()).unwrap();
let mut table2 = RegionTable::new();
let region2_id = table2.create_root(budget, Time::ZERO);
let region2 = table2.get(region2_id.arena_index()).unwrap();
let states1 = apply_operation_sequence(region1, &ops1);
let states2 = apply_operation_sequence(region2, &ops2);
let state1 = table1.state(region1_id);
let state2 = table2.state(region2_id);
prop_assert!(state1.is_some(), "Region 1 should have valid state");
prop_assert!(state2.is_some(), "Region 2 should have valid state");
prop_assert!(
state_sequence_is_monotonic(&states1),
"Region 1 state path should never regress: {:?}",
states1
);
prop_assert!(
state_sequence_is_monotonic(&states2),
"Region 2 state path should never regress: {:?}",
states2
);
});
}
fn apply_operation_sequence(
region: &crate::record::RegionRecord,
ops: &[&str],
) -> Vec<RegionState> {
let mut states = Vec::with_capacity(ops.len() + 1);
states.push(region.state());
for op in ops {
match *op {
"begin_close" => {
region.begin_close(None);
}
"begin_drain" => {
region.begin_drain();
}
"begin_finalize" => {
region.begin_finalize();
}
"complete_close" => {
region.complete_close();
}
_ => {}
}
states.push(region.state());
}
states
}
#[test]
fn mr_compound_idempotence_and_monotonicity() {
proptest!(|(budget in arb_budget())| {
let mut table = RegionTable::new();
let region_id = table.create_root(budget, Time::ZERO);
let region = table.get(region_id.arena_index()).unwrap();
let mut states = vec![table.state(region_id).unwrap()];
for _ in 0..5 {
region.begin_close(None);
states.push(table.state(region_id).unwrap());
region.begin_finalize();
states.push(table.state(region_id).unwrap());
region.complete_close();
states.push(table.state(region_id).unwrap());
}
for i in 1..states.len() {
prop_assert!(state_ordering(states[i]) >= state_ordering(states[i-1]),
"State ordering violation at step {}: {:?} -> {:?}",
i, states[i-1], states[i]);
}
let final_states = &states[states.len()-3..];
prop_assert!(final_states.iter().all(|&s| s == final_states[0]),
"Final states should be stable: {:?}", final_states);
});
}
#[cfg(test)]
mod mutation_tests {
#[test]
fn mr_suite_catches_panic_mutation() {
fn mutated_close_operation(call_count: &mut u32) -> bool {
*call_count += 1;
assert!(*call_count <= 1, "Double close not allowed!");
true
}
let mut call_count = 0;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
mutated_close_operation(&mut call_count); mutated_close_operation(&mut call_count); }));
assert!(result.is_err(), "MR suite should detect panic mutation");
}
#[test]
fn mr_suite_catches_state_corruption_mutation() {
#[derive(Debug, Clone, Copy, PartialEq)]
enum MockState {
Open,
Closing,
}
fn mutated_state_transition(state: &mut MockState) -> bool {
match *state {
MockState::Open => {
*state = MockState::Closing;
true
}
MockState::Closing => {
*state = MockState::Open;
false
} }
}
let mut state = MockState::Open;
mutated_state_transition(&mut state); assert_eq!(state, MockState::Closing);
mutated_state_transition(&mut state); assert_eq!(state, MockState::Open);
}
}
#[cfg(test)]
mod mr_validation {
#[test]
fn validate_mr_fault_sensitivity() {
fn mutated_close_operation(call_count: &mut u32) -> bool {
*call_count += 1;
assert!(*call_count <= 1, "Double close not allowed!");
true
}
let mut call_count = 0;
let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
mutated_close_operation(&mut call_count);
mutated_close_operation(&mut call_count);
}));
assert!(
panic_result.is_err(),
"MR1/MR5 should detect panic-on-repeat mutations"
);
let regressing_path = [
super::RegionState::Open,
super::RegionState::Closing,
super::RegionState::Open,
];
assert!(
!super::state_sequence_is_monotonic(®ressing_path),
"MR2/MR6 should reject state-regression mutations"
);
}
#[test]
fn validate_mr_independence() {
let target_bug_classes = [
("MR1", "panic_on_repeat"),
("MR2", "state_regression"),
("MR3", "return_inconsistency"),
("MR4", "failed_transition_mutation"),
("MR5", "sequence_instability"),
("MR6", "order_dependent_state_regression"),
];
let unique_classes = target_bug_classes
.iter()
.map(|(_, class)| *class)
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(
unique_classes.len(),
target_bug_classes.len(),
"Each MR should target a distinct primary bug class"
);
assert!(
unique_classes.contains("state_regression")
&& unique_classes.contains("failed_transition_mutation")
&& unique_classes.contains("order_dependent_state_regression"),
"MR suite should cover logic, isolation, and ordering failures"
);
}
}