Repository: rust-bitcoin/rust-bitcoin (language: rust)

Issue:
units: Implement `Arbitrary` for all types
If #3682 merges you can use `cotrib/api.sh units types` to get a list of types and you check the api files (`api/units/all-features.txt`) to see which are done.


Discussion / hints:

Fix patch:
diff --git a/api/units/all-features.txt b/api/units/all-features.txt
index 66adfa66c..0d51f112c 100644
--- a/api/units/all-features.txt
+++ b/api/units/all-features.txt
@@ -606,7 +606,14 @@ impl serde::ser::Serialize for bitcoin_units::locktime::relative::Time
 impl serde::ser::Serialize for bitcoin_units::weight::Weight
 impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::Amount
 impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::SignedAmount
+impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::amount::Denomination
+impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::block::BlockHeight
+impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::block::BlockInterval
 impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::fee_rate::FeeRate
+impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::locktime::absolute::Height
+impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::locktime::absolute::Time
+impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::locktime::relative::Height
+impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::locktime::relative::Time
 impl<'a> arbitrary::Arbitrary<'a> for bitcoin_units::weight::Weight
 impl<'a> core::iter::traits::accum::Sum<&'a bitcoin_units::Amount> for bitcoin_units::Amount
 impl<'a> core::iter::traits::accum::Sum<&'a bitcoin_units::SignedAmount> for bitcoin_units::SignedAmount
@@ -849,6 +856,7 @@ pub fn bitcoin_units::SignedAmount::unchecked_add(self, rhs: bitcoin_units::Sign
 pub fn bitcoin_units::SignedAmount::unchecked_sub(self, rhs: bitcoin_units::SignedAmount) -> bitcoin_units::SignedAmount
 pub fn bitcoin_units::SignedAmount::unsigned_abs(self) -> bitcoin_units::Amount
 pub fn bitcoin_units::amount::CheckedSum::checked_sum(self) -> core::option::Option<R>
+pub fn bitcoin_units::amount::Denomination::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
 pub fn bitcoin_units::amount::Denomination::clone(&self) -> bitcoin_units::amount::Denomination
 pub fn bitcoin_units::amount::Denomination::eq(&self, other: &bitcoin_units::amount::Denomination) -> bool
 pub fn bitcoin_units::amount::Denomination::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
@@ -936,6 +944,7 @@ pub fn bitcoin_units::amount::serde::as_str::opt::deserialize<'d, A: bitcoin_uni
 pub fn bitcoin_units::amount::serde::as_str::opt::serialize<A: bitcoin_units::amount::serde::SerdeAmountForOpt, S: serde::ser::Serializer>(a: &core::option::Option<A>, s: S) -> core::result::Result<<S as serde::ser::Serializer>::Ok, <S as serde::ser::Serializer>::Error>
 pub fn bitcoin_units::amount::serde::as_str::serialize<A: bitcoin_units::amount::serde::SerdeAmount, S: serde::ser::Serializer>(a: &A, s: S) -> core::result::Result<<S as serde::ser::Serializer>::Ok, <S as serde::ser::Serializer>::Error>
 pub fn bitcoin_units::block::BlockHeight::add(self, rhs: bitcoin_units::block::BlockInterval) -> Self::Output
+pub fn bitcoin_units::block::BlockHeight::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
 pub fn bitcoin_units::block::BlockHeight::clone(&self) -> bitcoin_units::block::BlockHeight
 pub fn bitcoin_units::block::BlockHeight::cmp(&self, other: &bitcoin_units::block::BlockHeight) -> core::cmp::Ordering
 pub fn bitcoin_units::block::BlockHeight::deserialize<__D>(__deserializer: __D) -> core::result::Result<Self, <__D as serde::de::Deserializer>::Error> where __D: serde::de::Deserializer<'de>
@@ -954,6 +963,7 @@ pub fn bitcoin_units::block::BlockHeight::try_from(s: alloc::boxed::Box<str>) ->
 pub fn bitcoin_units::block::BlockHeight::try_from(s: alloc::string::String) -> core::result::Result<Self, Self::Error>
 pub fn bitcoin_units::block::BlockInterval::add(self, rhs: bitcoin_units::block::BlockInterval) -> Self::Output
 pub fn bitcoin_units::block::BlockInterval::add_assign(&mut self, rhs: bitcoin_units::block::BlockInterval)
+pub fn bitcoin_units::block::BlockInterval::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
 pub fn bitcoin_units::block::BlockInterval::clone(&self) -> bitcoin_units::block::BlockInterval
 pub fn bitcoin_units::block::BlockInterval::cmp(&self, other: &bitcoin_units::block::BlockInterval) -> core::cmp::Ordering
 pub fn bitcoin_units::block::BlockInterval::default() -> bitcoin_units::block::BlockInterval
@@ -1007,6 +1017,7 @@ pub fn bitcoin_units::locktime::absolute::ConversionError::clone(&self) -> bitco
 pub fn bitcoin_units::locktime::absolute::ConversionError::eq(&self, other: &bitcoin_units::locktime::absolute::ConversionError) -> bool
 pub fn bitcoin_units::locktime::absolute::ConversionError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 pub fn bitcoin_units::locktime::absolute::ConversionError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
+pub fn bitcoin_units::locktime::absolute::Height::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
 pub fn bitcoin_units::locktime::absolute::Height::clone(&self) -> bitcoin_units::locktime::absolute::Height
 pub fn bitcoin_units::locktime::absolute::Height::cmp(&self, other: &bitcoin_units::locktime::absolute::Height) -> core::cmp::Ordering
 pub fn bitcoin_units::locktime::absolute::Height::deserialize<D>(deserializer: D) -> core::result::Result<Self, <D as serde::de::Deserializer>::Error> where D: serde::de::Deserializer<'de>
@@ -1029,6 +1040,7 @@ pub fn bitcoin_units::locktime::absolute::ParseTimeError::clone(&self) -> bitcoi
 pub fn bitcoin_units::locktime::absolute::ParseTimeError::eq(&self, other: &bitcoin_units::locktime::absolute::ParseTimeError) -> bool
 pub fn bitcoin_units::locktime::absolute::ParseTimeError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
 pub fn bitcoin_units::locktime::absolute::ParseTimeError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
+pub fn bitcoin_units::locktime::absolute::Time::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
 pub fn bitcoin_units::locktime::absolute::Time::clone(&self) -> bitcoin_units::locktime::absolute::Time
 pub fn bitcoin_units::locktime::absolute::Time::cmp(&self, other: &bitcoin_units::locktime::absolute::Time) -> core::cmp::Ordering
 pub fn bitcoin_units::locktime::absolute::Time::deserialize<D>(deserializer: D) -> core::result::Result<Self, <D as serde::de::Deserializer>::Error> where D: serde::de::Deserializer<'de>
@@ -1042,6 +1054,7 @@ pub fn bitcoin_units::locktime::absolute::Time::serialize<S>(&self, serializer:
 pub fn bitcoin_units::locktime::absolute::Time::try_from(s: &str) -> core::result::Result<Self, Self::Error>
 pub fn bitcoin_units::locktime::absolute::Time::try_from(s: alloc::boxed::Box<str>) -> core::result::Result<Self, Self::Error>
 pub fn bitcoin_units::locktime::absolute::Time::try_from(s: alloc::string::String) -> core::result::Result<Self, Self::Error>
+pub fn bitcoin_units::locktime::relative::Height::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
 pub fn bitcoin_units::locktime::relative::Height::clone(&self) -> bitcoin_units::locktime::relative::Height
 pub fn bitcoin_units::locktime::relative::Height::cmp(&self, other: &bitcoin_units::locktime::relative::Height) -> core::cmp::Ordering
 pub fn bitcoin_units::locktime::relative::Height::default() -> bitcoin_units::locktime::relative::Height
@@ -1057,6 +1070,7 @@ pub fn bitcoin_units::locktime::relative::Height::try_from(h: bitcoin_units::blo
 pub fn bitcoin_units::locktime::relative::Height::try_from(s: &str) -> core::result::Result<Self, Self::Error>
 pub fn bitcoin_units::locktime::relative::Height::try_from(s: alloc::boxed::Box<str>) -> core::result::Result<Self, Self::Error>
 pub fn bitcoin_units::locktime::relative::Height::try_from(s: alloc::string::String) -> core::result::Result<Self, Self::Error>
+pub fn bitcoin_units::locktime::relative::Time::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
 pub fn bitcoin_units::locktime::relative::Time::clone(&self) -> bitcoin_units::locktime::relative::Time
 pub fn bitcoin_units::locktime::relative::Time::cmp(&self, other: &bitcoin_units::locktime::relative::Time) -> core::cmp::Ordering
 pub fn bitcoin_units::locktime::relative::Time::default() -> bitcoin_units::locktime::relative::Time
diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs
index c25e78a1a..025a86616 100644
--- a/units/src/amount/mod.rs
+++ b/units/src/amount/mod.rs
@@ -20,6 +20,9 @@ use core::cmp::Ordering;
 use core::fmt;
 use core::str::FromStr;
 
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+
 use self::error::{MissingDigitsKind, ParseAmountErrorInner, ParseErrorInner};
 
 #[rustfmt::skip]                // Keep public re-exports separate.
@@ -599,3 +602,18 @@ mod sealed {
     impl<T> Sealed<Amount> for T where T: Iterator<Item = Amount> {}
     impl<T> Sealed<SignedAmount> for T where T: Iterator<Item = SignedAmount> {}
 }
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Denomination {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=5)?;
+        match choice {
+            0 => Ok(Denomination::Bitcoin),
+            1 => Ok(Denomination::CentiBitcoin),
+            2 => Ok(Denomination::MilliBitcoin),
+            3 => Ok(Denomination::MicroBitcoin),
+            4 => Ok(Denomination::Bit),
+            _ => Ok(Denomination::Satoshi),
+        }
+    }
+}
diff --git a/units/src/block.rs b/units/src/block.rs
index bdbf886b0..f0abe5c48 100644
--- a/units/src/block.rs
+++ b/units/src/block.rs
@@ -13,6 +13,8 @@
 
 use core::{fmt, ops};
 
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
 
@@ -248,6 +250,30 @@ impl<'a> core::iter::Sum<&'a BlockInterval> for BlockInterval {
     }
 }
 
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for BlockHeight {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=2)?;
+        match choice {
+            0 => Ok(BlockHeight::MIN),
+            1 => Ok(BlockHeight::MAX),
+            _ => Ok(BlockHeight::from_u32(u32::arbitrary(u)?)),
+        }
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for BlockInterval {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=2)?;
+        match choice {
+            0 => Ok(BlockInterval::MIN),
+            1 => Ok(BlockInterval::MAX),
+            _ => Ok(BlockInterval::from_u32(u32::arbitrary(u)?)),
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/units/src/fee_rate.rs b/units/src/fee_rate.rs
index d6b4c33cb..0c329ac5a 100644
--- a/units/src/fee_rate.rs
+++ b/units/src/fee_rate.rs
@@ -162,14 +162,6 @@ impl FeeRate {
     }
 }
 
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for FeeRate {
-    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
-        let f = u64::arbitrary(u)?;
-        Ok(FeeRate(f))
-    }
-}
-
 /// Alternative will display the unit.
 impl fmt::Display for FeeRate {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -294,6 +286,20 @@ impl<'a> core::iter::Sum<&'a FeeRate> for FeeRate {
 
 crate::impl_parse_str_from_int_infallible!(FeeRate, u64, from_sat_per_kwu);
 
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for FeeRate {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=4)?;
+        match choice {
+            0 => Ok(FeeRate::MIN),
+            1 => Ok(FeeRate::BROADCAST_MIN),
+            2 => Ok(FeeRate::DUST),
+            3 => Ok(FeeRate::MAX),
+            _ => Ok(FeeRate(u64::arbitrary(u)?)),
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/units/src/locktime/absolute.rs b/units/src/locktime/absolute.rs
index 4ee23e3dd..b9a0a9e0e 100644
--- a/units/src/locktime/absolute.rs
+++ b/units/src/locktime/absolute.rs
@@ -4,6 +4,8 @@
 
 use core::fmt;
 
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
 use internals::error::InputString;
 
 use crate::parse::{self, ParseIntError};
@@ -384,6 +386,40 @@ impl From<ConversionError> for ParseError {
     fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
 }
 
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Height {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=2)?;
+        match choice {
+            0 => Ok(Height::MIN),
+            1 => Ok(Height::MAX),
+            _ => {
+                let min = Height::MIN.to_consensus_u32();
+                let max = Height::MAX.to_consensus_u32();
+                let h = u.int_in_range(min..=max)?;
+                Ok(Height::from_consensus(h).unwrap())
+            }
+        }
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Time {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=2)?;
+        match choice {
+            0 => Ok(Time::MIN),
+            1 => Ok(Time::MAX),
+            _ => {
+                let min = Time::MIN.to_consensus_u32();
+                let max = Time::MAX.to_consensus_u32();
+                let t = u.int_in_range(min..=max)?;
+                Ok(Time::from_consensus(t).unwrap())
+            }
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     #[cfg(feature = "serde")]
diff --git a/units/src/locktime/relative.rs b/units/src/locktime/relative.rs
index 401ac2ced..39796ab6b 100644
--- a/units/src/locktime/relative.rs
+++ b/units/src/locktime/relative.rs
@@ -4,6 +4,8 @@
 
 use core::fmt;
 
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
 
@@ -159,6 +161,32 @@ impl fmt::Display for TimeOverflowError {
 #[cfg(feature = "std")]
 impl std::error::Error for TimeOverflowError {}
 
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Height {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=2)?;
+
+        match choice {
+            0 => Ok(Height::MIN),
+            1 => Ok(Height::MAX),
+            _ => Ok(Height::from_height(u16::arbitrary(u)?)),
+        }
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Time {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let choice = u.int_in_range(0..=2)?;
+
+        match choice {
+            0 => Ok(Time::MIN),
+            1 => Ok(Time::MAX),
+            _ => Ok(Time::from_512_second_intervals(u16::arbitrary(u)?)),
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;


Test patch:
diff --git a/units/tests/api.rs b/units/tests/api.rs
index 20746a3a8..44eee3d9c 100644
--- a/units/tests/api.rs
+++ b/units/tests/api.rs
@@ -25,6 +25,8 @@ use bitcoin_units::{
     amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, FeeRate,
     SignedAmount, Weight,
 };
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
 
 /// A struct that includes all public non-error enums.
 #[derive(Debug)] // All public types implement Debug (C-DEBUG).
@@ -263,3 +265,42 @@ fn regression_default() {
     };
     assert_eq!(got, want);
 }
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Types {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let a = Types { a: Enums::arbitrary(u)?, b: Structs::arbitrary(u)? };
+        Ok(a)
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Structs {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let a = Structs {
+            a: Amount::arbitrary(u)?,
+            // Skip the `Display` type.
+            b: Amount::MAX.display_in(amount::Denomination::Bitcoin),
+            c: SignedAmount::arbitrary(u)?,
+            d: BlockHeight::arbitrary(u)?,
+            e: BlockInterval::arbitrary(u)?,
+            f: FeeRate::arbitrary(u)?,
+            g: absolute::Height::arbitrary(u)?,
+            h: absolute::Time::arbitrary(u)?,
+            i: relative::Height::arbitrary(u)?,
+            j: relative::Time::arbitrary(u)?,
+            k: Weight::arbitrary(u)?,
+        };
+        Ok(a)
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Enums {
+    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+        let a = Enums {
+            a: amount::Denomination::arbitrary(u)?,
+        };
+        Ok(a)
+    }
+}