66898d81e4
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
157 lines
4.9 KiB
Rust
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);
|
|
}
|