Files
improvise/crates/zset/tests/rebuild.rs
T

157 lines
4.9 KiB
Rust

//! A rebuild across every public operator: the tick it loads, the ticks after it, and `current()`.
//! Evidence: `context/test-evidence/zset.md`.
use proptest::prelude::*;
use std::cell::Cell;
use zset::{Circuit, Input, Progress, Rebuild, ZSet};
type Z<K> = ZSet<K>;
fn pairs() -> impl Strategy<Value = Z<u8>> {
prop::collection::vec((0u8..6, -3i64..=3), 0..6).prop_map(|p| p.into_iter().collect())
}
/// A pipeline using every operator; returns the input and a closure that drains all sinks.
fn build(c: &mut Circuit) -> (Input<u8>, Box<dyn Fn() -> String>) {
let (input, s) = c.input::<u8>();
let d = c.delay(&s);
let i = c.integrate(&s);
let df = c.differentiate(&s);
let a = c.index_by(&s, |k| k % 2);
let b = c.index_by(&i, |k| k % 2);
let j = c.join_indexed(&a, &b, |k, x, y| (*k, *x, *y));
let ij = c.integrate(&j);
let g = c.index_by(&df, |k| k % 3);
let agg = c.aggregate(&g, |_, vals| {
Some(vals.iter().map(|(_, w)| *w).sum::<i64>())
});
let dagg = c.delay(&agg);
let dist = c.distinct(&i);
let ddist = c.differentiate(&dist);
let fm = c.flat_map(&d, |k| vec![*k, k + 1]);
let sinks = (
c.output(&d),
c.output(&i),
c.output(&df),
c.output(&j),
c.output(&ij),
c.output(&agg),
c.output(&dagg),
c.output(&ddist),
c.output(&fm),
);
let drain = move || {
format!(
"d={:?} i={:?} df={:?} j={:?} ij={:?} agg={:?} dagg={:?} ddist={:?} fm={:?}",
sinks.0.take(),
sinks.1.take(),
sinks.2.take(),
sinks.3.take(),
sinks.4.take(),
sinks.5.take(),
sinks.6.take(),
sinks.7.take(),
sinks.8.take()
)
};
(input, Box::new(drain))
}
fn run(
prior: &Z<u8>,
snap: &Z<u8>,
post: &Z<u8>,
chunk: usize,
budget: usize,
) -> (String, String, String) {
let mut c = Circuit::new();
let (input, drain) = build(&mut c);
input.push(prior.clone());
c.step();
let _ = drain();
let mut job = Rebuild::new(input.clone(), snap.clone(), chunk);
let mut done = false;
for _ in 0..50 {
let left = Cell::new(budget);
if job.step(&mut c, || {
left.set(left.get() - 1);
left.get() == 0
}) == Progress::Done
{
done = true;
break;
}
}
assert!(done, "[rebuild] never finished");
let at = drain();
input.push(post.clone());
c.step();
let next = drain();
c.step();
(at, next, drain())
}
proptest! {
#[test]
fn every_operator_chunked_equals_one_step_and_so_do_the_ticks_after(
prior in pairs(), snap in pairs(), post in pairs(), chunk in 0usize..4, budget in 1usize..4,
) {
let want = run(&prior, &snap, &post, 100, 100);
let got = run(&prior, &snap, &post, chunk, budget);
prop_assert_eq!(&got.0, &want.0, "[rebuild-tick]");
prop_assert_eq!(&got.1, &want.1, "[next-tick]");
prop_assert_eq!(&got.2, &want.2, "[tick-after]");
}
}
#[test]
fn current_after_a_chunked_rebuild_is_the_whole_ticks_value() {
let snap: Z<u8> = [(1, 1), (2, 1)].into_iter().collect();
let cur = |chunk| {
let mut c = Circuit::new();
let (input, s) = c.input::<u8>();
let i = c.integrate(&s);
let mut job = Rebuild::new(input, snap.clone(), chunk);
while job.step(&mut c, || false) == Progress::Pending {}
i.current()
};
assert_eq!(cur(1), cur(10), "[current]");
}
#[test]
#[should_panic(expected = "rebuild was interrupted")]
fn a_host_step_between_rebuild_calls_is_refused_loudly() {
let snap: Z<u8> = [(1, 1), (2, 1)].into_iter().collect();
let mut c = Circuit::new();
let (input, _s) = c.input::<u8>();
let mut job = Rebuild::new(input, snap, 1);
assert_eq!(job.step(&mut c, || true), Progress::Pending);
c.step();
job.step(&mut c, || false);
}
#[test]
#[should_panic(expected = "rebuild was interrupted")]
fn two_interleaved_rebuilds_are_refused_loudly() {
let snap: Z<u8> = [(1, 1), (2, 1)].into_iter().collect();
let mut c = Circuit::new();
let (i1, _s1) = c.input::<u8>();
let (i2, _s2) = c.input::<u8>();
let (mut a, mut b) = (Rebuild::new(i1, snap.clone(), 1), Rebuild::new(i2, snap, 1));
assert_eq!(a.step(&mut c, || true), Progress::Pending);
assert_eq!(b.step(&mut c, || true), Progress::Pending);
a.step(&mut c, || false);
}
#[test]
fn a_finished_rebuild_and_an_interrupted_check_do_not_trip_on_later_ticks() {
let snap: Z<u8> = [(1, 1)].into_iter().collect();
let mut c = Circuit::new();
let (input, _s) = c.input::<u8>();
let mut job = Rebuild::new(input, snap, 1);
assert_eq!(job.step(&mut c, || true), Progress::Done);
c.step();
assert!(!job.interrupted(&c));
assert_eq!(job.step(&mut c, || true), Progress::Done);
}