tempa 0.1.9

A no_std temporal system for animation and simulation, providing time, duration, time ranges, frame rates, and deterministic frame-index stepping.
Documentation
#![cfg(test)]

use rinia::{
    Scalarf,
    numeric::{BoundedMax, BoundedMin, MinMax, Zero},
};

use crate::{Duration, Error, HasDuration};

mod construction {
    use super::*;

    #[test]
    fn new_and_seconds_roundtrip() {
        let d = Duration::new(1.25_f32);
        assert_eq!(d.seconds(), 1.25_f32);
        assert_eq!(d.into_seconds(), 1.25_f32);
    }

    #[test]
    fn from_and_into_conversions() {
        let d = Duration::from(2.5_f32);
        let s: Scalarf = d.into();
        assert_eq!(s, 2.5_f32);
    }

    #[test]
    fn try_from_seconds_accepts_finite_values() {
        assert_eq!(Duration::try_from_seconds(0.0_f32), Ok(Duration::new(0.0_f32)));
        assert_eq!(Duration::try_from_seconds(-3.0_f32), Ok(Duration::new(-3.0_f32)));
    }

    #[test]
    fn try_from_seconds_rejects_non_finite_values() {
        assert!(matches!(Duration::try_from_seconds(Scalarf::NAN), Err(Error::InvalidValue(_))));
        assert!(matches!(
            Duration::try_from_seconds(Scalarf::INFINITY),
            Err(Error::InvalidValue(_))
        ));
        assert!(matches!(
            Duration::try_from_seconds(Scalarf::NEG_INFINITY),
            Err(Error::InvalidValue(_))
        ));
    }

    #[test]
    #[should_panic(expected = "Duration cannot be created from NaN or infinite values")]
    fn from_seconds_panics_for_non_finite() {
        let _ = Duration::from_seconds(Scalarf::NAN);
    }
}

mod numeric {
    use super::*;

    #[test]
    fn zero_and_default_are_consistent() {
        assert_eq!(Duration::<Scalarf>::ZERO, Duration::new(0.0_f32));
        assert_eq!(Duration::<Scalarf>::default(), Duration::<Scalarf>::ZERO);
        assert_eq!(<Duration<Scalarf> as Zero>::ZERO, Duration::<Scalarf>::ZERO);
    }

    #[test]
    fn bounded_values_match_underlying_type() {
        assert_eq!(Duration::<Scalarf>::MIN.seconds(), Scalarf::MIN);
        assert_eq!(Duration::<Scalarf>::MAX.seconds(), Scalarf::MAX);
        assert_eq!(<Duration<Scalarf> as BoundedMin>::MIN, Duration::<Scalarf>::MIN);
        assert_eq!(<Duration<Scalarf> as BoundedMax>::MAX, Duration::<Scalarf>::MAX);
    }

    #[test]
    fn min_max_and_is_finite_behave_as_expected() {
        let a = Duration::new(1.0_f32);
        let b = Duration::new(2.0_f32);

        assert_eq!(a.min(b), a);
        assert_eq!(a.max(b), b);
        assert_eq!(a.minimum(b), a);
        assert_eq!(a.maximum(b), b);

        assert!(a.is_finite());
        assert!(!Duration::new(Scalarf::NEG_INFINITY).is_finite());
    }
}

mod ops {
    use super::*;

    #[test]
    fn add_sub_with_durations() {
        let a = Duration::new(10.0_f32);
        let b = Duration::new(2.5_f32);

        assert_eq!(a + b, Duration::new(12.5_f32));
        assert_eq!(a - b, Duration::new(7.5_f32));
    }

    #[test]
    fn add_sub_with_scalars() {
        let a = Duration::new(10.0_f32);

        assert_eq!(a + 1.5_f32, Duration::new(11.5_f32));
        assert_eq!(a - 3.0_f32, Duration::new(7.0_f32));
    }

    #[test]
    fn add_assign_and_sub_assign() {
        let mut d = Duration::new(3.0_f32);
        d += Duration::new(2.0_f32);
        assert_eq!(d, Duration::new(5.0_f32));

        d -= Duration::new(1.5_f32);
        assert_eq!(d, Duration::new(3.5_f32));

        d += 1.0_f32;
        assert_eq!(d, Duration::new(4.5_f32));

        d -= 0.5_f32;
        assert_eq!(d, Duration::new(4.0_f32));
    }

    #[test]
    fn multiply_divide_and_ratio() {
        let d = Duration::new(8.0_f32);

        assert_eq!(d / Duration::new(2.0_f32), 4.0_f32);
        assert_eq!(d / 4.0_f32, Duration::new(2.0_f32));
        assert_eq!(d * 1.5_f32, Duration::new(12.0_f32));
        assert_eq!(2.0_f32 * d, Duration::new(16.0_f32));
    }

    #[test]
    fn multiply_assign_and_divide_assign() {
        let mut d = Duration::new(9.0_f32);
        d *= 2.0_f32;
        assert_eq!(d, Duration::new(18.0_f32));

        d /= 3.0_f32;
        assert_eq!(d, Duration::new(6.0_f32));
    }
}

mod traits {
    use super::*;

    #[test]
    fn has_duration_for_duration_returns_self() {
        let d = Duration::new(3.25_f32);
        assert_eq!(HasDuration::duration(&d), d);
    }
}

mod approx {
    use rinia::{
        assert_approx_eq_abs_tol, assert_approx_eq_rel_tol, assert_approx_ne_abs_tol,
        assert_approx_ne_rel_tol,
    };

    use super::*;

    #[test]
    fn approx_eq_abs_is_forwarded_to_inner_seconds() {
        let a = Duration::new(1.0_f32);
        let b = Duration::new(1.0008_f32);

        assert_approx_eq_abs_tol!(a, b, 0.001_f32);
        assert_approx_ne_abs_tol!(a, b, 0.0001_f32);
    }

    #[test]
    fn approx_eq_rel_is_forwarded_to_inner_seconds() {
        let a = Duration::new(100.0_f32);
        let b = Duration::new(100.4_f32);

        assert_approx_eq_rel_tol!(a, b, 0.005_f32);
        assert_approx_ne_rel_tol!(a, b, 0.001_f32);
    }
}

mod compat {
    #[allow(unused_imports)]
    use super::*;

    #[cfg(feature = "bytemuck")]
    #[test]
    fn bytemuck_roundtrip() {
        let d = Duration::new(123_i32);
        let bytes = bytemuck::bytes_of(&d);
        let out = bytemuck::pod_read_unaligned::<Duration<i32>>(bytes);
        assert_eq!(out, d);
    }

    #[cfg(feature = "zerocopy")]
    #[test]
    fn zerocopy_roundtrip() {
        let d = Duration::new(45_u8);
        let bytes = <Duration<u8> as zerocopy::IntoBytes>::as_bytes(&d);
        let out = <Duration<u8> as zerocopy::FromBytes>::read_from_bytes(bytes)
            .expect("valid duration bytes");
        assert_eq!(out, d);
    }

    #[cfg(feature = "sakka")]
    #[test]
    fn sakka_roundtrip() {
        let d = Duration::new(789_i32);

        let mut writer = sakka::Writer::new(sakka::Endian::Little, ());
        <Duration<i32> as sakka::Encode>::encode(&d, &mut writer).expect("encode duration");
        let bytes = writer.finish();

        let mut reader = sakka::Reader::new(&bytes, sakka::Endian::Little, ());
        let out = <Duration<i32> as sakka::Decode>::decode(&mut reader).expect("decode duration");

        assert_eq!(out, d);
        assert!(reader.is_eof());
    }
}