diff --git a/crates/lox_core/src/errors.rs b/crates/lox_core/src/errors.rs index a053c036..b4cd4ea9 100644 --- a/crates/lox_core/src/errors.rs +++ b/crates/lox_core/src/errors.rs @@ -8,7 +8,7 @@ use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum LoxError { #[error("invalid date `{0}-{1}-{2}`")] InvalidDate(i64, i64, i64), diff --git a/crates/lox_core/src/time.rs b/crates/lox_core/src/time.rs index a9a295c7..055248c5 100644 --- a/crates/lox_core/src/time.rs +++ b/crates/lox_core/src/time.rs @@ -67,18 +67,94 @@ impl Into for PerMille { } #[cfg(test)] -mod per_mille_tests { +mod tests { + use crate::errors::LoxError; use crate::time::PerMille; #[test] - fn test_new_valid() { - assert!(PerMille::new(0).is_ok()); - assert!(PerMille::new(999).is_ok()); + fn test_per_mille_new() { + struct TestCase { + desc: &'static str, + input: u16, + expected: Result, + } + + let test_cases = [ + TestCase { + desc: "on lower bound", + input: 0, + expected: Ok(PerMille(0)), + }, + TestCase { + desc: "between bounds", + input: 1, + expected: Ok(PerMille(1)), + }, + TestCase { + desc: "on upper bound", + input: 999, + expected: Ok(PerMille(999)), + }, + TestCase { + desc: "above upper bound", + input: 1000, + expected: Err(LoxError::InvalidPerMille(1000)), + }, + ]; + + for tc in test_cases { + let actual = PerMille::new(tc.input); + assert_eq!( + actual, tc.expected, + "expected {:?} when input is {:?}, but got {:?}", + tc.expected, tc.input, tc.desc + ); + } + } + + #[test] + fn test_per_mille_display() { + struct TestCase { + input: PerMille, + expected: &'static str, + } + + let test_cases = [ + TestCase { + input: PerMille(1), + expected: "001", + }, + TestCase { + input: PerMille(11), + expected: "011", + }, + TestCase { + input: PerMille(111), + expected: "111", + }, + ]; + + for tc in test_cases { + let actual = format!("{}", tc.input); + assert_eq!( + actual, tc.expected, + "expected {:?} when input is {:?}, but got {:?}", + tc.expected, tc.input, actual, + ); + } + } + + #[test] + fn test_per_mille_try_from() { + assert_eq!(PerMille::try_from(0), Ok(PerMille(0))); + assert_eq!( + PerMille::try_from(1000), + Err(LoxError::InvalidPerMille(1000)) + ); } #[test] - fn test_new_invalid() { - assert!(PerMille::new(1000).is_err()); - assert!(PerMille::new(1001).is_err()); + fn test_per_mille_into_i64() { + assert_eq!(Into::::into(PerMille(0)), 0i64); } } diff --git a/crates/lox_core/src/time/continuous.rs b/crates/lox_core/src/time/continuous.rs index 72893a34..e482802a 100644 --- a/crates/lox_core/src/time/continuous.rs +++ b/crates/lox_core/src/time/continuous.rs @@ -975,147 +975,182 @@ mod tests { } #[test] - fn test_raw_time_add_time_delta_positive_time_no_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 1, - }; - let time = RawTime { - seconds: 1, - attoseconds: 0, - }; - let expected = RawTime { - seconds: 2, - attoseconds: 1, - }; - let actual = time + delta; - assert_eq!(expected, actual); - } - - #[test] - fn test_raw_time_add_time_delta_positive_time_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 2, - }; - let time = RawTime { - seconds: 1, - attoseconds: ATTOSECONDS_PER_SECOND - 1, - }; - let expected = RawTime { - seconds: 3, - attoseconds: 1, - }; - let actual = time + delta; - assert_eq!(expected, actual); - } - - #[test] - fn test_raw_time_add_time_delta_negative_time_no_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 1, - }; - let time = RawTime { - seconds: -1, - attoseconds: 0, - }; - let expected = RawTime { - seconds: 0, - attoseconds: 1, - }; - let actual = time + delta; - assert_eq!(expected, actual); - } + fn test_raw_time_add_time_delta() { + struct TestCase { + desc: &'static str, + delta: TimeDelta, + time: RawTime, + expected: RawTime, + } - #[test] - fn test_raw_time_add_time_delta_negative_time_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 2, - }; - let time = RawTime { - seconds: -1, - attoseconds: ATTOSECONDS_PER_SECOND - 1, - }; - let expected = RawTime { - seconds: 1, - attoseconds: 1, - }; - let actual = time + delta; - assert_eq!(expected, actual); - } + let test_cases = [ + TestCase { + desc: "positive time with no attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 1, + }, + time: RawTime { + seconds: 1, + attoseconds: 0, + }, + expected: RawTime { + seconds: 2, + attoseconds: 1, + }, + }, + TestCase { + desc: "positive time with attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 2, + }, + time: RawTime { + seconds: 1, + attoseconds: ATTOSECONDS_PER_SECOND - 1, + }, + expected: RawTime { + seconds: 3, + attoseconds: 1, + }, + }, + TestCase { + desc: "negative time with no attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 1, + }, + time: RawTime { + seconds: -1, + attoseconds: 0, + }, + expected: RawTime { + seconds: 0, + attoseconds: 1, + }, + }, + TestCase { + desc: "negative time with attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 2, + }, + time: RawTime { + seconds: -1, + attoseconds: ATTOSECONDS_PER_SECOND - 1, + }, + expected: RawTime { + seconds: 1, + attoseconds: 1, + }, + }, + ]; - #[test] - fn test_raw_time_sub_time_delta_positive_time_no_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 1, - }; - let time = RawTime { - seconds: 2, - attoseconds: 2, - }; - let expected = RawTime { - seconds: 1, - attoseconds: 1, - }; - let actual = time - delta; - assert_eq!(expected, actual); + for tc in test_cases { + let actual = tc.time + tc.delta; + assert_eq!( + actual, tc.expected, + "{}: expected {:?}, got {:?}", + tc.desc, tc.expected, actual + ); + } } #[test] - fn test_raw_time_sub_time_delta_positive_time_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 2, - }; - let time = RawTime { - seconds: 2, - attoseconds: 1, - }; - let expected = RawTime { - seconds: 0, - attoseconds: ATTOSECONDS_PER_SECOND - 1, - }; - let actual = time - delta; - assert_eq!(expected, actual); - } + fn test_raw_time_sub_time_delta() { + struct TestCase { + desc: &'static str, + delta: TimeDelta, + time: RawTime, + expected: RawTime, + } - #[test] - fn test_raw_time_sub_time_delta_negative_time_no_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 1, - }; - let time = RawTime { - seconds: -1, - attoseconds: 2, - }; - let expected = RawTime { - seconds: -2, - attoseconds: 1, - }; - let actual = time - delta; - assert_eq!(expected, actual); - } + let test_cases = [ + TestCase { + desc: "positive time with no attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 1, + }, + time: RawTime { + seconds: 2, + attoseconds: 2, + }, + expected: RawTime { + seconds: 1, + attoseconds: 1, + }, + }, + TestCase { + desc: "positive time with attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 2, + }, + time: RawTime { + seconds: 2, + attoseconds: 1, + }, + expected: RawTime { + seconds: 0, + attoseconds: ATTOSECONDS_PER_SECOND - 1, + }, + }, + TestCase { + desc: "negative time with no attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 1, + }, + time: RawTime { + seconds: -1, + attoseconds: 2, + }, + expected: RawTime { + seconds: -2, + attoseconds: 1, + }, + }, + TestCase { + desc: "negative time with attosecond wrap", + delta: TimeDelta { + seconds: 1, + attoseconds: 2, + }, + time: RawTime { + seconds: -1, + attoseconds: 1, + }, + expected: RawTime { + seconds: -3, + attoseconds: ATTOSECONDS_PER_SECOND - 1, + }, + }, + TestCase { + desc: "transition from positive to negative time", + delta: TimeDelta { + seconds: 1, + attoseconds: 2, + }, + time: RawTime { + seconds: 0, + attoseconds: 1, + }, + expected: RawTime { + seconds: -2, + attoseconds: ATTOSECONDS_PER_SECOND - 1, + }, + }, + ]; - #[test] - fn test_raw_time_sub_time_delta_negative_time_attosecond_wrap() { - let delta = TimeDelta { - seconds: 1, - attoseconds: 2, - }; - let time = RawTime { - seconds: -1, - attoseconds: 1, - }; - let expected = RawTime { - seconds: -3, - attoseconds: ATTOSECONDS_PER_SECOND - 1, - }; - let actual = time - delta; - assert_eq!(expected, actual); + for tc in test_cases { + let actual = tc.time - tc.delta; + assert_eq!( + actual, tc.expected, + "{}: expected {:?}, got {:?}", + tc.desc, tc.expected, actual + ); + } } #[test] diff --git a/crates/lox_core/src/time/utc.rs b/crates/lox_core/src/time/utc.rs index 028aeed9..a193b911 100644 --- a/crates/lox_core/src/time/utc.rs +++ b/crates/lox_core/src/time/utc.rs @@ -77,7 +77,7 @@ impl Display for UTC { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{:02}:{:02}:{:02}.{:03}.{:03}.{:03}.{:03}.{:03}.{:03} UTC", + "{:02}:{:02}:{:02}.{}.{}.{}.{}.{}.{} UTC", self.hour, self.minute, self.second, @@ -308,4 +308,25 @@ mod tests { let actual = UTCDateTime::new(date, time); assert_eq!(expected, actual); } + + #[test] + fn test_from_fractional_seconds() { + let hour = 0; + let minute = 0; + let second = 0.123_456_789_123_456_78; + let expected = UTC { + hour: 0, + minute: 0, + second: 0, + milli: PerMille(123), + micro: PerMille(456), + nano: PerMille(789), + pico: PerMille(123), + femto: PerMille(456), + atto: PerMille(780), + }; + let actual = + UTC::from_fractional_seconds(hour, minute, second).expect("time should be valid"); + assert_eq!(expected, actual); + } }