mod common;
use atap::{
Runtime, RuntimeError, TaskState,
sleep::{Sleep, SleepMode},
};
use common::{drain, report, take_a_run};
use std::{
thread,
time::{Duration, Instant},
};
#[test]
fn sleep_accuracy_vs_std_blocking() {
let _ = Runtime::init();
let duration = Duration::from_secs(1);
let handle = thread::spawn(move || {
println!("Running std ...\n");
let start = Instant::now();
thread::sleep(duration);
let elapsed = start.elapsed();
println!("std slept for: {:?}", elapsed);
elapsed
});
println!("Running atap ...");
let result = Runtime::block(Sleep::sleep(duration));
println!("Result: {:?}", result);
let std = handle.join().unwrap();
println!("\nDiff: {:?}", std - result);
println!(
"std error:{:?}\natap error:{:?}\n",
std - duration,
result - duration
)
}
#[test]
fn sleep_multi_threaded_blocking() {
let _ = Runtime::init();
let threads = 4;
(1..=threads).into_iter().for_each(|i| {
thread::spawn(move || {
let duration = Duration::from_secs(i);
let mode = match i % 2 == 0 {
true => SleepMode::Precise,
false => SleepMode::Relaxed,
};
let time = Runtime::block(Sleep::sleep(duration).mode(mode));
let error = time - duration;
println!("Thread {} slept for {:?}\n{:?} error\n", i, time, error);
});
});
Runtime::block(Sleep::sleep(Duration::from_secs(threads + 2)).mode(SleepMode::Relaxed));
}
#[test]
fn single_spawned_task() {
let _ = Runtime::init();
let handle = Runtime::task(Sleep::sleep(Duration::from_secs(2))).spawn();
if let Ok(time) = handle.join() {
println!("atap slept for: {:?}", time);
} else {
println!("Failed");
};
}
#[test]
fn duplicated_handles_both_join() {
let _ = Runtime::init();
let duration = Duration::from_millis(200);
let first = Runtime::task(Sleep::sleep(duration).mode(SleepMode::Relaxed)).spawn();
let second = first.clone();
let one = first.join().expect("first listener");
let two = second.join().expect("second listener");
println!("first got {:?}, second got {:?}", one, two);
assert_eq!(one, two, "both listeners read the same output");
assert!(one >= duration, "the task actually ran");
}
#[test]
fn take_invalidates_other_handles() {
let _ = Runtime::init();
let first =
Runtime::task(Sleep::sleep(Duration::from_millis(200)).mode(SleepMode::Relaxed)).spawn();
let second = first.clone();
let taken = first.take().expect("the value moves out once");
println!("took {:?}", taken);
assert_eq!(
second.join(),
Err(RuntimeError::AlreadyTaken),
"the second listener finds the value gone rather than blocking",
);
}
#[test]
fn cancelled_task_is_unreadable() {
let _ = Runtime::init();
let first =
Runtime::task(Sleep::sleep(Duration::from_secs(1)).mode(SleepMode::Relaxed)).spawn();
let second = first.clone();
first.cancel();
assert_eq!(
second.join(),
Err(RuntimeError::Cancelled),
"a cancelled task hands nothing out",
);
}
#[test]
fn try_join_says_why_rather_than_just_nothing() {
let _ = Runtime::init();
let handle =
Runtime::task(Sleep::sleep(Duration::from_secs(1)).mode(SleepMode::Relaxed)).spawn();
let watcher = handle.clone();
assert_eq!(
watcher.try_join(),
Err(RuntimeError::NotReady),
"a task still running hasn't failed, it just isn't finished",
);
handle.cancel();
assert_eq!(
watcher.try_join(),
Err(RuntimeError::Cancelled),
"a cancelled task says so rather than looking unfinished",
);
}
#[test]
fn join_with_timeout_gives_up_without_giving_up_the_handle() {
let _ = Runtime::init();
let duration = Duration::from_secs(1);
let handle = Runtime::task(Sleep::sleep(duration).mode(SleepMode::Relaxed)).spawn();
assert_eq!(
handle.join_with_timeout(Duration::from_millis(50)),
Err(RuntimeError::NotReady),
"nowhere near long enough, and it says so",
);
let slept = handle
.join_with_timeout(Duration::from_secs(5))
.expect("the second wait is long enough");
println!("gave up once, then waited and got {:?}", slept);
assert!(
slept >= duration,
"the task came back with {:?} for a {:?} sleep",
slept,
duration,
);
}
#[test]
fn repeating_runs_until_cancelled() {
let _ = Runtime::init();
let wanted = 20;
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(5)).mode(SleepMode::Relaxed))
.repeat()
.spawn();
for _ in 0..wanted {
take_a_run(&handle);
}
handle.clone().cancel();
assert_eq!(
handle.clone().take(),
Err(RuntimeError::Cancelled),
"a cancelled series hands nothing out",
);
thread::sleep(Duration::from_millis(100));
assert_eq!(
handle.take(),
Err(RuntimeError::Cancelled),
"the series carried on after being cancelled",
);
println!("{} runs through one handle, then cancelled", wanted);
}
#[test]
fn repeating_finishes_a_run_before_the_next() {
let _ = Runtime::init();
let duration = Duration::from_millis(50);
let runs: u32 = 5;
let handle = Runtime::task(Sleep::sleep(duration).mode(SleepMode::Relaxed))
.repeat()
.spawn();
take_a_run(&handle);
let started = Instant::now();
for _ in 0..runs {
take_a_run(&handle);
}
let elapsed = started.elapsed();
handle.clone().cancel();
let floor = duration * (runs - 1);
println!(
"{} runs of {:?} took {:?}, floor {:?}",
runs, duration, elapsed, floor
);
assert!(
elapsed >= floor,
"{} runs of {:?} took {:?}, so they were overlapping",
runs,
duration,
elapsed,
);
}
#[test]
fn repeat_every_waits_between_runs() {
let _ = Runtime::init();
let interval = Duration::from_millis(50);
let runs: u32 = 5;
let handle = Runtime::task(Sleep::sleep(Duration::from_nanos(1)))
.repeat()
.every(interval)
.spawn();
take_a_run(&handle);
let started = Instant::now();
for _ in 0..runs {
take_a_run(&handle);
}
let elapsed = started.elapsed();
handle.clone().cancel();
let floor = interval * (runs - 1);
println!(
"{} runs {:?} apart took {:?}, floor {:?}",
runs, interval, elapsed, floor,
);
assert!(
elapsed >= floor,
"{} runs {:?} apart took only {:?}",
runs,
interval,
elapsed,
);
}
#[test]
fn every_starts_runs_on_the_interval() {
let _ = Runtime::init();
let interval = Duration::from_millis(50);
let runs: u32 = 5;
let handle = Runtime::task(Sleep::sleep(Duration::from_nanos(1)))
.at_rate(interval)
.spawn();
take_a_run(&handle);
let started = Instant::now();
for _ in 0..runs {
take_a_run(&handle);
}
let elapsed = started.elapsed();
handle.clone().cancel();
let floor = interval * (runs - 1);
println!(
"{} runs on a {:?} period took {:?}, floor {:?}",
runs, interval, elapsed, floor,
);
assert!(
elapsed >= floor,
"{} runs on a {:?} period took only {:?}",
runs,
interval,
elapsed,
);
}
#[test]
fn every_ends_the_whole_series_on_cancel() {
let _ = Runtime::init();
let handle = Runtime::task(Sleep::sleep(Duration::from_nanos(1)))
.at_rate(Duration::from_millis(5))
.spawn();
for _ in 0..10 {
take_a_run(&handle);
}
handle.clone().cancel();
assert_eq!(
handle.clone().take(),
Err(RuntimeError::Cancelled),
"a cancelled schedule hands nothing out",
);
thread::sleep(Duration::from_millis(200));
assert_eq!(
handle.take(),
Err(RuntimeError::Cancelled),
"the schedule carried on after being cancelled",
);
}
#[test]
fn many_one_by_one_tasks() {
let _ = Runtime::init();
let tasks = 1_000_000;
let mut avg = 0.0;
for _ in 0..tasks {
let handle = Runtime::task(Sleep::sleep(Duration::from_nanos(500))).spawn();
if let Ok(time) = handle.join() {
avg += time.as_nanos() as f32;
}
}
avg /= tasks as f32;
println!("Average time: {}", avg);
}
#[test]
fn survives_losing_its_manager() {
let _ = Runtime::init();
let tasks = 200_000;
let quick = || Sleep::sleep(Duration::from_nanos(1));
let started = Instant::now();
let before: Vec<_> = (0..tasks).map(|_| Runtime::task(quick()).spawn()).collect();
Runtime::inject_manager_faults(3);
let during: Vec<_> = (0..tasks).map(|_| Runtime::task(quick()).spawn()).collect();
let mut finished = 0u64;
for handle in before.into_iter().chain(during) {
handle
.join()
.expect("every task finishes with no manager to help it");
finished += 1;
}
report("manager back");
let timed = Runtime::task(quick())
.repeat()
.every(Duration::from_millis(20))
.spawn();
for _ in 0..3 {
take_a_run(&timed);
}
timed.cancel();
println!(
"{} tasks through a pool that lost its manager three times in {:?}, and timers after",
finished,
started.elapsed(),
);
}
#[test]
fn try_take_polls_without_committing() {
let _ = Runtime::init();
let handle =
Runtime::task(Sleep::sleep(Duration::from_millis(200)).mode(SleepMode::Relaxed)).spawn();
assert_eq!(
handle.try_take(),
Err(RuntimeError::NotReady),
"a task still running hasn't failed, it just isn't finished",
);
handle.wait().expect("the task settles");
let taken = handle.try_take().expect("the value moves out once");
println!("took {taken:?}");
assert_eq!(
handle.try_take(),
Err(RuntimeError::AlreadyTaken),
"only one caller ever gets the output, however it is asked for",
);
}
#[test]
fn take_with_timeout_costs_nothing_when_it_gives_up() {
let _ = Runtime::init();
let handle =
Runtime::task(Sleep::sleep(Duration::from_millis(300)).mode(SleepMode::Relaxed)).spawn();
assert_eq!(
handle.take_with_timeout(Duration::from_millis(20)),
Err(RuntimeError::NotReady),
"ran out of patience before the task ran out of work",
);
let taken = handle
.take_with_timeout(Duration::from_secs(5))
.expect("the output survived the caller giving up on it");
println!("took {taken:?} on the second ask");
}
#[test]
fn wait_settles_without_consuming_or_reading() {
let _ = Runtime::init();
let handle =
Runtime::task(Sleep::sleep(Duration::from_millis(50)).mode(SleepMode::Relaxed)).spawn();
let state = handle.wait().expect("the task settles");
assert_eq!(state, TaskState::Ready, "it finished, so it has an output");
assert!(handle.is_ready(), "and the handle agrees");
handle.join().expect("the output is still there to be had");
}
#[test]
fn state_and_predicates_agree() {
let _ = Runtime::init();
let ready =
Runtime::task(Sleep::sleep(Duration::from_millis(20)).mode(SleepMode::Relaxed)).spawn();
ready.wait().expect("it settles");
assert_eq!(ready.state(), TaskState::Ready);
assert!(ready.is_ready() && ready.settled());
let cancelled =
Runtime::task(Sleep::sleep(Duration::from_secs(5)).mode(SleepMode::Relaxed)).spawn();
cancelled.clone().cancel();
assert_eq!(cancelled.state(), TaskState::Cancelled);
assert!(cancelled.is_cancelled() && cancelled.settled());
assert!(
!cancelled.is_ready(),
"a cancelled task has settled and has nothing to give",
);
let taken =
Runtime::task(Sleep::sleep(Duration::from_millis(20)).mode(SleepMode::Relaxed)).spawn();
let watcher = taken.clone();
taken.take().expect("the value moves out");
assert_eq!(watcher.state(), TaskState::Taken);
assert!(watcher.is_taken());
for handle in [&ready, &cancelled, &watcher] {
assert_ne!(
handle.state(),
TaskState::Free,
"a live handle is never free"
);
}
}
#[test]
fn builder_repeats_at_a_priority() {
let _ = Runtime::init();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(10)).mode(SleepMode::Relaxed))
.priority(200)
.repeat()
.spawn();
let first = handle.take_with_timeout(Duration::from_secs(5));
let deadline = Instant::now() + Duration::from_secs(5);
let mut second = Err(RuntimeError::AlreadyTaken);
while Instant::now() < deadline {
second = handle.take_with_timeout(Duration::from_millis(100));
if second.is_ok() {
break;
}
}
handle.cancel();
assert!(first.is_ok(), "the first run publishes: {first:?}");
assert!(
second.is_ok(),
"a later read succeeds where a one shot would stay AlreadyTaken: {second:?}",
);
}
#[test]
fn handles_compare_and_hash_on_the_task() {
use std::collections::HashSet;
let _ = Runtime::init();
let handle =
Runtime::task(Sleep::sleep(Duration::from_millis(20)).mode(SleepMode::Relaxed)).spawn();
let same = handle.clone();
let other =
Runtime::task(Sleep::sleep(Duration::from_millis(20)).mode(SleepMode::Relaxed)).spawn();
assert_eq!(handle, same, "a clone points at the same task");
assert_ne!(handle.id(), other.id(), "two spawns are two tasks");
let mut seen = HashSet::new();
assert!(seen.insert(handle.clone()));
assert!(!seen.insert(same), "the same task doesn't go in twice");
assert!(seen.insert(other.clone()));
handle.join().expect("still finishes");
other.join().expect("still finishes");
}
#[test]
fn join_all_keeps_the_order_it_was_given() {
let _ = Runtime::init();
let handles: Vec<_> = (1..=5)
.map(|step| {
Runtime::task(Sleep::sleep(Duration::from_millis(step * 10)).mode(SleepMode::Relaxed))
.spawn()
})
.collect();
let results = Runtime::join_all(handles);
assert_eq!(results.len(), 5, "one result per task");
for (index, result) in results.into_iter().enumerate() {
let slept = result.expect("every task finishes");
let asked = Duration::from_millis((index as u64 + 1) * 10);
assert!(
slept >= asked,
"result {index} slept {slept:?} against {asked:?}, so the order moved",
);
}
}
#[test]
fn status_reports_a_live_runtime() {
let _ = Runtime::init();
let status = Runtime::status();
println!("{status}");
assert!(Runtime::initialised(), "init has finished");
assert!(status.initialised());
assert!(!status.shut_down(), "nothing has shut this down");
assert!(status.reactor_alive(), "the reactor is up");
assert!(status.manager_alive(), "the manager is up");
assert!(status.healthy());
}
#[test]
fn after_waits_before_it_runs() {
let _ = Runtime::init();
let delay = Duration::from_millis(200);
let started = Instant::now();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(10)).mode(SleepMode::Relaxed))
.after(delay)
.spawn();
assert!(!handle.settled(), "nowhere near the delay being up");
assert_eq!(
handle.try_join(),
Err(RuntimeError::NotReady),
"a task waiting out a delay hasn't failed, it just hasn't started",
);
let state = handle.wait().expect("it runs once the delay is up");
let waited = started.elapsed();
println!("ran after {waited:?} of a {delay:?} delay");
assert_eq!(state, TaskState::Ready, "it ran and published");
assert!(
waited >= delay,
"started after only {waited:?}, which is early",
);
}
#[test]
fn many_delayed_tasks_cost_no_threads() {
let _ = Runtime::init();
let delay = Duration::from_millis(300);
let started = Instant::now();
let handles: Vec<_> = (0..2_000)
.map(|_| {
Runtime::task(Sleep::sleep(Duration::from_micros(50)).mode(SleepMode::Relaxed))
.after(delay)
.spawn()
})
.collect();
for (task, handle) in handles.into_iter().enumerate() {
handle
.join()
.unwrap_or_else(|error| panic!("delayed task {task} never ran: {error}"));
}
let total = started.elapsed();
println!("2000 delayed tasks all ran, {total:?} against a {delay:?} delay");
assert!(
total >= delay,
"they can't all have waited their delay in {total:?}",
);
}
#[test]
fn builder_after_delays_too() {
let _ = Runtime::init();
let delay = Duration::from_millis(150);
let started = Instant::now();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(10)).mode(SleepMode::Relaxed))
.priority(200)
.after(delay)
.spawn();
handle.wait().expect("it runs once the delay is up");
let waited = started.elapsed();
println!("the built one ran after {waited:?} of a {delay:?} delay");
assert!(waited >= delay, "started after only {waited:?}");
handle.take().expect("the output is there");
}
#[test]
fn delay_and_repeat_compose() {
let _ = Runtime::init();
let delay = Duration::from_millis(200);
let gap = Duration::from_millis(20);
let started = Instant::now();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(1)).mode(SleepMode::Relaxed))
.after(delay)
.repeat()
.every(gap)
.spawn();
handle
.wait()
.expect("the first run happens once the delay is up");
let first = started.elapsed();
println!("first run after {first:?} of a {delay:?} delay");
handle.cancel();
assert!(
first >= delay,
"the first run came after {first:?}, so the delay was lost when the kind was set",
);
}
#[test]
fn until_in_the_past_runs_once() {
let _ = Runtime::init();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(1)).mode(SleepMode::Relaxed))
.repeat()
.until(Instant::now())
.spawn();
let seen = drain(&handle, Duration::from_secs(5));
println!("a deadline already past ran {seen} time(s)");
assert!(handle.is_finished());
assert_eq!(seen, 1, "the first run is never the one that is refused");
}
#[test]
fn at_rate_starts_exactly_its_count() {
let _ = Runtime::init();
let runs = 4;
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(1)).mode(SleepMode::Relaxed))
.at_rate(Duration::from_millis(40))
.count(runs)
.spawn();
let seen = drain(&handle, Duration::from_secs(10));
println!("a schedule bounded to {runs} published {seen} times");
assert!(
handle.is_finished(),
"the schedule never reported finishing"
);
assert!(
seen > 0 && seen as u32 <= runs,
"saw {seen} outputs from {runs} runs",
);
}
#[test]
fn at_rate_waits_out_a_delay_before_its_first_run() {
let _ = Runtime::init();
let delay = Duration::from_millis(200);
let started = Instant::now();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(1)).mode(SleepMode::Relaxed))
.at_rate(Duration::from_millis(30))
.after(delay)
.spawn();
handle
.wait()
.expect("the first run happens once the delay is up");
let first = started.elapsed();
println!("the schedule's first run landed after {first:?} of a {delay:?} delay");
handle.cancel();
assert!(
first >= delay,
"the first run came after {first:?}, so the delay was never served",
);
}
#[test]
fn a_cancelled_bounded_repeat_is_finished() {
let _ = Runtime::init();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(10)).mode(SleepMode::Relaxed))
.repeat()
.every(Duration::from_millis(50))
.count(1_000)
.spawn();
let watcher = handle.clone();
handle.cancel();
assert_eq!(
watcher.clone().join(),
Err(RuntimeError::Cancelled),
"a cancelled series hands nothing out",
);
assert!(
watcher.is_finished(),
"a cancelled series is not going to run again",
);
watcher.cancel();
}
#[test]
fn an_unbounded_repeat_is_never_finished() {
let _ = Runtime::init();
let handle = Runtime::task(Sleep::sleep(Duration::from_millis(5)).mode(SleepMode::Relaxed))
.repeat()
.every(Duration::from_secs(60))
.spawn();
handle.wait().expect("a run publishes");
let settled = handle.settled();
let finished = handle.is_finished();
handle.cancel();
assert!(settled, "it published, so it has settled");
assert!(
!finished,
"but there is another run coming, so it isn't over"
);
}
#[test]
fn sleeping_until_a_moment_waits_for_it() {
let _ = Runtime::init();
let when = Instant::now() + Duration::from_millis(80);
let slept = Runtime::block(Sleep::until(when));
assert!(Instant::now() >= when, "woke before the moment");
assert!(slept >= Duration::from_millis(70), "slept only {slept:?}");
let when = Instant::now() + Duration::from_millis(80);
let handle = Runtime::task(Sleep::until(when).mode(SleepMode::Relaxed)).spawn();
assert!(handle.join().is_ok());
assert!(
Instant::now() >= when,
"a spawned sleep woke before the moment"
);
}
#[test]
fn sleeping_until_the_past_returns_at_once() {
let _ = Runtime::init();
let slept = Runtime::block(Sleep::until(Instant::now() - Duration::from_millis(5)));
assert!(
slept < Duration::from_secs(1),
"slept {slept:?} for a moment already gone"
);
}