From 446b045161ebad3fbdf0ea96e8ff6f9da8ad212c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 5 Apr 2024 16:43:13 +0800 Subject: [PATCH] test(chain): introduce proptest for `CheckPoint::range` 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 | 1 + README.md | 2 ++ crates/chain/Cargo.toml | 1 + crates/chain/tests/test_local_chain.rs | 48 ++++++++++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index f7af6d054..6a4df0a81 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -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 diff --git a/README.md b/README.md index a8c18d1aa..d32224308 100644 --- 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 diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml index 6c5a59915..5b4370e23 100644 --- a/crates/chain/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -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"] diff --git a/crates/chain/tests/test_local_chain.rs b/crates/chain/tests/test_local_chain.rs index 482792f50..70a089016 100644 --- a/crates/chain/tests/test_local_chain.rs +++ b/crates/chain/tests/test_local_chain.rs @@ -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, Bound)> { + fn generate_height_bound(height_upper_bound: u32) -> impl Strategy> { + 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 { + 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::>(); + let heights = cp.range(range).map(|cp| cp.height()).collect::>(); + prop_assert_eq!(heights, exp_heights); + } +}