Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl Arbitrary for SystemTime #178

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use core::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZ
use core::ops::{Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive};
use core::str;
use core::time::Duration;
use std::borrow::{Cow, ToOwned};
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
use std::ffi::{CString, OsString};
use std::hash::BuildHasher;
Expand All @@ -53,6 +53,7 @@ use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;

/// Generate arbitrary structured values from raw, unstructured data.
///
Expand Down Expand Up @@ -1272,6 +1273,32 @@ impl<'a> Arbitrary<'a> for SocketAddr {
}
}

impl<'a> Arbitrary<'a> for SystemTime {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
// Create a SystemTime by adding or subtracting a duration from epoch.
// The result is not guaranteed to fit. Keep trying until a good SystemTime if found.
loop {
Copy link
Member

@nagisa nagisa Mar 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unfortunate to have behaviour of this that is dependent on the target, but since the underlying type requires it, why not #[cfg] or even just check at runtime if the platform considers UNIX_EPOCH the minimal representable timestamp and just not bother with the add/sub on those targets?

Would save on the input bytes and such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs for SystemTime don't guarantee anything regarding what fits in a SystemTime. So I would not know what to check for. Maybe UNIX_EPOCH is the minimum. Maybe the year 0 is the minimum.

Also, the maximum is interesting. It is not guaranteed that SystemTime::UNIX_EPOCH + Duration::MAX will fit. Therefore, it needs to be checked.

I could determine the minimum and maximum by trail and error, save this result and use that to make sure we never generate a value that doesn't fit. But that isn't great code as well.

Copy link
Member

@nagisa nagisa Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UNIX_EPOCH.checked_sub(Duration::from_nanos(1)).is_none() implies that UNIX_EPOCH is the min value representable. This could be checked once and saved in a static or somesuch.

Unfortunately I've no good ideas for the maximum, so yeah, you would need the loop regardless. Feel free to ignore the comment until others chime in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand, the minimum and maximum are platform-specific. I see three possible solutions:

  1. Try to generate a value, check whether in range, if not, loop (current patch)
  2. Try to find min and max, save those values, then generate a value in between (thus moving the loop to the process of finding the min and max)
  3. Work with upstream to define the min and max as a constant (Duration has a min/max, so why shouldn't SystemTime)

let add: bool = u.arbitrary()?;
let duration: Duration = u.arbitrary()?;
if add {
if let Some(system_time) = SystemTime::UNIX_EPOCH.checked_add(duration) {
return Ok(system_time);
}
} else if let Some(system_time) = SystemTime::UNIX_EPOCH.checked_sub(duration) {
return Ok(system_time);
}
}
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
size_hint::and_all(&[
bool::size_hint(depth),
Duration::size_hint(depth),
(0, None),
])
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -1587,6 +1614,19 @@ mod test {
);
assert_eq!((1, None), <(u8, Vec<u8>) as Arbitrary>::size_hint(0));
}

#[test]
fn size_hint_for_system_time() {
let system_time = SystemTime::size_hint(0);

// SystemTime::size_hint as minimum of bool + Duration
assert_eq!(
system_time.0,
size_hint::and(bool::size_hint(0), Duration::size_hint(0)).0
);
// SystemTime::size_hint has no maximum
assert_eq!(system_time.1, None);
}
}

/// Multiple conflicting arbitrary attributes are used on the same field:
Expand Down