]> Untitled Git - bdk/commitdiff
test(chain): introduce proptest for `CheckPoint::range`
author志宇 <hello@evanlinjin.me>
Fri, 5 Apr 2024 08:43:13 +0000 (16:43 +0800)
committer志宇 <hello@evanlinjin.me>
Thu, 11 Apr 2024 13:43:21 +0000 (21:43 +0800)
Ensure that `CheckPoint::range` returns expected values by comparing
against values returned from a primitive approach.

I think it is a good idea to start writing proptests for this crate.

.github/workflows/cont_integration.yml
README.md
crates/chain/Cargo.toml
crates/chain/tests/test_local_chain.rs

index f7af6d054c943132a5a540a4f098c08d1a3f3dca..6a4df0a81c501c99b1414bf8ebbc18d6fe63b559 100644 (file)
@@ -33,6 +33,7 @@ jobs:
           cargo update -p zstd-sys --precise "2.0.8+zstd.1.5.5"
           cargo update -p time --precise "0.3.20"
           cargo update -p home --precise "0.5.5"
+          cargo update -p proptest --precise "1.2.0"
       - name: Build
         run: cargo build ${{ matrix.features }}
       - name: Test
index a8c18d1aa6ef6e7bd4e80255e71ef565fac00ac4..d3222430854972d7544505934afb4f6047489e4f 100644 (file)
--- a/README.md
+++ b/README.md
@@ -75,6 +75,8 @@ cargo update -p time --precise "0.3.20"
 cargo update -p jobserver --precise "0.1.26"
 # home 0.5.9 has MSRV 1.70.0
 cargo update -p home --precise "0.5.5"
+# proptest 1.4.0 has MSRV 1.65.0
+cargo update -p proptest --precise "1.2.0"
 ```
 
 ## License
index 6c5a59915d9a32ceea5f288486a1cae11c63432e..5b4370e239fd692cb68053ef39d93bea4668fed0 100644 (file)
@@ -23,6 +23,7 @@ miniscript = { version = "10.0.0", optional = true, default-features = false }
 
 [dev-dependencies]
 rand = "0.8"
+proptest = "1.2.0"
 
 [features]
 default = ["std"]
index 482792f5012ac2778a97e9877ab22322c7a20e6e..70a0890161e8091d3523e7f2b04dd0671fd13eff 100644 (file)
@@ -1,3 +1,5 @@
+use std::ops::{Bound, RangeBounds};
+
 use bdk_chain::{
     local_chain::{
         AlterCheckPointError, ApplyHeaderError, CannotConnectError, ChangeSet, CheckPoint,
@@ -6,6 +8,7 @@ use bdk_chain::{
     BlockId,
 };
 use bitcoin::{block::Header, hashes::Hash, BlockHash};
+use proptest::prelude::*;
 
 #[macro_use]
 mod common;
@@ -725,3 +728,48 @@ fn local_chain_apply_header_connected_to() {
         assert_eq!(result, exp_result, "[{}:{}] unexpected result", i, t.name);
     }
 }
+
+fn generate_height_range_bounds(
+    height_upper_bound: u32,
+) -> impl Strategy<Value = (Bound<u32>, Bound<u32>)> {
+    fn generate_height_bound(height_upper_bound: u32) -> impl Strategy<Value = Bound<u32>> {
+        prop_oneof![
+            (0..height_upper_bound).prop_map(Bound::Included),
+            (0..height_upper_bound).prop_map(Bound::Excluded),
+            Just(Bound::Unbounded),
+        ]
+    }
+    (
+        generate_height_bound(height_upper_bound),
+        generate_height_bound(height_upper_bound),
+    )
+}
+
+fn generate_checkpoints(max_height: u32, max_count: usize) -> impl Strategy<Value = CheckPoint> {
+    proptest::collection::btree_set(1..max_height, 0..max_count).prop_map(|mut heights| {
+        heights.insert(0); // must have genesis
+        CheckPoint::from_block_ids(heights.into_iter().map(|height| {
+            let hash = bitcoin::hashes::Hash::hash(height.to_le_bytes().as_slice());
+            BlockId { height, hash }
+        }))
+        .expect("blocks must be in order as it comes from btreeset")
+    })
+}
+
+proptest! {
+    #![proptest_config(ProptestConfig {
+        ..Default::default()
+    })]
+
+    /// Ensure that [`CheckPoint::range`] returns the expected checkpoint heights by comparing it
+    /// against a more primitive approach.
+    #[test]
+    fn checkpoint_range(
+        range in generate_height_range_bounds(21_000),
+        cp in generate_checkpoints(21_000, 2100)
+    ) {
+        let exp_heights = cp.iter().map(|cp| cp.height()).filter(|h| range.contains(h)).collect::<Vec<u32>>();
+        let heights = cp.range(range).map(|cp| cp.height()).collect::<Vec<u32>>();
+        prop_assert_eq!(heights, exp_heights);
+    }
+}