No public description PiperOrigin-RevId: 964325485
diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 2a7116c..7c3cbae 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml
@@ -44,6 +44,9 @@ version = "1.0.103" features = ["diff"] +[dev-dependencies.googletest] +version = "0.14.2" + [profile.fuzztest] inherits = 'release' panic = 'abort'
diff --git a/rust/src/domains.rs b/rust/src/domains.rs index a4bcd2c..0447752 100644 --- a/rust/src/domains.rs +++ b/rust/src/domains.rs
@@ -13,6 +13,7 @@ // limitations under the License. pub mod arbitrary; +pub mod containers; pub mod range; pub mod tuple_of; pub mod utility;
diff --git a/rust/src/domains/arbitrary.rs b/rust/src/domains/arbitrary.rs index 99e0f41..3ad8bdb 100644 --- a/rust/src/domains/arbitrary.rs +++ b/rust/src/domains/arbitrary.rs
@@ -44,6 +44,18 @@ _phantom: std::marker::PhantomData<T>, } +impl<T> Clone for Arbitrary<T> { + fn clone(&self) -> Self { + Self { _phantom: std::marker::PhantomData } + } +} + +impl<T> std::fmt::Debug for Arbitrary<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Arbitrary").field("_phantom", &self._phantom).finish() + } +} + // We cannot just use `#[derive(Default)]` because `T` might not be `Default`. impl<T> Default for Arbitrary<T> { fn default() -> Self {
diff --git a/rust/src/domains/containers.rs b/rust/src/domains/containers.rs new file mode 100644 index 0000000..3c34565 --- /dev/null +++ b/rust/src/domains/containers.rs
@@ -0,0 +1,435 @@ +use rand::RngExt; + +use super::Domain; + +const DEFAULT_MAX_LEN: usize = 5000; + +macro_rules! choose_one { + ($rng:expr, $( $cond:expr $(; w = $weight:expr)? => $action:expr ),* $(,)? ) => {{ + struct Branch { + cond: bool, + weight: usize, + } + + let branches = [ + $( Branch { cond: $cond, weight: choose_one!(@default 1usize, $($weight)?) } ),* + ]; + let total_weight: usize = branches.iter() + .map(|&Branch { cond, weight }| if cond { weight } else { 0 }) + .sum(); + + if total_weight > 0 { + let choice = ::rand::RngExt::random_range($rng, 0..total_weight); + let mut current = 0usize; + let mut i = 0usize; + 'choose_one_loop: { + $( + if branches[i].cond { + let w = branches[i].weight; + if choice >= current && choice < current + w { + let _ = { $action }; + break 'choose_one_loop; + } + #[allow(unused_assignments)] + { current += w; } + } + i += 1; + )* + } + } + }}; + (@default $default:expr, ) => { $default }; + (@default $default:expr, $val:expr) => { $val }; +} + +/// A trait for configuring the length constraints of container domains. +/// +/// This trait provides a fluent interface to specify the length requirements +/// for domains that generate collections, such as [`VecOf`]. +pub trait ContainerDomain: Sized { + /// Sets the length of the container to be exactly `len`. + fn with_len(self, len: usize) -> Self; + + /// Sets the minimum length of the container. + /// + /// # Panics + /// + /// Panics if the specified `min_len` is greater than the current maximum length (if set). + fn with_min_len(self, min_len: usize) -> Self; + + /// Sets the maximum length of the container. + /// + /// Using this API will override any previous `with_soft_max_len` call. + /// + /// # Panics + /// + /// Panics if the specified `max_len` is less than the current minimum length. + fn with_max_len(self, max_len: usize) -> Self; + + /// Sets a "soft" maximum length of the container. + /// + /// Using this API will override any previous `with_max_len` call. + /// + /// With this constraint, the domain will consider containers longer than + /// `soft_max_len` as valid. When mutating the containers, the domain will + /// not increase their size further if it is already greater or equal to + /// `soft_max_len`. + /// + /// # Panics + /// + /// Panics if the specified `soft_max_len` is less than the current minimum length. + fn with_soft_max_len(self, soft_max_len: usize) -> Self; +} + +pub struct VecOf<T> { + inner: T, + min_len: usize, + max_len: Option<usize>, + max_len_is_soft: bool, +} + +impl<T: Clone> Clone for VecOf<T> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + min_len: self.min_len, + max_len: self.max_len, + max_len_is_soft: self.max_len_is_soft, + } + } +} + +impl<T: std::fmt::Debug> std::fmt::Debug for VecOf<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VecOf") + .field("inner", &self.inner) + .field("min_len", &self.min_len) + .field("max_len", &self.max_len) + .field("max_len_is_soft", &self.max_len_is_soft) + .finish() + } +} + +impl<T> VecOf<T> { + pub fn new(inner: T) -> Self { + Self { inner, min_len: 0, max_len: None, max_len_is_soft: false } + } + + fn max_len(&self) -> usize { + self.max_len.unwrap_or(self.min_len.max(DEFAULT_MAX_LEN)) + } +} + +impl<T> Domain for VecOf<T> +where + T: Domain, +{ + type CorpusValue = Vec<T::CorpusValue>; + type UserValue<'user> = Vec<T::UserValue<'user>>; + + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + if self.max_len() == 0 { + return Ok(Vec::new()); + } + + let initial_size = if self.min_len == 0 { rng.random_range(0..=1) } else { self.min_len }; + + let mut val = Vec::with_capacity(initial_size); + for _ in 0..initial_size { + val.push(self.inner.init(rng)?); + } + Ok(val) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + if self.max_len_is_soft { + anyhow::ensure!( + self.min_len <= val.len(), + "Length {} is less than the minimum length {}", + val.len(), + self.min_len + ); + } else { + anyhow::ensure!( + self.min_len <= val.len() && val.len() <= self.max_len(), + "Length {} is not between the minimum length {} and maximum length {}", + val.len(), + self.min_len, + self.max_len() + ); + } + + choose_one!(rng, + val.len() > self.min_len => { + let idx = rng.random_range(0..val.len()); + val.remove(idx); + }, + !only_shrink && val.len() < self.max_len() => { + let idx = rng.random_range(0..=val.len()); + val.insert(idx, self.inner.init(rng)?); + }, + !val.is_empty() => { + let idx = rng.random_range(0..val.len()); + self.inner.mutate(&mut val[idx], rng, only_shrink)?; + }, + ); + + Ok(()) + } + + fn get_user_value<'a>( + &self, + corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result<Self::UserValue<'a>> { + let mut user_values = Vec::with_capacity(corpus_value.len()); + for item in corpus_value { + user_values.push(self.inner.get_user_value(item)?); + } + Ok(user_values) + } +} + +impl<T> ContainerDomain for VecOf<T> { + fn with_len(self, len: usize) -> Self { + Self { min_len: len, max_len: Some(len), ..self } + } + + fn with_min_len(self, min_len: usize) -> Self { + assert!( + self.max_len.map_or(true, |max| min_len <= max), + "Minimum length {} cannot be greater than the maximum length {}", + min_len, + self.max_len.unwrap() + ); + Self { min_len, ..self } + } + + fn with_max_len(self, max_len: usize) -> Self { + assert!( + max_len >= self.min_len, + "Maximum length {} cannot be less than the minimum length {}", + max_len, + self.min_len + ); + Self { max_len_is_soft: false, max_len: Some(max_len), ..self } + } + + fn with_soft_max_len(self, soft_max_len: usize) -> Self { + assert!( + soft_max_len >= self.min_len, + "Soft maximum length {} cannot be less than the minimum length {}", + soft_max_len, + self.min_len + ); + Self { max_len_is_soft: true, max_len: Some(soft_max_len), ..self } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::domains::arbitrary::Arbitrary; + use googletest::prelude::*; + use rand::rngs::{SmallRng, SysRng}; + use rand::SeedableRng; + + fn get_rng() -> SmallRng { + SmallRng::try_from_rng(&mut SysRng).expect("Failed to create RNG") + } + + #[gtest] + fn test_vec_of_mutate_shrink() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10); + + let mut rng = get_rng(); + + let mut val = vec![1, 2, 3, 4, 5]; + + // Test shrinking length + for _ in 0..100 { + let orig_len = val.len(); + if orig_len == 0 { + break; + } + domain.mutate(&mut val, &mut rng, true).unwrap(); + expect_that!(val.len(), le(orig_len)); + } + } + + #[gtest] + fn test_vec_of_mutate_grow_and_change() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10); + + let mut rng = get_rng(); + + let mut val = vec![1]; + let mut grew = false; + + for _ in 0..100 { + let orig_len = val.len(); + domain.mutate(&mut val, &mut rng, false).unwrap(); + expect_that!(val.len(), le(10)); + if val.len() > orig_len { + grew = true; + } + } + expect_that!(grew, eq(true), "VecOf should be able to grow during normal mutation"); + } + + #[gtest] + fn test_vec_of_init_respects_min_len() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5); + let mut rng = get_rng(); + + for _ in 0..100 { + let val = domain.init(&mut rng).unwrap(); + expect_that!(val.len(), ge(5)); + } + } + + #[gtest] + fn test_vec_of_init_fixed_len() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(7); + let mut rng = get_rng(); + + for _ in 0..100 { + let val = domain.init(&mut rng).unwrap(); + expect_that!(val.len(), eq(7)); + } + } + + #[gtest] + fn test_vec_of_init_default_max_len() { + let domain = VecOf::new(Arbitrary::<u32>::default()); + let mut rng = get_rng(); + + for _ in 0..100 { + let val = domain.init(&mut rng).unwrap(); + expect_that!(val.len(), le(DEFAULT_MAX_LEN)); + } + } + + #[gtest] + fn test_vec_of_mutate_respects_min_len() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(3); + let mut rng = get_rng(); + + let mut val = vec![1, 2, 3]; + for _ in 0..100 { + domain.mutate(&mut val, &mut rng, true).unwrap(); + expect_that!(val.len(), ge(3)); + } + } + + #[gtest] + fn test_vec_of_mutate_respects_max_len() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(3); + let mut rng = get_rng(); + + let mut val = vec![1, 2, 3]; + for _ in 0..100 { + domain.mutate(&mut val, &mut rng, false).unwrap(); + expect_that!(val.len(), le(3)); + } + } + + #[gtest] + fn test_vec_of_mutate_min_len_validation() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5); + let mut rng = get_rng(); + + let mut val = vec![1, 2, 3]; // Length 3, which is < 5 + let result = domain.mutate(&mut val, &mut rng, false); + expect_that!(result.is_err(), eq(true)); + let err_msg = format!("{}", result.unwrap_err()); + expect_that!( + err_msg, + contains_substring( + "Length 3 is not between the minimum length 5 and maximum length 5000" + ) + ); + } + + #[gtest] + fn test_vec_of_mutate_soft_max_len_behavior() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_soft_max_len(5); + let mut rng = get_rng(); + + // Valid mutation within bounds + let mut valid_val = vec![1, 2, 3, 4, 5]; + let valid_result = domain.mutate(&mut valid_val, &mut rng, false); + expect_that!(valid_result.is_ok(), eq(true)); + + // In soft max mode, we DON'T error if we are already over the limit on entry + let mut over_val = vec![1, 2, 3, 4, 5, 6]; + let result = domain.mutate(&mut over_val, &mut rng, false); + expect_that!(result.is_ok(), eq(true)); + // But it should not have grown further + expect_that!(over_val.len(), le(6)); + } + + #[gtest] + #[should_panic(expected = "Minimum length 6 cannot be greater than the maximum length 5")] + fn test_vec_of_with_invalid_min_len() { + VecOf::new(Arbitrary::<u32>::default()).with_max_len(5).with_min_len(6); + } + + #[gtest] + #[should_panic(expected = "Maximum length 4 cannot be less than the minimum length 5")] + fn test_vec_of_with_invalid_max_len() { + VecOf::new(Arbitrary::<u32>::default()).with_min_len(5).with_max_len(4); + } + + #[gtest] + #[should_panic(expected = "Soft maximum length 4 cannot be less than the minimum length 5")] + fn test_vec_of_with_invalid_soft_max_len() { + VecOf::new(Arbitrary::<u32>::default()).with_min_len(5).with_soft_max_len(4); + } + + #[gtest] + fn test_vec_of_mutate_no_action_at_bounds() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(1); + let mut rng = get_rng(); + + let mut val = vec![100u32]; + for _ in 0..100 { + // with_len(1) means min=1, max=1. + // insert/remove should be disabled. + // mutate inner should still be possible. + let orig_val = val[0]; + domain.mutate(&mut val, &mut rng, false).unwrap(); + expect_that!(val.len(), eq(1)); + // In this case, since action_count will be 1 (can_change), it MUST call inner.mutate + // Arbitrary::<u32> mutate should eventually change the value. + if val[0] != orig_val { + return; + } + } + } + + #[gtest] + fn test_vec_of_zero_len() { + let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(0); + let mut rng = get_rng(); + + let val = domain.init(&mut rng).unwrap(); + expect_that!(val, is_empty()); + + let mut val = Vec::new(); + domain.mutate(&mut val, &mut rng, false).unwrap(); + expect_that!(val, is_empty()); + } + + #[gtest] + fn test_vec_of_get_user_value() { + let domain = VecOf::new(Arbitrary::<u32>::default()); + let corpus_val = vec![1u32, 2u32, 3u32]; + let user_val = domain.get_user_value(&corpus_val).unwrap(); + expect_that!(user_val, container_eq(vec![1u32, 2u32, 3u32])); + } +}
diff --git a/rust/tests/macro_compiles.rs b/rust/tests/macro_compiles.rs index 2c1506d..5eee104 100644 --- a/rust/tests/macro_compiles.rs +++ b/rust/tests/macro_compiles.rs
@@ -13,6 +13,8 @@ // limitations under the License. use fuzztest::domains::arbitrary::Arbitrary; +use fuzztest::domains::containers::ContainerDomain; +use fuzztest::domains::containers::VecOf; use fuzztest::fuzztest; #[fuzztest(_a = Arbitrary::<i32>::default())] @@ -21,4 +23,7 @@ #[fuzztest(_a = Arbitrary::<i32>::default(), _b = Arbitrary::<i32>::default())] fn fuzztest_macro_compiles_with_two_args(_a: i32, _b: i32) {} +#[fuzztest(_a = VecOf::new(Arbitrary::<i32>::default()).with_max_len(10))] +fn fuzztest_macro_compiles_with_vec(_a: Vec<i32>) {} + fn main() {}