Skip to content
Open
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RELEASE_TYPE: patch

Add generator for Duration
14 changes: 11 additions & 3 deletions src/generators/default.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use super::{
BoolGenerator, BoxedGenerator, FloatGenerator, Generator, HashMapGenerator, IntegerGenerator,
OptionalGenerator, TextGenerator, VecGenerator, booleans, collections::ArrayGenerator, floats,
hashmaps, integers, optional, text, vecs,
BoolGenerator, BoxedGenerator, DurationGenerator, FloatGenerator, Generator, HashMapGenerator,
IntegerGenerator, OptionalGenerator, TextGenerator, VecGenerator, booleans,
collections::ArrayGenerator, durations, floats, hashmaps, integers, optional, text, vecs,
};
use std::collections::HashMap;
use std::hash::Hash;
use std::time::Duration;

/// Trait for types that have a default generator.
///
Expand Down Expand Up @@ -187,6 +188,13 @@ where
}
}

impl DefaultGenerator for Duration {
type Generator = DurationGenerator;
fn default_generator() -> Self::Generator {
durations()
}
}

impl<K: DefaultGenerator + 'static, V: DefaultGenerator + 'static> DefaultGenerator
for HashMap<K, V>
where
Expand Down
2 changes: 2 additions & 0 deletions src/generators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod generators;
mod misc;
mod numeric;
mod strings;
mod time;
mod tuples;

#[cfg(feature = "rand")]
Expand Down Expand Up @@ -52,6 +53,7 @@ pub use strings::{
IpAddressGenerator, RegexGenerator, TextGenerator, TimeGenerator, UrlGenerator, binary, dates,
datetimes, domains, emails, from_regex, ip_addresses, text, times, urls,
};
pub use time::{DurationGenerator, durations};
#[doc(hidden)]
pub use tuples::{
tuples0, tuples1, tuples2, tuples3, tuples4, tuples5, tuples6, tuples7, tuples8, tuples9,
Expand Down
82 changes: 82 additions & 0 deletions src/generators/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use super::{BasicGenerator, Generator, TestCase};
use crate::cbor_utils::cbor_map;
use std::time::Duration;

/// Generator for [`Duration`] values. Created by [`durations()`].
///
/// Internally generates nanoseconds as a `u64`, so the maximum representable
/// duration is approximately 584 years (`u64::MAX` nanoseconds).
/// Use `min_value` and `max_value` to constrain the range.
pub struct DurationGenerator {
min_nanos: u64,
max_nanos: u64,
}

impl DurationGenerator {
/// Set the minimum duration (inclusive).
pub fn min_value(mut self, min: Duration) -> Self {
self.min_nanos = duration_to_nanos(min);
self
}

/// Set the maximum duration (inclusive).
pub fn max_value(mut self, max: Duration) -> Self {
self.max_nanos = duration_to_nanos(max);
self
}

fn build_schema(&self) -> ciborium::Value {
assert!(
self.min_nanos <= self.max_nanos,
"Cannot have max_value < min_value"
);
cbor_map! {
"type" => "integer",
"min_value" => self.min_nanos,
"max_value" => self.max_nanos
}
}
}

impl Generator<Duration> for DurationGenerator {
fn do_draw(&self, tc: &TestCase) -> Duration {
let nanos: u64 = super::generate_from_schema(tc, &self.build_schema());
Duration::from_nanos(nanos)
}

fn as_basic(&self) -> Option<BasicGenerator<'_, Duration>> {
Some(BasicGenerator::new(self.build_schema(), |raw| {
let nanos: u64 = super::deserialize_value(raw);
Duration::from_nanos(nanos)
}))
}
}

/// Generate [`Duration`] values.
///
/// By default, generates durations from zero up to `u64::MAX` nanoseconds
/// (approximately 584 years). Use `min_value` and `max_value` to constrain
/// the range.
///
/// # Example
///
/// ```no_run
/// use std::time::Duration;
///
/// #[hegel::test]
/// fn my_test(tc: hegel::TestCase) {
/// let d = tc.draw(hegel::generators::durations()
/// .max_value(Duration::from_secs(60)));
/// assert!(d <= Duration::from_secs(60));
/// }
/// ```
pub fn durations() -> DurationGenerator {
DurationGenerator {
min_nanos: 0,
max_nanos: u64::MAX,
}
}

fn duration_to_nanos(d: Duration) -> u64 {
d.as_nanos().try_into().unwrap_or(u64::MAX)
}
20 changes: 20 additions & 0 deletions tests/test_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
mod common;

use common::utils::assert_all_examples;
use hegel::generators;
use std::time::Duration;

#[test]
fn test_durations_default() {
assert_all_examples(generators::durations(), |d| *d >= Duration::ZERO);
}

#[test]
fn test_durations_bounded() {
let min = Duration::from_secs(5);
let max = Duration::from_secs(60);
assert_all_examples(
generators::durations().min_value(min).max_value(max),
move |d| *d >= min && *d <= max,
);
}
Loading