use super::*;
use alloc::collections::BTreeSet;
use proptest::prelude::*;
fn d(station: u32, counter: u64) -> Dot {
Dot::from_parts(station, counter).expect("a test dot has a nonzero counter")
}
fn model_diff(held: &BTreeSet<Dot>, live: &BTreeSet<Dot>) -> BTreeSet<Dot> {
held.symmetric_difference(live).copied().collect()
}
proptest! {
#[test]
fn prop_occupancy_agrees_with_the_set_model(
ops in prop::collection::vec((0..3u32, 1..400u64, any::<bool>()), 1..120),
cut in 0usize..120,
) {
let mut plane = OccupancyPlane::new();
let mut model: BTreeSet<Dot> = BTreeSet::new();
let mut held_plane = OccupancyPlane::new();
let mut held_model: BTreeSet<Dot> = BTreeSet::new();
for (step, &(station, index, present)) in ops.iter().enumerate() {
if step == cut {
held_plane.adopt(&plane);
held_model = model.clone();
}
plane.set(station, index, present);
if present {
let _ = model.insert(d(station, index));
} else {
let _ = model.remove(&d(station, index));
}
plane.check_against(model.iter().copied());
}
let walked: BTreeSet<Dot> = held_plane
.changed_pages(&plane)
.flat_map(|(key, held, live)| OccupancyPlane::dots_of(key, held ^ live))
.collect();
prop_assert_eq!(walked, model_diff(&held_model, &model));
}
}
proptest! {
#[test]
fn prop_batched_flips_agree_with_the_point_form(
batches in prop::collection::vec(
prop::collection::btree_map((0..3u32, 1..400u64), any::<bool>(), 1..40),
1..12,
),
) {
let mut batched = OccupancyPlane::new();
let mut pointwise = OccupancyPlane::new();
let mut model: BTreeSet<Dot> = BTreeSet::new();
for batch in batches {
batched.apply_flips(
batch
.iter()
.map(|(&(station, index), &present)| (d(station, index), present)),
);
for (&(station, index), &present) in &batch {
pointwise.set(station, index, present);
if present {
let _ = model.insert(d(station, index));
} else {
let _ = model.remove(&d(station, index));
}
}
prop_assert_eq!(&batched, &pointwise);
batched.check_against(model.iter().copied());
}
}
}
proptest! {
#[test]
fn prop_set_run_agrees_with_the_point_form(
ops in prop::collection::vec(
prop_oneof![
(0..3u32, 1..400u64, any::<bool>())
.prop_map(|(station, index, present)| (station, index, 1u32, present)),
(0..3u32, 1..400u64, 1..200u32)
.prop_map(|(station, first, len)| (station, first, len, true)),
(0..3u32, (u64::MAX - 100)..=u64::MAX, 1..80u32)
.prop_map(|(station, first, len)| (station, first, len, true)),
],
1..40,
),
) {
let mut bulk = OccupancyPlane::new();
let mut pointwise = OccupancyPlane::new();
let mut model: BTreeSet<Dot> = BTreeSet::new();
for &(station, first, len, present) in &ops {
if len == 1 && !present {
bulk.set(station, first, false);
pointwise.set(station, first, false);
let _ = model.remove(&d(station, first));
} else {
bulk.set_run(station, first, len);
let last = first.saturating_add(u64::from(len) - 1);
let mut dot = first;
loop {
pointwise.set(station, dot, true);
let _ = model.insert(d(station, dot));
if dot >= last {
break;
}
dot += 1;
}
}
prop_assert_eq!(&bulk, &pointwise);
}
bulk.check_against(model.iter().copied());
}
}
#[test]
fn a_page_is_born_and_retires_with_its_bits() {
let mut plane = OccupancyPlane::new();
plane.set(1, 64, true);
plane.set(1, 65, true);
plane.set(1, 64, false);
plane.check_against(core::iter::once(d(1, 65)));
plane.set(1, 65, false);
plane.check_against(core::iter::empty());
assert_eq!(plane, OccupancyPlane::new(), "the drained plane is empty");
}
#[test]
fn the_changed_walk_reads_both_directions() {
let held = OccupancyPlane::from_dots([d(1, 1), d(1, 2), d(2, 70)].into_iter());
let live = OccupancyPlane::from_dots([d(1, 2), d(1, 3), d(3, 5)].into_iter());
let changed: Vec<Dot> = held
.changed_pages(&live)
.flat_map(|(key, a, b)| OccupancyPlane::dots_of(key, a ^ b))
.collect();
assert_eq!(changed, [d(1, 1), d(1, 3), d(2, 70), d(3, 5)]);
}
#[test]
fn a_ceiling_dot_occupies_the_last_page() {
let mut plane = OccupancyPlane::new();
plane.set(7, u64::MAX, true);
plane.check_against(core::iter::once(d(7, u64::MAX)));
plane.set(7, u64::MAX, false);
assert_eq!(plane, OccupancyPlane::new());
}