mod common;
use atap::{Runtime, RuntimeError, compute::Compute};
use common::settles;
use std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
mpsc,
},
time::Duration,
};
const PATIENCE: Duration = Duration::from_secs(10);
#[test]
fn a_panicking_compute_fails() {
let _ = Runtime::init();
let doomed = Runtime::task(Compute::compute(|()| -> u32 {
panic!("a compute went down")
}))
.spawn();
assert_eq!(
doomed.join_with_timeout(PATIENCE),
Err(RuntimeError::TaskFailed)
);
let after: Vec<_> = (0..256u32)
.map(|value| Runtime::task(Compute::compute(move |()| value)).spawn())
.collect();
for (value, handle) in after.into_iter().enumerate() {
assert_eq!(handle.join_with_timeout(PATIENCE), Ok(value as u32));
}
}
#[test]
fn a_parent_sees_its_child_fail() {
let _ = Runtime::init();
let parent = Runtime::task(Compute::compute(|()| {
let child =
Runtime::task(Compute::compute(|()| -> u8 { panic!("a child went down") })).spawn();
match child.join() {
Err(RuntimeError::TaskFailed) => "handled",
_ => "missed",
}
}))
.spawn();
assert_eq!(parent.join_with_timeout(PATIENCE), Ok("handled"));
}
#[test]
fn a_panic_deep_in_a_split_fails_its_branch() {
let _ = Runtime::init();
fn split(depth: u32, doomed: u32) -> Result<u32, RuntimeError> {
if depth == 0 {
if doomed == 0 {
panic!("a leaf went down");
}
return Ok(1);
}
let left = Runtime::task(Compute::compute(move |()| split(depth - 1, doomed))).spawn();
let right = Runtime::task(Compute::compute(move |()| split(depth - 1, u32::MAX))).spawn();
let left = left.join().and_then(|inner| inner);
let right = right.join().and_then(|inner| inner);
Ok(left.unwrap_or(0) + right?)
}
let intact = Runtime::task(Compute::compute(|()| split(8, u32::MAX))).spawn();
let broken = Runtime::task(Compute::compute(|()| split(8, 0))).spawn();
assert_eq!(intact.join_with_timeout(PATIENCE), Ok(Ok(256)));
assert_eq!(broken.join_with_timeout(PATIENCE), Ok(Ok(255)));
}
#[test]
fn a_repeat_that_panics_ends() {
let _ = Runtime::init();
let runs = Arc::new(AtomicUsize::new(0));
let counted = Arc::clone(&runs);
let handle = Runtime::task(Compute::compute(move |()| {
if counted.fetch_add(1, Ordering::SeqCst) == 3 {
panic!("the fourth run went down");
}
}))
.repeat()
.every(Duration::from_millis(2))
.spawn();
assert!(
settles(|| handle.is_finished()),
"a repeat kept going after a run panicked"
);
assert_eq!(handle.try_join(), Err(RuntimeError::TaskFailed));
std::thread::sleep(Duration::from_millis(50));
assert_eq!(
runs.load(Ordering::SeqCst),
4,
"a repeat ran again after it failed"
);
}
#[test]
fn a_waiting_task_that_panics_refuses_later_gives() {
let _ = Runtime::init();
let handle = Runtime::task(Compute::compute(|value: i32| {
if value < 0 {
panic!("given a negative");
}
value
}))
.wait_for::<i32>()
.spawn();
handle.give(-1).expect("the first give was refused");
assert_eq!(
handle.join_with_timeout(PATIENCE),
Err(RuntimeError::TaskFailed)
);
assert!(
settles(|| handle.give(1).is_err()),
"a failed waiting task still took gives"
);
}
#[test]
fn a_receive_from_a_panicking_source_finishes() {
let _ = Runtime::init();
let source = Runtime::task(Compute::compute(|()| -> u32 {
panic!("the source went down")
}))
.spawn();
let (ran, runs) = mpsc::channel();
let receiver = Runtime::task(Compute::compute(move |value: u32| {
let _ = ran.send(value);
}))
.receive(source.clone())
.spawn();
assert!(
settles(|| receiver.is_finished()),
"a receive outlived a source that could never publish"
);
assert!(
runs.try_recv().is_err(),
"the receive ran with a value a failed source never made"
);
}
#[test]
fn a_panicking_receive_leaves_its_source_alone() {
let _ = Runtime::init();
let source = Runtime::task(Compute::compute(|()| 12u32)).spawn();
let receiver = Runtime::task(Compute::compute(|_: u32| -> u32 {
panic!("the receive went down")
}))
.receive(source.clone())
.count(1)
.spawn();
assert_eq!(
receiver.join_with_timeout(PATIENCE),
Err(RuntimeError::TaskFailed)
);
assert_eq!(source.join_with_timeout(PATIENCE), Ok(12));
}
#[test]
fn give_to_skips_the_runs_that_panicked() {
let _ = Runtime::init();
let (ran, runs) = mpsc::channel();
let sink = Runtime::task(Compute::compute(move |value: u32| {
let _ = ran.send(value);
}))
.wait_for::<u32>()
.spawn();
let sources: Vec<_> = (0..16u32)
.map(|value| {
Runtime::task(Compute::compute(move |()| {
if value % 4 == 0 {
panic!("source {} went down", value);
}
value
}))
.give_to(&sink)
.spawn()
})
.collect();
for handle in sources {
let _ = handle.join_with_timeout(PATIENCE);
}
std::thread::sleep(Duration::from_millis(100));
let seen: Vec<u32> = runs.try_iter().collect();
assert!(
!seen.is_empty(),
"no source that succeeded reached the sink"
);
assert!(
seen.iter().all(|value| value % 4 != 0),
"the sink ran with a value from a source that panicked: {:?}",
seen
);
sink.cancel();
}
#[test]
fn a_flood_of_panics_leaves_everything_else_right() {
let _ = Runtime::init();
let handles: Vec<_> = (0..2_000u32)
.map(|value| {
Runtime::task(Compute::compute(move |()| {
if value % 7 == 0 {
panic!("value {} went down", value);
}
value
}))
.spawn()
})
.collect();
for (value, handle) in handles.into_iter().enumerate() {
let value = value as u32;
match value % 7 {
0 => assert_eq!(
handle.join_with_timeout(PATIENCE),
Err(RuntimeError::TaskFailed)
),
_ => assert_eq!(handle.join_with_timeout(PATIENCE), Ok(value)),
}
}
}