mod common;
use atap::{JoinPolicy, Runtime, RuntimeError, TaskHandle, TaskState, fs::File};
use common::sleeping;
use std::{
fs,
path::PathBuf,
sync::atomic::{AtomicUsize, Ordering},
thread,
time::Duration,
};
static NEXT: AtomicUsize = AtomicUsize::new(0);
struct TestFile(PathBuf);
impl TestFile {
fn new(tag: &str, body: &[u8]) -> Self {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/files");
fs::create_dir_all(&root).expect("could not make tests/files");
let path = root.join(format!(
"join-{}-{}-{}.txt",
tag,
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed),
));
fs::write(&path, body).expect("could not write the fixture");
Self(path)
}
fn path(&self) -> &PathBuf {
&self.0
}
}
impl Drop for TestFile {
fn drop(&mut self) {
let _ = fs::remove_file(&self.0);
}
}
#[test]
fn cancel_stops_the_losers() {
let _ = Runtime::init();
let quick = sleeping(5);
let slow: Vec<_> = (0..3).map(|_| sleeping(4000)).collect();
let watching: Vec<_> = slow.to_vec();
let (first, rest) = Runtime::join_first(std::iter::once(quick).chain(slow), JoinPolicy::Cancel);
assert!(rest.is_none(), "Cancel should not hand the losers back");
assert!(first.settled(), "the winner should be settled");
for handle in &watching {
let state = handle.wait().expect("a cancelled task still settles");
assert_eq!(state, TaskState::Cancelled, "a loser was not cancelled");
assert!(
handle.is_cancelled(),
"is_cancelled disagrees with the state"
);
}
}
#[test]
fn drop_leaves_the_losers_running() {
let _ = Runtime::init();
let quick = sleeping(5);
let slow: Vec<_> = (0..3).map(|_| sleeping(200)).collect();
let watching: Vec<_> = slow.to_vec();
let (first, rest) = Runtime::join_first(std::iter::once(quick).chain(slow), JoinPolicy::Drop);
assert!(rest.is_none(), "Drop should not hand the losers back");
assert!(first.settled(), "the winner should be settled");
for handle in &watching {
let state = handle.wait().expect("a dropped loser still settles");
assert_ne!(state, TaskState::Cancelled, "a dropped loser was cancelled");
assert!(
handle.is_ready() || handle.is_taken(),
"a dropped loser never finished"
);
}
}
#[test]
fn an_empty_set_gives_back_a_dead_handle() {
let _ = Runtime::init();
let (first, rest) = Runtime::join_first(Vec::<TaskHandle<Duration>>::new(), JoinPolicy::Cancel);
assert!(rest.is_none(), "Cancel should not hand anything back");
assert_eq!(
first.try_join(),
Err(RuntimeError::NoSuchTask),
"a dead handle should refuse rather than wait",
);
let (_, rest) = Runtime::join_first(Vec::<TaskHandle<Duration>>::new(), JoinPolicy::PassBack);
assert_eq!(
rest.map(|losers| losers.len()),
Some(0),
"PassBack over nothing should hand back nothing, not None",
);
}
#[test]
fn a_set_of_one_is_just_a_join() {
let _ = Runtime::init();
let only = sleeping(20);
let only_id = only.id();
let (first, rest) = Runtime::join_first(vec![only], JoinPolicy::PassBack);
assert_eq!(first.id(), only_id, "the only task should have won");
assert_eq!(
rest.map(|losers| losers.len()),
Some(0),
"there are no losers"
);
first.join().expect("the winner still has its output");
}
#[test]
fn file_reads_race_each_other() {
let _ = Runtime::init();
let small = TestFile::new("small", b"quick");
let large = TestFile::new("large", &vec![b'x'; 8 * 1024 * 1024]);
let handles = vec![
Runtime::task(File::read(small.path())).spawn(),
Runtime::task(File::read(large.path())).spawn(),
Runtime::task(File::read(large.path())).spawn(),
];
let (first, rest) = Runtime::join_first(handles, JoinPolicy::PassBack);
let winner = first
.join()
.expect("the winner joins")
.expect("the read worked");
println!("the winning read was {} bytes", winner.len());
for handle in rest.expect("PassBack hands the losers back") {
handle
.join()
.expect("a loser joins")
.expect("the read worked");
}
}
#[test]
fn a_race_from_several_threads_at_once() {
let _ = Runtime::init();
let shared: Vec<_> = (0..4).map(|_| sleeping(60)).collect();
let crews: Vec<_> = (0..2)
.map(|_| {
let mine: Vec<_> = shared.to_vec();
thread::spawn(move || {
let (first, rest) = Runtime::join_first(mine, JoinPolicy::PassBack);
assert!(first.settled(), "the winner should be settled");
assert_eq!(
rest.map(|losers| losers.len()),
Some(3),
"wrong loser count"
);
first.id()
})
})
.collect();
for crew in crews {
crew.join().expect("a racing thread went down");
}
for handle in shared {
handle.join().expect("every task still finishes");
}
}