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::{
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<Vec<Arc<Self>>> {
) -> BoxedStrategy<SummaryDebug<Vec<Arc<Self>>>> {
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()
}

View File

@ -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<T>(pub 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 fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, len={}", std::any::type_name::<T>(), self.0.len())
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> fmt::Debug for SummaryDebug<&Vec<T>> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, len={}", std::any::type_name::<T>(), self.0.len())
impl<T> From<T> for DisplayToDebug<T> {
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<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 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<Self> {
(
transparent::Input::vec_strategy(ledger_state, 10),
vec(any::<transparent::Output>(), 0..10),
transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(),
)
.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<Self> {
(
transparent::Input::vec_strategy(ledger_state, 10),
vec(any::<transparent::Output>(), 0..10),
transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(),
option::of(any::<JoinSplitData<Bctv14Proof>>()),
)
@ -59,8 +65,8 @@ impl Transaction {
/// Generate a proptest strategy for V3 Transactions
pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
(
transparent::Input::vec_strategy(ledger_state, 10),
vec(any::<transparent::Output>(), 0..10),
transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(),
any::<block::Height>(),
option::of(any::<JoinSplitData<Bctv14Proof>>()),
@ -80,8 +86,8 @@ impl Transaction {
/// Generate a proptest strategy for V4 Transactions
pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
(
transparent::Input::vec_strategy(ledger_state, 10),
vec(any::<transparent::Output>(), 0..10),
transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
any::<LockTime>(),
any::<block::Height>(),
option::of(any::<JoinSplitData<Groth16Proof>>()),
@ -113,8 +119,8 @@ impl Transaction {
NetworkUpgrade::branch_id_strategy(),
any::<LockTime>(),
any::<block::Height>(),
transparent::Input::vec_strategy(ledger_state, 10),
vec(any::<transparent::Output>(), 0..10),
transparent::Input::vec_strategy(ledger_state, MAX_ARBITRARY_ITEMS),
vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
option::of(any::<sapling::ShieldedData<sapling::SharedAnchor>>()),
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 {
(
any::<sprout::JoinSplit<P>>(),
vec(any::<sprout::JoinSplit<P>>(), 0..10),
vec(any::<sprout::JoinSplit<P>>(), 0..MAX_ARBITRARY_ITEMS),
array::uniform32(any::<u8>()),
vec(any::<u8>(), 64),
)
@ -256,8 +262,11 @@ impl Arbitrary for sapling::TransferData<PerSpendAnchor> {
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
// TODO: add an extra spend or output using Either, and stop using filter_map
(
vec(any::<sapling::Spend<PerSpendAnchor>>(), 0..10),
vec(any::<sapling::Output>(), 0..10),
vec(
any::<sapling::Spend<PerSpendAnchor>>(),
0..MAX_ARBITRARY_ITEMS,
),
vec(any::<sapling::Output>(), 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<SharedAnchor> {
// TODO: add an extra spend or output using Either, and stop using filter_map
(
any::<sapling::tree::Root>(),
vec(any::<sapling::Spend<SharedAnchor>>(), 0..10),
vec(any::<sapling::Output>(), 0..10),
vec(
any::<sapling::Spend<SharedAnchor>>(),
0..MAX_ARBITRARY_ITEMS,
),
vec(any::<sapling::Output>(), 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::<Amount>(),
any::<orchard::tree::Root>(),
any::<Halo2Proof>(),
vec(any::<orchard::shielded_data::AuthorizedAction>(), 1..10),
vec(
any::<orchard::shielded_data::AuthorizedAction>(),
1..MAX_ARBITRARY_ITEMS,
),
any::<Signature<Binding>>(),
)
.prop_map(

View File

@ -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 { .. });

View File

@ -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<Vec<PreparedBlock>>,
chain: Arc<SummaryDebug<Vec<PreparedBlock>>>,
count: BinarySearch,
network: Network,
}
impl ValueTree for PreparedChainTree {
type Value = (
Arc<Vec<PreparedBlock>>,
Arc<SummaryDebug<Vec<PreparedBlock>>>,
<BinarySearch as ValueTree>::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<Option<(Network, Arc<Vec<PreparedBlock>>)>>,
chain: std::sync::Mutex<Option<(Network, Arc<SummaryDebug<Vec<PreparedBlock>>>)>>,
}
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<_>>(),
vec.iter()
.map(|blk| blk.clone().prepare())
.collect::<Vec<_>>(),
)
})
.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");