diff --git a/Cargo.toml b/Cargo.toml index 87eb8b27ec..c3de968d0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = {version = "0.7", optional = true} iana-time-zone = { version = "0.1.44", optional = true, features = ["fallback"] } +arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } diff --git a/ci/github.sh b/ci/github.sh index a52b5f8efa..9eb3f17859 100755 --- a/ci/github.sh +++ b/ci/github.sh @@ -6,7 +6,7 @@ set -euo pipefail source "${BASH_SOURCE[0]%/*}/_shlib.sh" TEST_TZS=(ACST-9:30 EST4 UTC0 Asia/Katmandu) -FEATURES=(std serde clock "alloc serde" unstable-locales) +FEATURES=(std serde clock "alloc serde" unstable-locales arbitrary) CHECK_FEATURES=(alloc "std unstable-locales" "serde clock" "clock unstable-locales") RUST_132_FEATURES=(serde) diff --git a/src/naive/date.rs b/src/naive/date.rs index 210bb19b12..693be36228 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2060,6 +2060,55 @@ mod serde { } } +#[cfg(feature = "arbitrary")] +#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] +mod arbitrary { + use super::{NaiveDate, YearFlags, MAX_YEAR, MIN_YEAR}; + use arbitrary::{Arbitrary, Unstructured}; + + #[cfg(feature = "arbitrary")] + impl Arbitrary<'_> for NaiveDate { + fn arbitrary(u: &mut Unstructured) -> arbitrary::Result { + let year = u.int_in_range(MIN_YEAR..=MAX_YEAR)?; + let max_days = YearFlags::from_year(year).ndays(); + let ord = u.int_in_range(1..=max_days)?; + let date = NaiveDate::from_yo_opt(year, ord) + .expect("Could not generate a valid chrono::NaiveDate. It looks like implementation of Arbitrary for NaiveDate is erroneous."); + Ok(date) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + const UNSTRCUTURED_BINARY1: [u8; 8] = [0x8f, 0xc2, 0x95, 0xdc, 0x3e, 0x45, 0xb2, 0x3e]; + const UNSTRCUTURED_BINARY2: [u8; 8] = [0x8b, 0xad, 0x2c, 0xc9, 0xf0, 0x05, 0x75, 0x84]; + + #[test] + fn test_different_unstructured() { + let mut unstrctured1 = Unstructured::new(&UNSTRCUTURED_BINARY1); + let date1 = NaiveDate::arbitrary(&mut unstrctured1).unwrap(); + + let mut unstrctured2 = Unstructured::new(&UNSTRCUTURED_BINARY2); + let date2 = NaiveDate::arbitrary(&mut unstrctured2).unwrap(); + + assert_ne!(date1, date2); + } + + #[test] + fn test_same_unstructured() { + let mut unstrctured1 = Unstructured::new(&UNSTRCUTURED_BINARY1); + let date1 = NaiveDate::arbitrary(&mut unstrctured1).unwrap(); + + let mut unstrctured2 = Unstructured::new(&UNSTRCUTURED_BINARY1); + let date2 = NaiveDate::arbitrary(&mut unstrctured2).unwrap(); + + assert_eq!(date1, date2); + } + } +} + #[cfg(test)] mod tests { use super::{ diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 363c5ace21..ccff8a06fe 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -74,6 +74,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// ``` #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct NaiveDateTime { date: NaiveDate, time: NaiveTime, diff --git a/src/naive/time/arbitrary.rs b/src/naive/time/arbitrary.rs new file mode 100644 index 0000000000..59b5c0dda8 --- /dev/null +++ b/src/naive/time/arbitrary.rs @@ -0,0 +1,44 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] + +use super::NaiveTime; +use arbitrary::{Arbitrary, Unstructured}; + +impl Arbitrary<'_> for NaiveTime { + fn arbitrary(u: &mut Unstructured) -> arbitrary::Result { + let secs = u.int_in_range(0..=86_399)?; + let nano = u.int_in_range(0..=1_999_999_999)?; + let time = NaiveTime::from_num_seconds_from_midnight_opt(secs, nano) + .expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous."); + Ok(time) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const UNSTRCUTURED_BINARY1: [u8; 8] = [0x8f, 0xc2, 0x95, 0xdc, 0x3e, 0x45, 0xb2, 0x3e]; + const UNSTRCUTURED_BINARY2: [u8; 8] = [0x8b, 0xad, 0x2c, 0xc9, 0xf0, 0x05, 0x75, 0x84]; + + #[test] + fn test_different_unstructured() { + let mut unstrctured1 = Unstructured::new(&UNSTRCUTURED_BINARY1); + let time1 = NaiveTime::arbitrary(&mut unstrctured1).unwrap(); + + let mut unstrctured2 = Unstructured::new(&UNSTRCUTURED_BINARY2); + let time2 = NaiveTime::arbitrary(&mut unstrctured2).unwrap(); + + assert_ne!(time1, time2); + } + + #[test] + fn test_same_unstructured() { + let mut unstrctured1 = Unstructured::new(&UNSTRCUTURED_BINARY1); + let time1 = NaiveTime::arbitrary(&mut unstrctured1).unwrap(); + + let mut unstrctured2 = Unstructured::new(&UNSTRCUTURED_BINARY1); + let time2 = NaiveTime::arbitrary(&mut unstrctured2).unwrap(); + + assert_eq!(time1, time2); + } +} diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f49d2a8212..33b10a3170 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -21,6 +21,9 @@ use crate::{TimeDelta, Timelike}; #[cfg(feature = "serde")] mod serde; +#[cfg(feature = "arbitrary")] +mod arbitrary; + #[cfg(test)] mod tests;