thread_timer 0.3.0

A simple, cancelable timer with no external dependencies
Documentation
use std::sync::mpsc::{self, Sender, TryRecvError};
use std::thread;
use std::time::Duration;

use thread_timer::*;

fn get_test_thunk(sender: &Sender<bool>) -> impl FnOnce() + Send + 'static {
  let sender_clone = sender.clone();
  move || {
    sender_clone.send(true).unwrap();
  }
}

#[test]
fn executes_thunk_after_wait() {
  let timer = ThreadTimer::new();
  let (sender, receiver) = mpsc::channel();
  let f = get_test_thunk(&sender);
  assert_eq!(timer.start(Duration::from_millis(50), f), Ok(()));
  assert_eq!(receiver.try_recv(), Err(TryRecvError::Empty));
  thread::sleep(Duration::from_millis(60));
  assert_eq!(receiver.try_recv(), Ok(true));
}

#[test]
fn can_be_reused() {
  let timer = ThreadTimer::new();
  let (sender, receiver) = mpsc::channel();
  let f1 = get_test_thunk(&sender);
  assert_eq!(timer.start(Duration::from_millis(50), f1), Ok(()));
  thread::sleep(Duration::from_millis(60));
  assert_eq!(receiver.try_recv(), Ok(true));

  let f2 = get_test_thunk(&sender);
  assert_eq!(timer.start(Duration::from_millis(50), f2), Ok(()));
  thread::sleep(Duration::from_millis(60));
  assert_eq!(receiver.try_recv(), Ok(true));
}

#[test]
fn cannot_cancel_if_not_waiting() {
  let timer = ThreadTimer::new();
  assert_eq!(timer.cancel(), Err(TimerCancelError::NotWaiting));
}

#[test]
fn thunk_does_not_run_if_canceled() {
  let timer = ThreadTimer::new();
  let (sender, receiver) = mpsc::channel();
  let f = get_test_thunk(&sender);
  assert_eq!(timer.start(Duration::from_millis(50), f), Ok(()));
  thread::sleep(Duration::from_millis(10));
  assert_eq!(timer.cancel(), Ok(()));
  thread::sleep(Duration::from_millis(75));
  assert_eq!(receiver.try_recv(), Err(TryRecvError::Empty));
}

#[test]
fn does_not_deadlock_when_canceling_long_task() {
  // TODO(greg): put a timeout on this test so that we get a failure rather than a hang
  let timer = ThreadTimer::new();
  let (sender, _) = mpsc::channel();
  let f = move || {
    thread::sleep(Duration::from_secs(10));
    // This send should fail because this thunk will only be run after the test
    // has been completed and the receiver dropped
    sender.send(true).unwrap_err();
  };
  assert_eq!(timer.start(Duration::from_millis(10), f), Ok(()));
  thread::sleep(Duration::from_millis(20));
  assert_eq!(timer.cancel(), Err(TimerCancelError::NotWaiting));
}

#[test]
fn can_start_cancel_start() {
  let timer = ThreadTimer::new();
  let (sender, receiver) = mpsc::channel();
  let f1 = get_test_thunk(&sender);
  assert_eq!(timer.start(Duration::from_millis(50), f1), Ok(()));
  thread::sleep(Duration::from_millis(10));
  assert_eq!(timer.cancel(), Ok(()));
  thread::sleep(Duration::from_millis(75));
  assert_eq!(receiver.try_recv(), Err(TryRecvError::Empty));
  let f2 = get_test_thunk(&sender);
  assert_eq!(timer.start(Duration::from_millis(50), f2), Ok(()));
  thread::sleep(Duration::from_millis(60));
  assert_eq!(receiver.try_recv(), Ok(true))
}

#[test]
fn can_start_immediately_after_cancel() {
  let timer = ThreadTimer::new();
  let (sender, receiver) = mpsc::channel();
  let f1 = get_test_thunk(&sender);
  let f2 = get_test_thunk(&sender);
  assert_eq!(timer.start(Duration::from_millis(50), f1), Ok(()));
  thread::sleep(Duration::from_millis(10));
  assert_eq!(timer.cancel(), Ok(()));
  assert_eq!(timer.start(Duration::from_millis(50), f2), Ok(()));
  thread::sleep(Duration::from_millis(60));
  assert_eq!(receiver.try_recv(), Ok(true))
}