extern crate alloc;
extern crate std;
use alloc::vec::Vec;
use crate::metis::{
AncestryAdmissionError, AncestryQueryError, AncestryRelation, Anchor, Composer, DotSet, Locus,
Metatheses, Metathesis, MovementBatchError, Rhapsody,
};
use proptest::prelude::*;
use super::{Moves, Text, clock, d};
use crate::metis::dot::RawDot;
fn root(text: &mut Text, rank: crate::kairos::Kairos) -> crate::metis::Dot {
text.compose(|dot| {
let mut delta = Rhapsody::new();
assert!(delta.weave(
dot,
Locus {
anchor: Anchor::Origin,
rank,
},
));
(delta, DotSet::new())
})
.0
}
fn rank_above_recension(
reading: &crate::metis::Recension,
clock: &crate::kairos::Clock<crate::kairos::TickCounter>,
anchor: Anchor,
) -> crate::kairos::Kairos {
if let Some(child) = reading.children_of(anchor).next()
&& let Some(locus) = reading.locus(child)
{
clock.observe(locus.rank);
}
clock.now(0u16)
}
fn rank_above_batch(
batch: &crate::metis::MovementBatch<'_>,
clock: &crate::kairos::Clock<crate::kairos::TickCounter>,
anchor: Anchor,
) -> crate::kairos::Kairos {
if let Some(child) = batch.children_of(anchor).next()
&& let Some(locus) = batch.locus(child)
{
clock.observe(locus.rank);
}
clock.now(0u16)
}
fn fixture(count: usize) -> (Text, Vec<crate::metis::Dot>) {
let mut text = Composer::new(1);
let clock = clock(1);
let roots = (0..count)
.map(|_| root(&mut text, clock.now(0u16)))
.collect();
(text, roots)
}
fn move_roots_sequentially(
text: &Text,
roots: &[crate::metis::Dot],
) -> (Moves, crate::metis::Recension, Vec<Anchor>) {
let mut moves = Composer::new(2);
let clock = clock(2);
let mut reading = text.state().store().recension(moves.state().store());
let pivot = roots[0];
let mut anchors = Vec::new();
for &target in &roots[1..] {
let anchor = reading.anchor_for_visual_insert(Some(pivot.into()));
let rank = rank_above_recension(&reading, &clock, anchor);
let _ = moves.compose_super(
|dot| {
Metatheses::singleton(
dot,
crate::metis::Metathesis {
target: target.into(),
to: Locus { anchor, rank },
},
)
},
|held| held.moves_of(target.into()),
);
reading.collate(text.state().store(), moves.state().store());
anchors.push(anchor);
}
(moves, reading, anchors)
}
fn move_roots_in_batch(
text: &Text,
roots: &[crate::metis::Dot],
) -> (Moves, crate::metis::Recension, Vec<Anchor>) {
let mut moves = Composer::new(2);
let clock = clock(2);
let mut reading = text.state().store().recension(moves.state().store());
let pivot = roots[0];
let mut anchors = Vec::new();
reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
for &target in &roots[1..] {
let anchor = batch.anchor_for_visual_insert(Some(pivot.into()));
let rank = rank_above_batch(batch, &clock, anchor);
let _ = batch
.move_to(target.into(), Locus { anchor, rank })
.expect("distinct placed roots admit a forward move");
anchors.push(anchor);
}
})
.expect("the fixture is fully placed");
(moves, reading, anchors)
}
#[test]
fn a_batch_preserves_intermediate_visual_anchor_selection() {
let (text, roots) = fixture(4);
let (sequential_moves, sequential, sequential_anchors) = move_roots_sequentially(&text, &roots);
let (batched_moves, batched, batched_anchors) = move_roots_in_batch(&text, &roots);
assert_eq!(batched_anchors, sequential_anchors);
assert!(matches!(batched_anchors[1], Anchor::Before(_)));
assert_eq!(
batched_moves.state().store(),
sequential_moves.state().store()
);
batched.check_collation(&sequential);
}
#[test]
fn an_invalid_batch_target_is_inert() {
let (text, roots) = fixture(3);
let mut moves = Composer::new(2);
let clock = clock(2);
let mut reading = text.state().store().recension(moves.state().store());
let original = reading.clone();
reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
let target = RawDot::new(77, 77);
let refusal = batch.move_to(
target,
Locus {
anchor: Anchor::After(roots[0].into()),
rank: clock.now(0u16),
},
);
assert!(matches!(
refusal,
Err(MovementBatchError::UnknownTarget { .. })
));
})
.expect("the fixture is fully placed");
assert!(moves.state().store().is_empty());
reading.check_collation(&original);
}
#[test]
fn exact_ancestry_tracks_each_accepted_batch_move() {
let (text, roots) = fixture(3);
let clock = clock(2);
let mut moves = Composer::new(2);
let mut reading = text.state().store().recension(moves.state().store());
let initial = reading
.with_exact_ancestry(|ancestry| ancestry.relation(roots[1], roots[0]).unwrap())
.unwrap();
assert_eq!(initial, AncestryRelation::NotDescendant);
reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
let _ = batch
.move_to(
roots[1].into(),
Locus {
anchor: Anchor::After(roots[0].into()),
rank: clock.now(0u16),
},
)
.unwrap();
assert_eq!(
batch.relation(roots[1], roots[0]),
Ok(AncestryRelation::Descendant)
);
assert_eq!(
batch.relation(roots[0], roots[0]),
Ok(AncestryRelation::Same)
);
assert_eq!(
batch.relation(d(9, 9), roots[0]),
Err(AncestryQueryError::UnknownDescendant { dot: d(9, 9) })
);
assert_eq!(
batch.relation(roots[0], d(9, 9)),
Err(AncestryQueryError::UnknownAncestor { dot: d(9, 9) })
);
})
.unwrap();
let final_relation = reading
.with_exact_ancestry(|ancestry| ancestry.relation(roots[1], roots[0]).unwrap())
.unwrap();
assert_eq!(final_relation, AncestryRelation::Descendant);
}
#[test]
fn an_unplaced_tentative_suffix_rolls_back_before_recording() {
let x = super::d(1, 1);
let d = super::d(1, 2);
let c = super::d(1, 3);
let unknown = (9, 9);
let mut text = Rhapsody::new();
for (dot, anchor, logical) in [
(x, Anchor::Origin, 1),
(d, Anchor::After(x.into()), 2),
(c, Anchor::Origin, 3),
] {
assert!(text.weave(
dot,
Locus {
anchor,
rank: crate::kairos::Kairos::new(1, logical, 1, 0u16),
},
));
}
let mut record = Metatheses::new();
let plays = [
(
super::d(2, 1),
Metathesis {
target: d.into(),
to: Locus {
anchor: Anchor::Origin,
rank: crate::kairos::Kairos::new(2, 1, 2, 0u16),
},
},
),
(
super::d(3, 1),
Metathesis {
target: x.into(),
to: Locus {
anchor: Anchor::After(unknown.into()),
rank: crate::kairos::Kairos::new(3, 1, 3, 0u16),
},
},
),
(
super::d(4, 1),
Metathesis {
target: x.into(),
to: Locus {
anchor: Anchor::After(d.into()),
rank: crate::kairos::Kairos::new(4, 1, 4, 0u16),
},
},
),
];
let mut context = DotSet::new();
for (witness, testimony) in plays {
assert!(record.insert(witness, testimony));
let _ = context.insert(witness);
}
let state = crate::metis::Dotted::try_new(record, context).unwrap();
let mut moves = Composer::adopt(5, state);
let mut reading = text.recension(moves.state().store());
assert!(reading.is_reachable(x));
assert!(reading.is_reachable(d));
let before = reading.locus(d);
reading
.with_movement_batch(&text, &mut moves, |batch| {
assert_eq!(
batch.move_to(
d.into(),
Locus {
anchor: Anchor::After(c.into()),
rank: crate::kairos::Kairos::new(10, 1, 5, 0u16),
},
),
Err(MovementBatchError::UnplacedResult {
target: d.into(),
first_unplaced: x.into(),
})
);
assert_eq!(batch.locus(d), before);
let _ = batch
.move_to(
c.into(),
Locus {
anchor: Anchor::Origin,
rank: crate::kairos::Kairos::new(5, 1, 5, 0u16),
},
)
.expect("the failed candidate did not raise rank admission");
})
.unwrap();
assert!(moves.state().store().get(super::d(5, 1)).is_some());
assert!(moves.state().store().get(super::d(5, 2)).is_none());
reading.check_collation(&text.recension(moves.state().store()));
}
#[test]
fn an_unplaced_reading_cannot_open_a_batch() {
let clock = clock(1);
let mut text = Rhapsody::new();
assert!(text.weave(
d(1, 1),
Locus {
anchor: Anchor::After(RawDot {
station: 9,
counter: 9
}),
rank: clock.now(0u16),
},
));
let mut moves = Composer::new(1);
let mut reading = text.recension(moves.state().store());
assert!(matches!(
reading.with_movement_batch(&text, &mut moves, |_| ()),
Err(MovementBatchError::UnplacedReading)
));
assert!(matches!(
reading.with_exact_ancestry(|_| ()),
Err(AncestryAdmissionError::UnplacedReading)
));
}
#[test]
fn a_repaired_dangling_birth_cannot_open_a_batch() {
let clock = clock(1);
let mut text = Rhapsody::new();
let target = d(1, 1);
let follower = d(1, 2);
assert!(text.weave(
target,
Locus {
anchor: Anchor::After(RawDot {
station: 9,
counter: 9
}),
rank: clock.now(0u16),
},
));
assert!(text.weave(
follower,
Locus {
anchor: Anchor::After(target.into()),
rank: clock.now(0u16),
},
));
let mut moves = Composer::new(1);
let _ = moves.compose_super(
|dot| {
Metatheses::singleton(
dot,
crate::metis::Metathesis {
target: target.into(),
to: Locus {
anchor: Anchor::Origin,
rank: clock.now(0u16),
},
},
)
},
|held| held.moves_of(target.into()),
);
let mut reading = text.recension(moves.state().store());
assert_eq!(reading.order(), [target, follower], "the move repairs both");
assert!(matches!(
reading.with_movement_batch(&text, &mut moves, |_| ()),
Err(MovementBatchError::UnplacedReading)
));
}
#[test]
fn a_nonmonotonic_batch_move_is_inert() {
let (text, roots) = fixture(3);
let mut moves = Composer::new(2);
let clock = clock(2);
let mut reading = text.state().store().recension(moves.state().store());
reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
let held_rank = clock.now(0u16);
let _ = batch
.move_to(
roots[1].into(),
Locus {
anchor: Anchor::After(roots[0].into()),
rank: held_rank,
},
)
.expect("the first forward move applies");
let before = batch.locus(roots[2]);
assert_eq!(
batch.move_to(
roots[2].into(),
Locus {
anchor: Anchor::After(roots[1].into()),
rank: held_rank,
},
),
Err(MovementBatchError::NonmonotonicRank)
);
assert_eq!(batch.locus(roots[2]), before);
})
.expect("the fixture is fully placed");
assert_eq!(moves.state().store().len(), 1);
reading.check_collation(&text.state().store().recension(moves.state().store()));
}
#[test]
fn consuming_the_batch_borrow_cannot_skip_finalization() {
fn consume<T>(_value: T) {}
let (text, roots) = fixture(3);
let mut moves = Composer::new(2);
let clock = clock(2);
let mut reading = text.state().store().recension(moves.state().store());
reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
let _ = batch
.move_to(
roots[1].into(),
Locus {
anchor: Anchor::After(roots[0].into()),
rank: clock.now(0u16),
},
)
.expect("the forward move applies");
consume(batch);
})
.expect("the fixture is fully placed");
reading.check_collation(&text.state().store().recension(moves.state().store()));
}
#[test]
fn panic_unwind_returns_the_coordinate_with_the_recorded_move() {
let (text, roots) = fixture(2);
let mut moves = Composer::new(2);
let clock = clock(2);
let mut reading = text.state().store().recension(moves.state().store());
let unwound = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = reading.with_movement_batch(text.state().store(), &mut moves, |batch| {
let _ = batch
.move_to(
roots[1].into(),
Locus {
anchor: Anchor::After(roots[0].into()),
rank: clock.now(0u16),
},
)
.unwrap();
panic!("exercise the batch finalizer");
});
}));
assert!(unwound.is_err());
reading.check_collation(&text.state().store().recension(moves.state().store()));
assert_eq!(
reading
.with_exact_ancestry(|ancestry| ancestry.relation(roots[1], roots[0]))
.unwrap(),
Ok(AncestryRelation::Descendant)
);
}
#[test]
fn an_exhausted_composer_cannot_diverge_record_and_reading() {
let (text, roots) = fixture(2);
let mut exhausted = DotSet::new();
assert!(exhausted.insert(d(2, u64::MAX)));
let mut moves = Composer::adopt(2, crate::metis::Dotted::from_context(exhausted));
let clock = clock(2);
let mut reading = text.state().store().recension(moves.state().store());
let original = reading.clone();
let refusal = reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
batch.move_to(
roots[1].into(),
Locus {
anchor: Anchor::After(roots[0].into()),
rank: clock.now(0u16),
},
)
})
.expect("the fixture is fully placed");
assert_eq!(
refusal,
Err(MovementBatchError::StationExhausted { station: 2 })
);
assert!(moves.state().store().is_empty());
reading.check_collation(&original);
}
#[cfg(feature = "timing")]
#[test]
fn a_profiled_move_uses_the_same_record_and_topology_path() {
let (text, roots) = fixture(2);
let clock = clock(2);
let mut moves = Composer::new(2);
let mut reading = text.state().store().recension(moves.state().store());
let profiled = reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
batch.move_to_profiled(
roots[1].into(),
Locus {
anchor: Anchor::After(roots[0].into()),
rank: clock.now(0u16),
},
)
})
.expect("the fixture is fully placed")
.expect("the forward move applies");
assert_eq!(profiled.witness, d(2, 1));
assert_eq!(profiled.delta, moves.state().clone());
assert_eq!(profiled.profile.topology.work.play_search_visits, 0);
assert_eq!(profiled.profile.topology.work.raw_suffix_len, 0);
assert_eq!(profiled.profile.topology.work.replay_len, 1);
assert_eq!(
profiled
.profile
.topology
.cycle_validation
.work
.guards_skipped,
1
);
assert_eq!(profiled.profile.topology.work.locus_replacements, 1);
#[allow(deprecated)]
let compatibility_total = profiled.profile.topology_placement;
assert_eq!(compatibility_total, profiled.profile.topology.total);
assert!(
profiled.profile.topology.total
>= profiled.profile.topology.phases.play_search
+ profiled.profile.topology.phases.suffix_unwind
+ profiled.profile.topology.phases.suffix_edit
+ profiled.profile.topology.phases.suffix_replay
);
reading.check_collation(&text.state().store().recension(moves.state().store()));
}
#[cfg(feature = "timing")]
#[test]
fn a_profiled_deep_refusal_uses_the_exact_coordinate() {
let (text, roots) = fixture(3);
let clock = clock(2);
let mut moves = Composer::new(2);
let mut reading = text.state().store().recension(moves.state().store());
let profiled = reading
.with_movement_batch(text.state().store(), &mut moves, |batch| {
for (target, anchor) in [(roots[1], roots[0]), (roots[2], roots[1])] {
let _ = batch
.move_to(
target.into(),
Locus {
anchor: Anchor::After(anchor.into()),
rank: clock.now(0u16),
},
)
.expect("the setup extends the effective chain");
}
batch.move_to_profiled(
roots[0].into(),
Locus {
anchor: Anchor::After(roots[2].into()),
rank: clock.now(0u16),
},
)
})
.expect("the fixture is fully placed")
.expect("a cycle-former remains valid record state");
let validation = profiled.profile.topology.cycle_validation;
assert_eq!(validation.work.guards_skipped, 0);
assert_eq!(validation.work.live_guard_entries, 1);
assert_eq!(validation.work.coordinate_queries, 1);
assert_eq!(validation.work.chain_nodes_visited, 0);
assert_eq!(validation.work.refusals, 1);
assert_eq!(validation.work.anchor_cycle_terminals, 0);
assert_eq!(validation.work.invariant_refusals, 0);
assert_eq!(profiled.profile.topology.work.replay_len, 1);
assert_eq!(profiled.profile.topology.work.locus_replacements, 0);
assert!(profiled.profile.topology.phases.suffix_replay >= validation.total);
assert_eq!(reading.refused(), &[profiled.witness]);
reading.check_collation(&text.state().store().recension(moves.state().store()));
}
proptest! {
#[test]
fn prop_forward_batches_equal_per_testimony_collation(count in 2usize..18) {
let (text, roots) = fixture(count);
let (sequential_moves, sequential, sequential_anchors) =
move_roots_sequentially(&text, &roots);
let (batched_moves, batched, batched_anchors) = move_roots_in_batch(&text, &roots);
prop_assert_eq!(batched_anchors, sequential_anchors);
prop_assert_eq!(batched_moves.state().store(), sequential_moves.state().store());
batched.check_collation(&sequential);
}
}