Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Testing] Introduce proptest property testing library and add type alises for hashbrown types #4477

Open
wants to merge 28 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
423855f
testing: introduce the crate to help with faking complex types
cylewitruk Mar 4, 2024
c89c917
fix: update faking methods to always use the received instance.
cylewitruk Mar 4, 2024
2d15f35
fix: update faking methods to always use the received 'Rng' instance.
cylewitruk Mar 4, 2024
9b63f36
added new 'StacksHashMap' and 'StacksHashSet' types to 'stacks-common'
cylewitruk Mar 4, 2024
a706834
wip: new clarity-db tests, fix remaining impls for 'fake'
cylewitruk Mar 4, 2024
cd7d30d
wip: moved fake to workspace dep
cylewitruk Mar 4, 2024
ff26172
wip: add fuzz-loop for tests using fake
cylewitruk Mar 4, 2024
7a468e8
chore: cargo fmt-stacks
cylewitruk Mar 4, 2024
b12c2c8
chore: update lockfile
cylewitruk Mar 4, 2024
ec807ed
chore: remove unnecessary include
cylewitruk Mar 4, 2024
fc62d41
wip: playing with propmap instead of fake
cylewitruk Mar 5, 2024
47b022f
feat(clarity-proptest): add a contract strategy stub
Acaccia Mar 5, 2024
bde7e77
fix: resolve DNS/hostnames for signer node_host value #4466
zone117x Mar 4, 2024
99a543a
wip: wrap hashbrown in fasade types
cylewitruk Mar 5, 2024
5a304c8
wip: wrap hashbrown in fasade types
cylewitruk Mar 5, 2024
80defd4
wip: fixes for StacksHashMap and StacksHashSet
cylewitruk Mar 5, 2024
23c26f0
wip: cargo fmt-stacks
cylewitruk Mar 5, 2024
aa37f32
wip: proptest vs fake
cylewitruk Mar 5, 2024
0bfd47d
wip: migrating from fake to proptest for generation
cylewitruk Mar 6, 2024
6f2ec6d
wip: fix for random usize within a range
cylewitruk Mar 6, 2024
290684a
replace fake with proptest
cylewitruk Mar 7, 2024
fce54d6
chore: cargo fmt-stacks
cylewitruk Mar 7, 2024
ec8ced6
chore: cleanup, fmt
cylewitruk Mar 7, 2024
f0b32a0
chore: rebase on next, cleanup & fmt
cylewitruk Mar 7, 2024
2f22c6c
added proptest strategy examples in stackslib + dependency issue + fm…
cylewitruk Mar 7, 2024
265ca00
wip: attempting to locate dkg timeout issue
cylewitruk Mar 7, 2024
ae14e59
revert wrapper hashmap/set types, caused problems in stackerlib
cylewitruk Mar 7, 2024
a53aeed
fmt-stacks
cylewitruk Mar 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
421 changes: 253 additions & 168 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Expand Up @@ -10,7 +10,8 @@ members = [
"contrib/tools/relay-server",
"libsigner",
"stacks-signer",
"testnet/stacks-node"]
"testnet/stacks-node"
]

# Dependencies we want to keep the same between workspace members
[workspace.dependencies]
Expand All @@ -21,6 +22,7 @@ rand = "0.8"
rand_chacha = "0.3.1"
tikv-jemallocator = "0.5.4"
wsts = { version = "8.1", default-features = false }
proptest = { version = "1.4.0" }

# Use a bit more than default optimization for
# dev builds to speed up test execution
Expand Down
12 changes: 7 additions & 5 deletions clarity/Cargo.toml
Expand Up @@ -30,7 +30,7 @@ slog = { version = "2.5.2", features = [ "max_level_trace" ] }
stacks_common = { package = "stacks-common", path = "../stacks-common" }
rstest = "0.17.0"
rstest_reuse = "0.5.0"
hashbrown = { workspace = true }
proptest = { workspace = true, optional = true }

[dependencies.serde_json]
version = "1.0"
Expand All @@ -46,13 +46,15 @@ features = ["std"]

[dev-dependencies]
assert-json-diff = "1.0.0"
# a nightly rustc regression (35dbef235 2021-03-02) prevents criterion from compiling
# but it isn't necessary for tests: only benchmarks. therefore, commenting out for now.
# criterion = "0.3"
clarity = { path = "./", features = ["testing"] }
# This ensures that `stacks-common` is built using the `testing` feature
# when we build this crate in dev/test. Note that the --features flag
# doesn't need to be provideed then.
stacks_common = { package = "stacks-common", path = "../stacks-common", features = ["testing"] }

[features]
default = []
developer-mode = []
slog_json = ["stacks_common/slog_json"]
testing = []
testing = ["dep:proptest", "stacks_common/testing"]

4 changes: 4 additions & 0 deletions clarity/src/libclarity.rs
Expand Up @@ -41,6 +41,10 @@ pub extern crate rstest_reuse;
#[macro_use]
extern crate stacks_common;

#[cfg(any(test, feature = "testing"))]
#[macro_use]
pub mod proptesting;

pub use stacks_common::{
codec, consts, impl_array_hexstring_fmt, impl_array_newtype, impl_byte_array_message_codec,
impl_byte_array_serde, types, util,
Expand Down
110 changes: 110 additions & 0 deletions clarity/src/proptesting/callables.rs
@@ -0,0 +1,110 @@
use proptest::prelude::*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing copyright and licensing comment block

use rand::distributions::uniform::SampleRange;
use serde::de::value;

use super::*;
use crate::vm::callables::{DefineType, DefinedFunction, FunctionIdentifier};
use crate::vm::database::{
DataMapMetadata, DataVariableMetadata, FungibleTokenMetadata, NonFungibleTokenMetadata,
};
use crate::vm::representations::TraitDefinition;
use crate::vm::types::FunctionSignature;

/// Returns a [`Strategy`] for randomly generating a [`FunctionIdentifier`] instance
/// representing a user-defined function.
pub fn function_identifier_user() -> impl Strategy<Value = FunctionIdentifier> {
(clarity_name(), clarity_name()).prop_map(|(name, context)| {
FunctionIdentifier::new_user_function(&context.to_string(), &name.to_string())
})
}

/// Returns a [`Strategy`] for randomly generating a [`FunctionIdentifier`] instance
/// representing a native function.
pub fn function_identifier_native() -> impl Strategy<Value = FunctionIdentifier> {
(clarity_name()).prop_map(|name| FunctionIdentifier::new_native_function(&name.to_string()))
}

/// Returns a [`Strategy`] for randomly generating a [`FunctionIdentifier`]
/// instance representing a function of any kind, user-defined or native.
pub fn function_identifier() -> impl Strategy<Value = FunctionIdentifier> {
prop_oneof![function_identifier_user(), function_identifier_native()]
}

/// Returns a [`Strategy`] for randomly generating a [`DefineType`] variant.
pub fn define_type() -> impl Strategy<Value = DefineType> {
prop_oneof![
Just(DefineType::Public),
Just(DefineType::Private),
Just(DefineType::ReadOnly)
]
}

/// Returns a [`Strategy`] for randomly generating a [`DataVariableMetadata`]
/// instance.
pub fn data_variable_metadata() -> impl Strategy<Value = DataVariableMetadata> {
type_signature().prop_map(|value_type| DataVariableMetadata { value_type })
}

/// Returns a [`Strategy`] for randomly generating a [`DataMapMetadata`] instance.
pub fn data_map_metadata() -> impl Strategy<Value = DataMapMetadata> {
(type_signature(), type_signature()).prop_map(|(key_type, value_type)| DataMapMetadata {
key_type,
value_type,
})
}

/// Returns a [`Strategy`] for randomly generating a [`NonFungibleTokenMetadata`]
/// instance.
pub fn nft_metadata() -> impl Strategy<Value = NonFungibleTokenMetadata> {
type_signature().prop_map(|key_type| NonFungibleTokenMetadata { key_type })
}

/// Returns a [`Strategy`] for randomly generating a [`FungibleTokenMetadata`]
/// instance.
pub fn ft_metadata() -> impl Strategy<Value = FungibleTokenMetadata> {
any::<Option<u128>>().prop_map(|total_supply| FungibleTokenMetadata { total_supply })
}

/// Returns a [`Strategy`] for randomly generating a [`FunctionSignature`]
/// instance.
pub fn function_signature() -> impl Strategy<Value = FunctionSignature> {
(
// arg_types
prop::collection::vec(type_signature(), 0..3),
// return_type
type_signature(),
)
.prop_map(|(args, returns)| FunctionSignature { args, returns })
}

/// Returns a [`Strategy`] for randomly generating a [`DefinedFunction`]
/// instance.
pub fn defined_function() -> impl Strategy<Value = DefinedFunction> {
(
// identifier
function_identifier(),
// name
clarity_name(),
// arg_types + arguments, which must have the same length
(0usize..3usize).prop_flat_map(|x| {
(
prop::collection::vec(type_signature(), x..=x),
prop::collection::vec(clarity_name(), x..=x),
)
}),
// define_type
define_type(),
// body
symbolic_expression(),
)
.prop_map(
|(identifier, name, args, define_type, body)| DefinedFunction {
identifier,
name,
arg_types: args.0,
define_type,
arguments: args.1,
body,
},
)
}
83 changes: 83 additions & 0 deletions clarity/src/proptesting/contracts.rs
@@ -0,0 +1,83 @@
use proptest::collection::btree_map;
use proptest::prelude::*;
use stacks_common::proptesting::*;

use super::*;
use crate::types::{StacksHashMap as HashMap, StacksHashSet as HashSet};
use crate::vm::contracts::Contract;
use crate::vm::types::PrincipalData;
use crate::vm::{ClarityVersion, ContractContext, Value};

pub fn contract_context(clarity_version: ClarityVersion) -> impl Strategy<Value = ContractContext> {
(
// contract_identifier
principal_contract().prop_map(|p| match p {
Value::Principal(PrincipalData::Contract(qual)) => qual,
_ => unreachable!(),
}),
// variables
prop::collection::vec((clarity_name(), PropValue::any().prop_map_into()), 0..8).prop_map(
|v| {
v.into_iter()
.map(|(k, v)| (k, v))
.collect::<HashMap<_, _>>()
},
),
// functions
stacks_hash_map(clarity_name(), defined_function(), 1..5),
// defined_traits
stacks_hash_map(
clarity_name(),
btree_map(clarity_name(), function_signature(), 1..5),
1..5,
),
// implemented_traits
stacks_hash_set(trait_identifier(), 0..3),
// persisted_names
stacks_hash_set(clarity_name(), 0..5),
// meta_data_map
stacks_hash_map(clarity_name(), data_map_metadata(), 1..5),
// meta_data_var
stacks_hash_map(clarity_name(), data_variable_metadata(), 1..5),
// meta_nft
stacks_hash_map(clarity_name(), nft_metadata(), 1..5),
// meta_ft
stacks_hash_map(clarity_name(), ft_metadata(), 1..5),
// data_size
0u64..64,
)
.prop_map(
move |(
contract_identifier,
variables,
functions,
defined_traits,
implemented_traits,
persisted_names,
meta_data_map,
meta_data_var,
meta_nft,
meta_ft,
data_size,
)| {
let mut cc = ContractContext::new(contract_identifier, clarity_version);
cc.variables = variables;
cc.functions = functions;
cc.defined_traits = defined_traits;
cc.implemented_traits = implemented_traits;
cc.persisted_names = persisted_names;
cc.meta_data_map = meta_data_map;
cc.meta_data_var = meta_data_var;
cc.meta_nft = meta_nft;
cc.meta_ft = meta_ft;
cc.data_size = data_size;
cc
},
)
}

pub fn contract() -> impl Strategy<Value = Contract> {
clarity_version()
.prop_flat_map(contract_context)
.prop_map(|contract_context| Contract { contract_context })
}
27 changes: 27 additions & 0 deletions clarity/src/proptesting/mod.rs
@@ -0,0 +1,27 @@
use proptest::prop_oneof;
use proptest::strategy::{Just, Strategy, ValueTree};
use proptest::test_runner::{Config, RngAlgorithm, TestRng, TestRunner};
use rand::Rng;
use stacks_common::types::StacksHashMap as HashMap;

pub mod callables;
pub mod contracts;
pub mod representations;
pub mod types;
pub mod values;

pub use callables::*;
pub use contracts::*;
pub use representations::*;
pub use types::*;
pub use values::*;

use crate::vm::ClarityVersion;

/// Returns a [`Strategy`] for randomly generating a [`ClarityVersion`] instance.
pub fn clarity_version() -> impl Strategy<Value = ClarityVersion> {
prop_oneof![
Just(crate::vm::ClarityVersion::Clarity1),
Just(crate::vm::ClarityVersion::Clarity2),
]
}
40 changes: 40 additions & 0 deletions clarity/src/proptesting/representations.rs
@@ -0,0 +1,40 @@
use proptest::prelude::*;

use super::*;
use crate::vm::representations::{Span, TraitDefinition};
use crate::vm::{ClarityName, ContractName, SymbolicExpression, SymbolicExpressionType};

/// Returns a [`Strategy`] for randomly generating a [`ClarityName`].
pub fn clarity_name() -> impl Strategy<Value = ClarityName> {
"[a-z]{40}".prop_map(|s| s.try_into().unwrap())
}

/// Returns a [`Strategy`] for randomly generating a [`ContractName`].
pub fn contract_name() -> impl Strategy<Value = ContractName> {
"[a-zA-Z]{1,40}".prop_map(|s| s.try_into().unwrap())
}

/// Returns a [`Strategy`] for randomly generating a [`TraitDefinition`].
pub fn trait_definition() -> impl Strategy<Value = TraitDefinition> {
prop_oneof![
trait_identifier().prop_map(TraitDefinition::Defined),
trait_identifier().prop_map(TraitDefinition::Imported)
]
}

/// Returns a [`Strategy`] for randomly generating a [`SymbolicExpression`].
pub fn symbolic_expression() -> impl Strategy<Value = SymbolicExpression> {
let leaf = prop_oneof![
clarity_name().prop_map(|name| SymbolicExpression::atom(name)),
PropValue::any().prop_map(|val| SymbolicExpression::atom_value(val.into())),
PropValue::any().prop_map(|val| SymbolicExpression::literal_value(val.into())),
trait_identifier().prop_map(|name| SymbolicExpression::field(name)),
(clarity_name(), trait_definition())
.prop_map(|(n, t)| SymbolicExpression::trait_reference(n, t)),
];

leaf.prop_recursive(3, 64, 5, |inner| {
prop::collection::vec(inner, 1..3)
.prop_map(|list| SymbolicExpression::list(list.into_boxed_slice()))
})
}