From 35f097995bfb24e6a7d58d15a66be362e272e5e1 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 2 Jun 2021 23:18:04 +1000 Subject: [PATCH] Make debugging easier on proptests with large vectors (#2232) * Restore SummaryDebug on arbitrary chains And also add it to some more proptest vectors. * Reduce most arbitrary vectors from 10 to 4 This makes debugging easier * Make SummaryDebug generic over collections and exact size iterators * Document DisplayToDebug --- zebra-chain/src/block/arbitrary.rs | 5 +- zebra-chain/src/fmt.rs | 67 ++++++++++++++++--- zebra-chain/src/transaction/arbitrary.rs | 47 ++++++++----- zebra-chain/src/transparent/prop.rs | 7 +- .../service/non_finalized_state/arbitrary.rs | 14 ++-- 5 files changed, 104 insertions(+), 36 deletions(-) diff --git a/zebra-chain/src/block/arbitrary.rs b/zebra-chain/src/block/arbitrary.rs index 8fb8f4980..0ce77130d 100644 --- a/zebra-chain/src/block/arbitrary.rs +++ b/zebra-chain/src/block/arbitrary.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use crate::{ block, + fmt::SummaryDebug, parameters::{Network, NetworkUpgrade, GENESIS_PREVIOUS_BLOCK_HASH}, serialization, work::{difficulty::CompactDifficulty, equihash}, @@ -249,7 +250,7 @@ impl Block { pub fn partial_chain_strategy( mut current: LedgerState, count: usize, - ) -> BoxedStrategy>> { + ) -> BoxedStrategy>>> { let mut vec = Vec::with_capacity(count); // generate block strategies with the correct heights @@ -267,7 +268,7 @@ impl Block { } previous_block_hash = Some(block.hash()); } - vec.into_iter().map(Arc::new).collect() + SummaryDebug(vec.into_iter().map(Arc::new).collect()) }) .boxed() } diff --git a/zebra-chain/src/fmt.rs b/zebra-chain/src/fmt.rs index 36f60e5eb..e0635eedd 100644 --- a/zebra-chain/src/fmt.rs +++ b/zebra-chain/src/fmt.rs @@ -1,7 +1,9 @@ //! Format wrappers for Zebra -use std::fmt; +use std::{fmt, ops}; +/// Wrapper to override `Debug`, redirecting it to the `Display` impl. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DisplayToDebug(pub T); impl fmt::Debug for DisplayToDebug @@ -13,16 +15,65 @@ where } } -pub struct SummaryDebug(pub T); +impl ops::Deref for DisplayToDebug { + type Target = T; -impl fmt::Debug for SummaryDebug> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}, len={}", std::any::type_name::(), self.0.len()) + fn deref(&self) -> &Self::Target { + &self.0 } } -impl fmt::Debug for SummaryDebug<&Vec> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}, len={}", std::any::type_name::(), self.0.len()) +impl From for DisplayToDebug { + fn from(t: T) -> Self { + Self(t) + } +} + +/// Wrapper to override `Debug` to display a shorter summary of the type. +/// +/// For collections and exact size iterators, it only displays the +/// collection/iterator type, the item type, and the length. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SummaryDebug(pub CollectionOrIter); + +impl fmt::Debug for SummaryDebug +where + CollectionOrIter: IntoIterator + Clone, + ::IntoIter: ExactSizeIterator, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}<{}>, len={}", + std::any::type_name::(), + std::any::type_name::<::Item>(), + self.0.clone().into_iter().len() + ) + } +} + +impl ops::Deref for SummaryDebug { + type Target = CollectionOrIter; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for SummaryDebug { + fn from(collection: CollectionOrIter) -> Self { + Self(collection) + } +} + +impl IntoIterator for SummaryDebug +where + CollectionOrIter: IntoIterator, +{ + type Item = ::Item; + type IntoIter = ::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() } } diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index 50fb318bb..850e80ab7 100644 --- a/zebra-chain/src/transaction/arbitrary.rs +++ b/zebra-chain/src/transaction/arbitrary.rs @@ -21,12 +21,18 @@ use itertools::Itertools; use super::{FieldNotPresent, JoinSplitData, LockTime, Memo, Transaction}; use sapling::{AnchorVariant, PerSpendAnchor, SharedAnchor}; +/// The maximum number of arbitrary transactions, inputs, or outputs. +/// +/// This size is chosen to provide interesting behaviour, but not be too large +/// for debugging. +pub const MAX_ARBITRARY_ITEMS: usize = 4; + impl Transaction { /// Generate a proptest strategy for V1 Transactions pub fn v1_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - transparent::Input::vec_strategy(ledger_state, 10), - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS), + vec(any::(), 0..MAX_ARBITRARY_ITEMS), any::(), ) .prop_map(|(inputs, outputs, lock_time)| Transaction::V1 { @@ -40,8 +46,8 @@ impl Transaction { /// Generate a proptest strategy for V2 Transactions pub fn v2_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - transparent::Input::vec_strategy(ledger_state, 10), - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS), + vec(any::(), 0..MAX_ARBITRARY_ITEMS), any::(), option::of(any::>()), ) @@ -59,8 +65,8 @@ impl Transaction { /// Generate a proptest strategy for V3 Transactions pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - transparent::Input::vec_strategy(ledger_state, 10), - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS), + vec(any::(), 0..MAX_ARBITRARY_ITEMS), any::(), any::(), option::of(any::>()), @@ -80,8 +86,8 @@ impl Transaction { /// Generate a proptest strategy for V4 Transactions pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy { ( - transparent::Input::vec_strategy(ledger_state, 10), - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS), + vec(any::(), 0..MAX_ARBITRARY_ITEMS), any::(), any::(), option::of(any::>()), @@ -113,8 +119,8 @@ impl Transaction { NetworkUpgrade::branch_id_strategy(), any::(), any::(), - transparent::Input::vec_strategy(ledger_state, 10), - vec(any::(), 0..10), + transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS), + vec(any::(), 0..MAX_ARBITRARY_ITEMS), option::of(any::>()), option::of(any::()), ) @@ -202,7 +208,7 @@ impl Arbitrary for JoinSplitData

{ fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( any::>(), - vec(any::>(), 0..10), + vec(any::>(), 0..MAX_ARBITRARY_ITEMS), array::uniform32(any::()), vec(any::(), 64), ) @@ -256,8 +262,11 @@ impl Arbitrary for sapling::TransferData { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { // TODO: add an extra spend or output using Either, and stop using filter_map ( - vec(any::>(), 0..10), - vec(any::(), 0..10), + vec( + any::>(), + 0..MAX_ARBITRARY_ITEMS, + ), + vec(any::(), 0..MAX_ARBITRARY_ITEMS), ) .prop_filter_map( "arbitrary v4 transfers with no spends and no outputs", @@ -290,8 +299,11 @@ impl Arbitrary for sapling::TransferData { // TODO: add an extra spend or output using Either, and stop using filter_map ( any::(), - vec(any::>(), 0..10), - vec(any::(), 0..10), + vec( + any::>(), + 0..MAX_ARBITRARY_ITEMS, + ), + vec(any::(), 0..MAX_ARBITRARY_ITEMS), ) .prop_filter_map( "arbitrary v5 transfers with no spends and no outputs", @@ -326,7 +338,10 @@ impl Arbitrary for orchard::ShieldedData { any::(), any::(), any::(), - vec(any::(), 1..10), + vec( + any::(), + 1..MAX_ARBITRARY_ITEMS, + ), any::>(), ) .prop_map( diff --git a/zebra-chain/src/transparent/prop.rs b/zebra-chain/src/transparent/prop.rs index 7675c5e6b..6103e1a82 100644 --- a/zebra-chain/src/transparent/prop.rs +++ b/zebra-chain/src/transparent/prop.rs @@ -4,7 +4,7 @@ use zebra_test::prelude::*; -use crate::{block, LedgerState}; +use crate::{block, fmt::SummaryDebug, transaction::arbitrary::MAX_ARBITRARY_ITEMS, LedgerState}; use super::Input; @@ -27,11 +27,10 @@ fn coinbase_has_height() -> Result<()> { fn input_coinbase_vecs_only_have_coinbase_input() -> Result<()> { zebra_test::init(); - let max_size = 100; let strategy = LedgerState::coinbase_strategy(None) - .prop_flat_map(|ledger_state| Input::vec_strategy(ledger_state, max_size)); + .prop_flat_map(|ledger_state| Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS)); - proptest!(|(inputs in strategy)| { + proptest!(|(inputs in strategy.prop_map(SummaryDebug))| { let len = inputs.len(); for (ind, input) in inputs.into_iter().enumerate() { let is_coinbase = matches!(input, Input::Coinbase { .. }); diff --git a/zebra-state/src/service/non_finalized_state/arbitrary.rs b/zebra-state/src/service/non_finalized_state/arbitrary.rs index eb16d1558..d00f6ae10 100644 --- a/zebra-state/src/service/non_finalized_state/arbitrary.rs +++ b/zebra-state/src/service/non_finalized_state/arbitrary.rs @@ -5,7 +5,7 @@ use proptest::{ }; use std::sync::Arc; -use zebra_chain::{block::Block, parameters::NetworkUpgrade::Nu5, LedgerState}; +use zebra_chain::{block::Block, fmt::SummaryDebug, parameters::NetworkUpgrade::Nu5, LedgerState}; use zebra_test::prelude::*; use crate::tests::Prepare; @@ -16,14 +16,14 @@ const MAX_PARTIAL_CHAIN_BLOCKS: usize = 102; #[derive(Debug)] pub struct PreparedChainTree { - chain: Arc>, + chain: Arc>>, count: BinarySearch, network: Network, } impl ValueTree for PreparedChainTree { type Value = ( - Arc>, + Arc>>, ::Value, Network, ); @@ -44,7 +44,7 @@ impl ValueTree for PreparedChainTree { #[derive(Debug, Default)] pub struct PreparedChain { // the proptests are threaded (not async), so we want to use a threaded mutex here - chain: std::sync::Mutex>)>>, + chain: std::sync::Mutex>>)>>, } impl Strategy for PreparedChain { @@ -67,12 +67,14 @@ impl Strategy for PreparedChain { .prop_map(|(network, vec)| { ( network, - vec.into_iter().map(|blk| blk.prepare()).collect::>(), + vec.iter() + .map(|blk| blk.clone().prepare()) + .collect::>(), ) }) .new_tree(runner)? .current(); - *chain = Some((network, Arc::new(blocks))); + *chain = Some((network, Arc::new(SummaryDebug(blocks)))); } let chain = chain.clone().expect("should be generated");