atomic-time 1.0.0

Thread-safe atomic time types, lock-free where supported
Documentation
use core::time::Duration;
use std::time::SystemTime;

use ::proptest::{
  arbitrary::{any, Arbitrary},
  strategy::{BoxedStrategy, Just, Strategy},
};

use crate::{AtomicDuration, AtomicOptionDuration, AtomicOptionSystemTime, AtomicSystemTime};

fn duration_strategy() -> BoxedStrategy<Duration> {
  (any::<u64>(), 0..1_000_000_000u32)
    .prop_map(|(seconds, nanoseconds)| Duration::new(seconds, nanoseconds))
    .boxed()
}

fn option_duration_strategy() -> BoxedStrategy<Option<Duration>> {
  ::proptest::prop_oneof![Just(None), duration_strategy().prop_map(Some),].boxed()
}

fn system_time_from_duration(duration: Duration) -> SystemTime {
  SystemTime::UNIX_EPOCH
    .checked_add(duration)
    .unwrap_or(SystemTime::UNIX_EPOCH)
}

fn system_time_strategy() -> BoxedStrategy<SystemTime> {
  (0..=i32::MAX as u64, 0..1_000_000_000u32)
    .prop_map(|(seconds, nanoseconds)| {
      system_time_from_duration(Duration::new(seconds, nanoseconds))
    })
    .boxed()
}

fn option_system_time_strategy() -> BoxedStrategy<Option<SystemTime>> {
  ::proptest::prop_oneof![Just(None), system_time_strategy().prop_map(Some),].boxed()
}

impl Arbitrary for AtomicDuration {
  type Parameters = ();
  type Strategy = BoxedStrategy<Self>;

  fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
    duration_strategy().prop_map(Self::new).boxed()
  }
}

impl Arbitrary for AtomicOptionDuration {
  type Parameters = ();
  type Strategy = BoxedStrategy<Self>;

  fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
    option_duration_strategy().prop_map(Self::new).boxed()
  }
}

impl Arbitrary for AtomicSystemTime {
  type Parameters = ();
  type Strategy = BoxedStrategy<Self>;

  fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
    system_time_strategy().prop_map(Self::new).boxed()
  }
}

impl Arbitrary for AtomicOptionSystemTime {
  type Parameters = ();
  type Strategy = BoxedStrategy<Self>;

  fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
    option_system_time_strategy().prop_map(Self::new).boxed()
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use core::sync::atomic::Ordering;
  use std::cell::Cell;

  use ::proptest::{strategy::ValueTree, test_runner::TestRunner};

  #[test]
  fn strategies_generate_readable_values() {
    let mut runner = TestRunner::default();

    let duration = <AtomicDuration as Arbitrary>::arbitrary()
      .new_tree(&mut runner)
      .unwrap()
      .current();
    let _ = duration.load(Ordering::SeqCst);

    let option_duration = <AtomicOptionDuration as Arbitrary>::arbitrary()
      .new_tree(&mut runner)
      .unwrap()
      .current();
    let _ = option_duration.load(Ordering::SeqCst);

    let system_time = <AtomicSystemTime as Arbitrary>::arbitrary()
      .new_tree(&mut runner)
      .unwrap()
      .current();
    assert!(system_time
      .load(Ordering::SeqCst)
      .duration_since(SystemTime::UNIX_EPOCH)
      .is_ok());

    let option_system_time = <AtomicOptionSystemTime as Arbitrary>::arbitrary()
      .new_tree(&mut runner)
      .unwrap()
      .current();
    assert!(option_system_time
      .load(Ordering::SeqCst)
      .is_none_or(|value| value.duration_since(SystemTime::UNIX_EPOCH).is_ok()));
  }

  #[test]
  fn option_strategies_cover_none_and_some_without_panicking() {
    let duration_none = Cell::new(false);
    let duration_some = Cell::new(false);
    let duration_strategy = <AtomicOptionDuration as Arbitrary>::arbitrary();
    TestRunner::default()
      .run(&duration_strategy, |value| {
        match value.load(Ordering::SeqCst) {
          None => duration_none.set(true),
          Some(_) => duration_some.set(true),
        }
        Ok(())
      })
      .unwrap();
    assert!(duration_none.get());
    assert!(duration_some.get());

    let system_time_none = Cell::new(false);
    let system_time_some = Cell::new(false);
    let system_time_strategy = <AtomicOptionSystemTime as Arbitrary>::arbitrary();
    TestRunner::default()
      .run(&system_time_strategy, |value| {
        match value.load(Ordering::SeqCst) {
          None => system_time_none.set(true),
          Some(time) => {
            system_time_some.set(true);
            assert!(time.duration_since(SystemTime::UNIX_EPOCH).is_ok());
          }
        }
        Ok(())
      })
      .unwrap();
    assert!(system_time_none.get());
    assert!(system_time_some.get());
  }
}