feat(stitched): Implement a simple REPL for testing stitched ordering

This commit is contained in:
Ginger
2026-01-22 14:55:08 -05:00
parent 4da955438e
commit 4a6295e374
6 changed files with 269 additions and 94 deletions
+5 -94
View File
@@ -1,101 +1,12 @@
use std::sync::atomic::{AtomicU64, Ordering};
use itertools::Itertools;
use super::{algorithm::*, *};
use crate::memory_backend::MemoryStitcherBackend;
mod parser;
/// A stitcher backend which holds a stitched ordering in RAM.
#[derive(Default)]
struct TestStitcherBackend<'id> {
items: Vec<(u64, StitchedItem<'id>)>,
counter: AtomicU64,
}
impl<'id> TestStitcherBackend<'id> {
fn next_id(&self) -> u64 { self.counter.fetch_add(1, Ordering::Relaxed) }
fn extend(&mut self, results: OrderUpdates<'id, <Self as StitcherBackend>::Key>) {
for update in results.gap_updates {
let Some(gap_index) = self.items.iter().position(|(key, _)| *key == update.key)
else {
panic!("bad update key {}", update.key);
};
let insertion_index = if update.gap.is_empty() {
self.items.remove(gap_index);
gap_index
} else {
match self.items.get_mut(gap_index) {
| Some((_, StitchedItem::Gap(gap))) => {
*gap = update.gap;
},
| Some((key, other)) => {
panic!("expected item with key {key} to be a gap, it was {other:?}");
},
| None => unreachable!("we just checked that this index is valid"),
}
gap_index.checked_add(1).expect(
"should never allocate usize::MAX ids. what kind of test are you running",
)
};
let to_insert: Vec<_> = update
.inserted_items
.into_iter()
.map(|item| (self.next_id(), item))
.collect();
self.items
.splice(insertion_index..insertion_index, to_insert.into_iter())
.for_each(drop);
}
let new_items: Vec<_> = results
.new_items
.into_iter()
.map(|item| (self.next_id(), item))
.collect();
self.items.extend(new_items);
}
fn iter(&self) -> impl Iterator<Item = &StitchedItem<'id>> {
self.items.iter().map(|(_, item)| item)
}
}
impl StitcherBackend for TestStitcherBackend<'_> {
type Key = u64;
fn find_matching_gaps<'a>(
&'a self,
events: impl Iterator<Item = &'a str>,
) -> impl Iterator<Item = (Self::Key, Gap)> {
// nobody cares about test suite performance right
let mut gaps = vec![];
for event in events {
for (key, item) in &self.items {
if let StitchedItem::Gap(gap) = item
&& gap.contains(event)
{
gaps.push((*key, gap.clone()));
}
}
}
gaps.into_iter()
}
fn event_exists<'a>(&'a self, event: &'a str) -> bool {
self.items
.iter()
.any(|item| matches!(item.1, StitchedItem::Event(id) if event == id))
}
}
fn run_testcase(testcase: parser::TestCase<'_>) {
let mut backend = TestStitcherBackend::default();
let mut backend = MemoryStitcherBackend::default();
for (index, phase) in testcase.into_iter().enumerate() {
let stitcher = Stitcher::new(&backend);
@@ -107,7 +18,7 @@ fn run_testcase(testcase: parser::TestCase<'_>) {
for update in &updates.gap_updates {
println!("update to gap {}:", update.key);
println!(" new gap contents: {:?}", update.gap);
println!(" new items: {:?}", update.inserted_items);
println!(" inserted items: {:?}", update.inserted_items);
}
println!("expected new items: {:?}", &phase.order.new_items);
@@ -134,9 +45,9 @@ fn run_testcase(testcase: parser::TestCase<'_>) {
}
backend.extend(updates);
println!("extended ordering: {:?}", backend.items);
println!("extended ordering: {:?}", backend);
for (expected, actual) in phase.order.iter().zip_eq(backend.iter()) {
for (expected, ref actual) in phase.order.iter().zip_eq(backend.iter()) {
assert_eq!(
expected, actual,
"bad item in order, expected {expected:?} but got {actual:?}",