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

172 lines
5.0 KiB
Rust

//! A circuit moves between threads, one at a time (`context/code/zset.md`, "Threads").
//! Evidence: `context/test-evidence/zset.md`.
use std::thread;
use zset::{Circuit, Input, Output, Rebuild, Stream, ZSet};
type Z<K> = ZSet<K>;
fn is_send<T: Send>() {}
#[test]
fn a_circuit_and_every_handle_it_hands_out_is_send() {
is_send::<Circuit>();
is_send::<Stream<Z<u8>>>();
is_send::<Input<u8>>();
is_send::<Output<u8>>();
is_send::<Rebuild<u8>>();
}
type Outs = (Output<(u8, i64)>, Output<(u8, u8)>);
fn delta(n: u8) -> Z<(u8, u8)> {
[((n % 5, n), 1), (((n + 1) % 5, n / 2), -1)]
.into_iter()
.collect()
}
/// The pipeline's outputs after `ticks` steps, collecting what `take` returns each time.
fn collect(c: &mut Circuit, i: &Input<(u8, u8)>, outs: &Outs, n: u8) -> String {
c.push(i, delta(n));
c.step();
format!("{:?} {:?}", c.take(&outs.0), c.take(&outs.1))
}
#[test]
fn a_circuit_built_on_one_thread_steps_on_another_and_returns() {
let build = || {
let mut c = Circuit::new();
let (input, s) = c.input::<(u8, u8)>();
let i = c.integrate(&s);
let d = c.distinct(&i);
let g = c.aggregate(&d, |_, vals| {
Some(vals.iter().map(|(v, w)| i64::from(*v) * w).sum::<i64>())
});
let f = c.filter(&s, |(k, _)| k % 2 == 0);
let m = c.delay(&f);
let outs = (c.output(&g), c.output(&m));
(c, input, outs)
};
let (mut one, i1, o1) = build();
let want: Vec<String> = (0..9).map(|n| collect(&mut one, &i1, &o1, n)).collect();
let (mut c, input, outs) = build();
let mut got = Vec::new();
for n in 0..9 {
// Each tick runs on a fresh thread: the circuit is moved in and moved out again.
let (back, line) = thread::spawn(move || {
let line = collect(&mut c, &input, &outs, n);
(c, line)
})
.join()
.unwrap();
c = back;
got.push(line);
}
assert_eq!(got, want, "[threads] each tick on its own thread");
}
#[test]
fn a_transaction_begun_on_one_thread_is_rolled_back_on_another() {
let mut c = Circuit::new();
let (input, s) = c.input::<u8>();
let i = c.integrate(&s);
let out = c.output(&i);
c.push(&input, Z::singleton(1, 1));
c.step();
let before = (c.current(&i), c.take(&out));
c.begin();
c.push(&input, Z::singleton(2, 1));
c.step();
let c = thread::spawn(move || {
let mut c = c;
c.rollback();
c
})
.join()
.unwrap();
let mut c = c;
assert_eq!(c.current(&i), before.0, "[txn] the stream is as it was");
assert_eq!(
c.take(&out),
Z::new(),
"[txn] the sink holds nothing of the step"
);
assert_eq!(c.ticks(), 1);
}
trait AmbiguousIfSync<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
struct IsSync;
impl<T: ?Sized + Sync> AmbiguousIfSync<IsSync> for T {}
// Fails to build (E0283) if `Circuit` becomes `Sync`.
#[test]
fn a_circuit_is_not_sync() {
let _ = <Circuit as AmbiguousIfSync<_>>::some_item;
}
type Site = fn(&mut Circuit, &Input<u8>, &Stream<Z<u8>>, &Output<u8>);
/// Two circuits of one shape, so a foreign handle indexes a live slot of the same type.
fn twin() -> (Circuit, Input<u8>, Stream<Z<u8>>, Output<u8>) {
let mut c = Circuit::new();
let (i, s) = c.input::<u8>();
let o = c.output(&s);
(c, i, s, o)
}
fn panic_text(f: impl FnOnce() + std::panic::UnwindSafe) -> Option<String> {
let e = std::panic::catch_unwind(f).err()?;
e.downcast_ref::<String>()
.cloned()
.or_else(|| e.downcast_ref::<&str>().map(|s| s.to_string()))
}
#[test]
fn every_call_that_takes_a_handle_refuses_a_foreign_one() {
let sites: [(&str, Site); 10] = [
("current", |c, _, s, _| {
let _ = c.current(s);
}),
("take", |c, _, _, o| drop(c.take(o))),
("push", |c, i, _, _| c.push(i, Z::new())),
("unary (map)", |c, _, s, _| {
let _ = c.map(s, |k| *k);
}),
("binary, first operand", |c, _, s, _| {
let (_, own) = c.input::<u8>();
let _ = c.plus(s, &own);
}),
("binary, second operand", |c, _, s, _| {
let (_, own) = c.input::<u8>();
let _ = c.plus(&own, s);
}),
("output", |c, _, s, _| {
let _ = c.output(s);
}),
("delay", |c, _, s, _| {
let _ = c.delay(s);
}),
("integrate", |c, _, s, _| {
let _ = c.integrate(s);
}),
("distinct", |c, _, s, _| {
let _ = c.distinct(s);
}),
];
for (name, site) in sites {
let (mut mine, ..) = twin();
let (_, fi, fs, fo) = twin();
let text = panic_text(std::panic::AssertUnwindSafe(|| {
site(&mut mine, &fi, &fs, &fo)
}));
assert!(
text.is_some_and(|t| t.contains("handle from another circuit")),
"[foreign] {name} accepted a handle from another circuit"
);
}
}