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() {}