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:
parent
db0cdb74ff
commit
35f097995b
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 { .. });
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in New Issue