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
This commit is contained in:
teor 2021-06-02 23:18:04 +10:00 committed by GitHub
parent db0cdb74ff
commit 35f097995b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 36 deletions

View File

@ -7,6 +7,7 @@ use std::sync::Arc;
use crate::{ use crate::{
block, block,
fmt::SummaryDebug,
parameters::{Network, NetworkUpgrade, GENESIS_PREVIOUS_BLOCK_HASH}, parameters::{Network, NetworkUpgrade, GENESIS_PREVIOUS_BLOCK_HASH},
serialization, serialization,
work::{difficulty::CompactDifficulty, equihash}, work::{difficulty::CompactDifficulty, equihash},
@ -249,7 +250,7 @@ impl Block {
pub fn partial_chain_strategy( pub fn partial_chain_strategy(
mut current: LedgerState, mut current: LedgerState,
count: usize, count: usize,
) -> BoxedStrategy<Vec<Arc<Self>>> { ) -> BoxedStrategy<SummaryDebug<Vec<Arc<Self>>>> {
let mut vec = Vec::with_capacity(count); let mut vec = Vec::with_capacity(count);
// generate block strategies with the correct heights // generate block strategies with the correct heights
@ -267,7 +268,7 @@ impl Block {
} }
previous_block_hash = Some(block.hash()); previous_block_hash = Some(block.hash());
} }
vec.into_iter().map(Arc::new).collect() SummaryDebug(vec.into_iter().map(Arc::new).collect())
}) })
.boxed() .boxed()
} }

View File

@ -1,7 +1,9 @@
//! Format wrappers for Zebra //! 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<T>(pub T); pub struct DisplayToDebug<T>(pub T);
impl<T> fmt::Debug for DisplayToDebug<T> impl<T> fmt::Debug for DisplayToDebug<T>
@ -13,16 +15,65 @@ where
} }
} }
pub struct SummaryDebug<T>(pub T); impl<T> ops::Deref for DisplayToDebug<T> {
type Target = T;
impl<T> fmt::Debug for SummaryDebug<Vec<T>> { fn deref(&self) -> &Self::Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { &self.0
write!(f, "{}, len={}", std::any::type_name::<T>(), self.0.len())
} }
} }
impl<T> fmt::Debug for SummaryDebug<&Vec<T>> { impl<T> From<T> for DisplayToDebug<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn from(t: T) -> Self {
write!(f, "{}, len={}", std::any::type_name::<T>(), self.0.len()) 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<CollectionOrIter>(pub CollectionOrIter);
impl<CollectionOrIter> fmt::Debug for SummaryDebug<CollectionOrIter>
where
CollectionOrIter: IntoIterator + Clone,
<CollectionOrIter as IntoIterator>::IntoIter: ExactSizeIterator,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}<{}>, len={}",
std::any::type_name::<CollectionOrIter>(),
std::any::type_name::<<CollectionOrIter as IntoIterator>::Item>(),
self.0.clone().into_iter().len()
)
}
}
impl<CollectionOrIter> ops::Deref for SummaryDebug<CollectionOrIter> {
type Target = CollectionOrIter;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<CollectionOrIter> From<CollectionOrIter> for SummaryDebug<CollectionOrIter> {
fn from(collection: CollectionOrIter) -> Self {
Self(collection)
}
}
impl<CollectionOrIter> IntoIterator for SummaryDebug<CollectionOrIter>
where
CollectionOrIter: IntoIterator,
{
type Item = <CollectionOrIter as IntoIterator>::Item;
type IntoIter = <CollectionOrIter as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
} }
} }

View File

@ -21,12 +21,18 @@ use itertools::Itertools;
use super::{FieldNotPresent, JoinSplitData, LockTime, Memo, Transaction}; use super::{FieldNotPresent, JoinSplitData, LockTime, Memo, Transaction};
use sapling::{AnchorVariant, PerSpendAnchor, SharedAnchor}; 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 { impl Transaction {
/// Generate a proptest strategy for V1 Transactions /// Generate a proptest strategy for V1 Transactions
pub fn v1_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> { pub fn v1_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
( (
transparent::Input::vec_strategy(ledger_state, 10), transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..10), vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(), any::<LockTime>(),
) )
.prop_map(|(inputs, outputs, lock_time)| Transaction::V1 { .prop_map(|(inputs, outputs, lock_time)| Transaction::V1 {
@ -40,8 +46,8 @@ impl Transaction {
/// Generate a proptest strategy for V2 Transactions /// Generate a proptest strategy for V2 Transactions
pub fn v2_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> { pub fn v2_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
( (
transparent::Input::vec_strategy(ledger_state, 10), transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..10), vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(), any::<LockTime>(),
option::of(any::<JoinSplitData<Bctv14Proof>>()), option::of(any::<JoinSplitData<Bctv14Proof>>()),
) )
@ -59,8 +65,8 @@ impl Transaction {
/// Generate a proptest strategy for V3 Transactions /// Generate a proptest strategy for V3 Transactions
pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> { pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
( (
transparent::Input::vec_strategy(ledger_state, 10), transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..10), vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(), any::<LockTime>(),
any::<block::Height>(), any::<block::Height>(),
option::of(any::<JoinSplitData<Bctv14Proof>>()), option::of(any::<JoinSplitData<Bctv14Proof>>()),
@ -80,8 +86,8 @@ impl Transaction {
/// Generate a proptest strategy for V4 Transactions /// Generate a proptest strategy for V4 Transactions
pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> { pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
( (
transparent::Input::vec_strategy(ledger_state, 10), transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..10), vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(), any::<LockTime>(),
any::<block::Height>(), any::<block::Height>(),
option::of(any::<JoinSplitData<Groth16Proof>>()), option::of(any::<JoinSplitData<Groth16Proof>>()),
@ -113,8 +119,8 @@ impl Transaction {
NetworkUpgrade::branch_id_strategy(), NetworkUpgrade::branch_id_strategy(),
any::<LockTime>(), any::<LockTime>(),
any::<block::Height>(), any::<block::Height>(),
transparent::Input::vec_strategy(ledger_state, 10), transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..10), vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
option::of(any::<sapling::ShieldedData<sapling::SharedAnchor>>()), option::of(any::<sapling::ShieldedData<sapling::SharedAnchor>>()),
option::of(any::<orchard::ShieldedData>()), option::of(any::<orchard::ShieldedData>()),
) )
@ -202,7 +208,7 @@ impl<P: ZkSnarkProof + Arbitrary + 'static> Arbitrary for JoinSplitData<P> {
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
( (
any::<sprout::JoinSplit<P>>(), any::<sprout::JoinSplit<P>>(),
vec(any::<sprout::JoinSplit<P>>(), 0..10), vec(any::<sprout::JoinSplit<P>>(), 0..MAX_ARBITRARY_ITEMS),
array::uniform32(any::<u8>()), array::uniform32(any::<u8>()),
vec(any::<u8>(), 64), vec(any::<u8>(), 64),
) )
@ -256,8 +262,11 @@ impl Arbitrary for sapling::TransferData<PerSpendAnchor> {
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
// TODO: add an extra spend or output using Either, and stop using filter_map // TODO: add an extra spend or output using Either, and stop using filter_map
( (
vec(any::<sapling::Spend<PerSpendAnchor>>(), 0..10), vec(
vec(any::<sapling::Output>(), 0..10), any::<sapling::Spend<PerSpendAnchor>>(),
0..MAX_ARBITRARY_ITEMS,
),
vec(any::<sapling::Output>(), 0..MAX_ARBITRARY_ITEMS),
) )
.prop_filter_map( .prop_filter_map(
"arbitrary v4 transfers with no spends and no outputs", "arbitrary v4 transfers with no spends and no outputs",
@ -290,8 +299,11 @@ impl Arbitrary for sapling::TransferData<SharedAnchor> {
// TODO: add an extra spend or output using Either, and stop using filter_map // TODO: add an extra spend or output using Either, and stop using filter_map
( (
any::<sapling::tree::Root>(), any::<sapling::tree::Root>(),
vec(any::<sapling::Spend<SharedAnchor>>(), 0..10), vec(
vec(any::<sapling::Output>(), 0..10), any::<sapling::Spend<SharedAnchor>>(),
0..MAX_ARBITRARY_ITEMS,
),
vec(any::<sapling::Output>(), 0..MAX_ARBITRARY_ITEMS),
) )
.prop_filter_map( .prop_filter_map(
"arbitrary v5 transfers with no spends and no outputs", "arbitrary v5 transfers with no spends and no outputs",
@ -326,7 +338,10 @@ impl Arbitrary for orchard::ShieldedData {
any::<Amount>(), any::<Amount>(),
any::<orchard::tree::Root>(), any::<orchard::tree::Root>(),
any::<Halo2Proof>(), any::<Halo2Proof>(),
vec(any::<orchard::shielded_data::AuthorizedAction>(), 1..10), vec(
any::<orchard::shielded_data::AuthorizedAction>(),
1..MAX_ARBITRARY_ITEMS,
),
any::<Signature<Binding>>(), any::<Signature<Binding>>(),
) )
.prop_map( .prop_map(

View File

@ -4,7 +4,7 @@
use zebra_test::prelude::*; use zebra_test::prelude::*;
use crate::{block, LedgerState}; use crate::{block, fmt::SummaryDebug, transaction::arbitrary::MAX_ARBITRARY_ITEMS, LedgerState};
use super::Input; use super::Input;
@ -27,11 +27,10 @@ fn coinbase_has_height() -> Result<()> {
fn input_coinbase_vecs_only_have_coinbase_input() -> Result<()> { fn input_coinbase_vecs_only_have_coinbase_input() -> Result<()> {
zebra_test::init(); zebra_test::init();
let max_size = 100;
let strategy = LedgerState::coinbase_strategy(None) 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(); let len = inputs.len();
for (ind, input) in inputs.into_iter().enumerate() { for (ind, input) in inputs.into_iter().enumerate() {
let is_coinbase = matches!(input, Input::Coinbase { .. }); let is_coinbase = matches!(input, Input::Coinbase { .. });

View File

@ -5,7 +5,7 @@ use proptest::{
}; };
use std::sync::Arc; 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 zebra_test::prelude::*;
use crate::tests::Prepare; use crate::tests::Prepare;
@ -16,14 +16,14 @@ const MAX_PARTIAL_CHAIN_BLOCKS: usize = 102;
#[derive(Debug)] #[derive(Debug)]
pub struct PreparedChainTree { pub struct PreparedChainTree {
chain: Arc<Vec<PreparedBlock>>, chain: Arc<SummaryDebug<Vec<PreparedBlock>>>,
count: BinarySearch, count: BinarySearch,
network: Network, network: Network,
} }
impl ValueTree for PreparedChainTree { impl ValueTree for PreparedChainTree {
type Value = ( type Value = (
Arc<Vec<PreparedBlock>>, Arc<SummaryDebug<Vec<PreparedBlock>>>,
<BinarySearch as ValueTree>::Value, <BinarySearch as ValueTree>::Value,
Network, Network,
); );
@ -44,7 +44,7 @@ impl ValueTree for PreparedChainTree {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct PreparedChain { pub struct PreparedChain {
// the proptests are threaded (not async), so we want to use a threaded mutex here // the proptests are threaded (not async), so we want to use a threaded mutex here
chain: std::sync::Mutex<Option<(Network, Arc<Vec<PreparedBlock>>)>>, chain: std::sync::Mutex<Option<(Network, Arc<SummaryDebug<Vec<PreparedBlock>>>)>>,
} }
impl Strategy for PreparedChain { impl Strategy for PreparedChain {
@ -67,12 +67,14 @@ impl Strategy for PreparedChain {
.prop_map(|(network, vec)| { .prop_map(|(network, vec)| {
( (
network, network,
vec.into_iter().map(|blk| blk.prepare()).collect::<Vec<_>>(), vec.iter()
.map(|blk| blk.clone().prepare())
.collect::<Vec<_>>(),
) )
}) })
.new_tree(runner)? .new_tree(runner)?
.current(); .current();
*chain = Some((network, Arc::new(blocks))); *chain = Some((network, Arc::new(SummaryDebug(blocks))));
} }
let chain = chain.clone().expect("should be generated"); let chain = chain.clone().expect("should be generated");