Repository: rust-bitcoin/rust-bitcoin
Pull request #4422 (2025-04-30 16:22:15)

units: Add regression tests for `Display` implementations
The output of Display should not change in stable crates for types that have well defined formatting and ones that implement FromStr. Error types do not need to be tested.  Discussed in #4415.

Patch 1 is a simple rename to allow for clearer naming when both height and time locktimes are tested.

Patch 2 adds the missing time locktime and denomination `Display` and round trip tests to the existing macro

Patch 3 adds all the other missing `Display` tests in `units`


Patch:
diff --git a/units/src/locktime/absolute.rs b/units/src/locktime/absolute.rs
index 153aa0e3..71f301e5 100644
--- a/units/src/locktime/absolute.rs
+++ b/units/src/locktime/absolute.rs
@@ -503,4 +503,15 @@ pub fn encode_decode_time() {
         serde_round_trip!(Time::MIN);
         serde_round_trip!(Time::MAX);
     }
+
+    #[test]
+    #[cfg(feature = "alloc")]
+    fn locktime_unit_display() {
+        use alloc::format;
+        let blocks = LockTimeUnit::Blocks;
+        let seconds = LockTimeUnit::Seconds;
+
+        assert_eq!(format!("{}", blocks), "expected lock-by-blockheight (must be < 500000000)");
+        assert_eq!(format!("{}", seconds), "expected lock-by-blocktime (must be >= 500000000)");
+    }
 }


Test patch:
diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs
index a9b19c13..8f93de9d 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -13,7 +13,7 @@
 use ::serde::{Deserialize, Serialize};
 
 use super::*;
-use crate::NumOpResult;
+use crate::{MathOp, NumOpResult};
 #[cfg(feature = "alloc")]
 use crate::{FeeRate, Weight};
 
@@ -120,6 +120,75 @@ fn from_int_btc() {
     assert_eq!(ssat(-200_000_000), amt);
 }
 
+#[test]
+#[cfg(feature = "alloc")]
+fn display_display_struct() {
+    let display_fixed_btc = Display {
+        sats_abs: 100_000_000,
+        is_negative: false,
+        style: DisplayStyle::FixedDenomination {
+            denomination: Denomination::Bitcoin,
+            show_denomination: true,
+        },
+    };
+    assert_eq!(format!("{}", display_fixed_btc), "1 BTC");
+
+    let display_fixed_sat = Display {
+        sats_abs: 1,
+        is_negative: false,
+        style: DisplayStyle::FixedDenomination {
+            denomination: Denomination::Satoshi,
+            show_denomination: true,
+        },
+    };
+    assert_eq!(format!("{}", display_fixed_sat), "1 satoshi");
+
+    let display_dynamic_btc = Display {
+        sats_abs: 100_000_000,
+        is_negative: false,
+        style: DisplayStyle::DynamicDenomination,
+    };
+    assert_eq!(format!("{}", display_dynamic_btc), "1 BTC");
+
+    let display_dynamic_sat = Display {
+        sats_abs: 99_999_999,
+        is_negative: false,
+        style: DisplayStyle::DynamicDenomination,
+    };
+    assert_eq!(format!("{}", display_dynamic_sat), "99999999 satoshi");
+
+    let display_negative_btc = Display {
+        sats_abs: 100_000_000,
+        is_negative: true,
+        style: DisplayStyle::DynamicDenomination,
+    };
+    assert_eq!(format!("{}", display_negative_btc), "-1 BTC");
+
+    let display_negative_sat = Display {
+        sats_abs: 99_999_999,
+        is_negative: true,
+        style: DisplayStyle::DynamicDenomination,
+    };
+    assert_eq!(format!("{}", display_negative_sat), "-99999999 satoshi");
+}
+
+#[test]
+#[cfg(feature = "alloc")]
+fn display_math_op() {
+    let cases = [
+        (MathOp::Add, "add"),
+        (MathOp::Sub, "sub"),
+        (MathOp::Mul, "mul"),
+        (MathOp::Div, "div"),
+        (MathOp::Rem, "rem"),
+        (MathOp::Neg, "neg"),
+    ];
+
+    for (op, expected) in cases {
+        assert_eq!(format!("{}", op), expected);
+    }
+}
+
 #[test]
 fn amount_try_from_signed_amount() {
     let sa_positive = ssat(123);
diff --git a/units/tests/str.rs b/units/tests/str.rs
index 0b365af2..98e06f72 100644
--- a/units/tests/str.rs
+++ b/units/tests/str.rs
@@ -3,6 +3,7 @@
 //! Do basic regression tests on the `Display` and `FromStr` impls.
 
 use bitcoin_units::locktime::{absolute, relative};
+use bitcoin_units::amount::Denomination;
 use bitcoin_units::{Amount, BlockHeight, BlockInterval, FeeRate, SignedAmount, Weight};
 
 macro_rules! check {
@@ -24,6 +25,9 @@ fn $test_name() {
     amount_unsigned_one_sat, Amount, Amount::ONE_SAT, "0.00000001 BTC";
     amount_unsigned_max_money, Amount, Amount::MAX, "21000000 BTC";
 
+    denomination_btc, Denomination, Denomination::BTC, "BTC";
+    denomination_sat, Denomination, Denomination::SAT, "satoshi";
+
     amount_signed_one_sat, SignedAmount, SignedAmount::ONE_SAT, "0.00000001 BTC";
     amount_signed_max_money, SignedAmount, SignedAmount::MAX, "21000000 BTC";
 
@@ -37,11 +41,17 @@ fn $test_name() {
     fee_rate_max, FeeRate, FeeRate::MAX, "18446744073709551615";
     fee_rate_dust, FeeRate, FeeRate::DUST, "750";
 
-    lock_time_absolute_min, absolute::Height, absolute::Height::MIN, "0";
-    lock_time_absolute_max, absolute::Height, absolute::Height::MAX, "499999999";
+    lock_by_height_absolute_min, absolute::Height, absolute::Height::MIN, "0";
+    lock_by_height_absolute_max, absolute::Height, absolute::Height::MAX, "499999999";
+
+    lock_by_height_relative_min, relative::Height, relative::Height::MIN, "0";
+    lock_by_height_relative_max, relative::Height, relative::Height::MAX, "65535";
+
+    lock_by_time_absolute_min, absolute::Time, absolute::Time::MIN, "500000000";
+    lock_by_time_absolute_max, absolute::Time, absolute::Time::MAX, "4294967295";
 
-    lock_time_relative_min, relative::Height, relative::Height::MIN, "0";
-    lock_time_relative_max, relative::Height, relative::Height::MAX, "65535";
+    lock_by_time_relative_min, relative::Time, relative::Time::MIN, "0";
+    lock_by_time_relative_max, relative::Time, relative::Time::MAX, "65535";
 
     weight_min, Weight, Weight::MIN, "0";
     weight_max, Weight, Weight::MAX, "18446744073709551615";