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

198 lines
6.3 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.
type Drain = Box<dyn Fn(&mut Circuit) -> String>;
fn build(c: &mut Circuit) -> (Input<u8>, Drain) {
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 |c: &mut Circuit| {
format!(
"d={:?} i={:?} df={:?} j={:?} ij={:?} agg={:?} dagg={:?} ddist={:?} fm={:?}",
c.take(&sinks.0),
c.take(&sinks.1),
c.take(&sinks.2),
c.take(&sinks.3),
c.take(&sinks.4),
c.take(&sinks.5),
c.take(&sinks.6),
c.take(&sinks.7),
c.take(&sinks.8)
)
};
(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);
c.push(&input, prior.clone());
c.step();
let _ = drain(&mut c);
let mut job = Rebuild::new(input, 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(&mut c);
c.push(&input, post.clone());
c.step();
let next = drain(&mut c);
c.step();
(at, next, drain(&mut c))
}
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 {}
c.current(&i)
};
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);
}
#[test]
#[should_panic(expected = "another circuit")]
fn a_rebuild_stepped_on_a_foreign_circuit_is_refused_loudly() {
let (mut a, mut b) = (Circuit::new(), Circuit::new());
let (input, _s) = a.input::<u8>();
let mut job = Rebuild::new(input, [(1u8, 1)].into_iter().collect(), 1);
job.step(&mut b, || false);
}
#[test]
fn a_rebuild_knows_the_circuit_its_input_came_from() {
let (mut a, b) = (Circuit::new(), Circuit::new());
let (input, _s) = a.input::<u8>();
let job = Rebuild::new(input, ZSet::new(), 1);
assert!(
job.belongs_to(&a),
"[owner] the circuit that made the input"
);
assert!(!job.belongs_to(&b), "[owner] another circuit");
}
#[test]
fn an_interrupted_rebuild_stays_interrupted_and_a_new_job_loads_the_snapshot() {
let snap: Z<u8> = [(1, 1), (2, 1), (3, 1)].into_iter().collect();
let mut c = Circuit::new();
let (input, s) = c.input::<u8>();
let i = c.integrate(&s);
let mut job = Rebuild::new(input, snap.clone(), 1);
assert_eq!(job.step(&mut c, || true), Progress::Pending);
c.step();
for _ in 0..3 {
assert!(job.interrupted(&c), "[forever] steps never un-interrupt it");
c.step();
}
let mut fresh = Rebuild::new(input, snap, 1);
assert_eq!(fresh.step(&mut c, || false), Progress::Done);
assert_eq!(c.current(&i).len(), 3);
}