mit einem Klick
evolve-proptest
// Write property-based tests for Evolve SDK using proptest. Use when writing property tests, invariant testing, fuzz testing, test generators, or finding edge cases.
// Write property-based tests for Evolve SDK using proptest. Use when writing property tests, invariant testing, fuzz testing, test generators, or finding edge cases.
| name | evolve-proptest |
| description | Write property-based tests for Evolve SDK using proptest. Use when writing property tests, invariant testing, fuzz testing, test generators, or finding edge cases. |
The evolve_proptest crate provides property-based testing infrastructure for Evolve SDK, including transaction generators, system invariants, and test case shrinking.
use evolve_proptest::{
generators::{arb_account_id, arb_tx, arb_block, arb_scenario},
invariants::{Invariant, InvariantChecker, BalanceConservation, NonNegativeBalances},
};
use proptest::prelude::*;
proptest! {
#[test]
fn test_balance_conservation(
block in arb_block(test_accounts(), 0, 10)
) {
// Execute block
let state = execute_block(block);
// Check invariant
let checker = InvariantChecker::new()
.with(BalanceConservation);
let results = checker.check_all(&state);
for result in results {
prop_assert!(result.passed, "Invariant violated: {}", result.name);
}
}
}
use evolve_proptest::generators::arb_account_id;
// Generate random account IDs
proptest! {
#[test]
fn test_with_account(account in arb_account_id()) {
assert!(account.as_bytes().len() == 16);
}
}
use evolve_proptest::generators::arb_tx;
let accounts = vec![AccountId::new(1), AccountId::new(2), AccountId::new(3)];
proptest! {
#[test]
fn test_with_tx(tx in arb_tx(accounts.clone())) {
// tx has valid sender from accounts
// tx has valid amount > 0
// tx has valid gas limit
}
}
use evolve_proptest::generators::arb_block;
// Generate block at height 5 with up to 10 transactions
proptest! {
#[test]
fn test_with_block(
block in arb_block(accounts.clone(), 5, 10)
) {
assert_eq!(block.height, 5);
assert!(block.transactions.len() <= 10);
}
}
use evolve_proptest::generators::{arb_scenario, ScenarioConfig};
let config = ScenarioConfig {
num_accounts: 5,
num_blocks: 10,
max_txs_per_block: 5,
initial_balance: 1_000_000,
};
proptest! {
#[test]
fn test_scenario(scenario in arb_scenario(config.clone())) {
// scenario.accounts - list of accounts
// scenario.initial_balances - account -> balance map
// scenario.blocks - list of blocks to execute
}
}
use evolve_proptest::invariants::{
BalanceConservation,
NonNegativeBalances,
NonceMonotonicity,
};
let checker = InvariantChecker::new()
.with(BalanceConservation)
.with(NonNegativeBalances)
.with(NonceMonotonicity);
let results = checker.check_all(&state);
use evolve_proptest::invariants::{Invariant, InvariantResult};
use evolve_core::ReadonlyKV;
struct NoEmptyAccounts;
impl Invariant for NoEmptyAccounts {
fn name(&self) -> &str {
"no_empty_accounts"
}
fn check(&self, state: &dyn ReadonlyKV) -> InvariantResult {
// Check that no account has zero balance
// Return InvariantResult::passed() or InvariantResult::violated(msg)
InvariantResult::passed("no_empty_accounts")
}
}
let checker = InvariantChecker::new()
.with(NoEmptyAccounts);
When a property test fails, the shrinker finds the minimal failing case:
use evolve_proptest::shrinker::FailureShrink;
fn run_test(scenario: &TestScenario) -> Result<(), String> {
// Run scenario and check invariants
}
let shrink = FailureShrink::new(
initial_seed,
failing_scenario,
"balance_conservation",
|scenario| run_test(scenario).is_err(),
);
let minimal = shrink.shrink();
println!("Minimal failing case:");
println!(" Seed: {}", minimal.seed);
println!(" Blocks: {}", minimal.blocks.len());
println!(" Transactions: {}", minimal.total_transactions());
use evolve_proptest::PropertyTestRunner;
use evolve_simulator::Simulator;
let runner = PropertyTestRunner::new()
.with_config(proptest_config)
.with_invariants(checker);
let result = runner.run(|scenario| {
let mut sim = Simulator::new(scenario.seed, SimConfig::default());
// Execute scenario
for block in &scenario.blocks {
execute_block(&mut sim, block)?;
}
Ok(())
});
if let Err(failure) = result {
println!("Property violated!");
println!("Minimal case: {:?}", failure.minimal);
println!("Reproduce: evolve-sim run --seed {}", failure.seed);
}
Configure test iterations via environment variables:
fn get_proptest_cases() -> u32 {
if std::env::var("CI").is_ok() {
1000 // More cases in CI
} else if let Ok(cases) = std::env::var("EVOLVE_PROPTEST_CASES") {
cases.parse().unwrap_or(100)
} else {
100 // Default for local development
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(get_proptest_cases()))]
#[test]
fn my_property_test(input in arb_input()) {
// ...
}
}
Test implementation against a simplified model:
use std::collections::HashMap;
struct TokenModel {
balances: HashMap<AccountId, u128>,
total_supply: u128,
}
impl TokenModel {
fn transfer(&mut self, from: AccountId, to: AccountId, amount: u128) -> bool {
if let Some(balance) = self.balances.get_mut(&from) {
if *balance >= amount {
*balance -= amount;
*self.balances.entry(to).or_insert(0) += amount;
return true;
}
}
false
}
}
proptest! {
#[test]
fn token_matches_model(ops in prop::collection::vec(arb_transfer_op(), 0..100)) {
let mut model = TokenModel::new();
let mut app = TestApp::new();
for op in ops {
let model_result = model.transfer(op.from, op.to, op.amount);
let impl_result = app.system_exec_as(op.from, |env| {
TokenRef::from(token).transfer(op.to, op.amount, env)
});
prop_assert_eq!(model_result, impl_result.is_ok());
}
// Verify final state matches
for (account, expected_balance) in model.balances {
let actual = app.system_exec_as(account, |env| {
TokenRef::from(token).get_balance(account, env)
}).unwrap();
prop_assert_eq!(actual, Some(expected_balance));
}
}
}
let runner = PropertyTestRunner::new()
.with_token_invariants(asset_id, initial_supply);
// This adds:
// - BalanceConservation: sum(balances) == total_supply
// - NonNegativeBalances: all balances >= 0
// - TotalSupplyMatch: total_supply storage == sum(balances)
use evolve_proptest::generators::arb_scenario;
use evolve_simulator::{Simulator, SimConfig};
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn fuzz_test(scenario in arb_scenario(config.clone())) {
let mut sim = Simulator::new(scenario.seed, SimConfig::default());
// Apply initial state
for (account, balance) in &scenario.initial_balances {
// Set up account balance
}
// Execute blocks
for block in &scenario.blocks {
// Execute each transaction
}
// Check invariants
let results = checker.check_all(sim.storage());
for result in results {
prop_assert!(result.passed);
}
}
}
crates/testing/proptest/src/lib.rs - Main exportscrates/testing/proptest/src/generators.rs - Test data generatorscrates/testing/proptest/src/invariants.rs - System invariantscrates/testing/proptest/src/shrinker.rs - Failing case minimizationUse the Evolve execution debugger for trace recording, replay, and time-travel debugging. Use when debugging failing tests, recording execution traces, stepping through state changes, or analyzing execution flow.
Write modules for the Evolve SDK using AccountCode trait and account_impl macro. Use when creating modules, writing account code, using AccountState, storage prefixes, or developing new blockchain modules.
Create custom Evolve blockchain nodes by composing storage, STF, mempool, RPC, and gRPC components. Use when the user wants to create a new node binary, build a custom chain, compose node components, or asks about evd/testapp architecture.
Use the Evolve deterministic simulator for testing with controlled time, storage, and randomness. Use when creating simulations, seed-based testing, fault injection, stress testing, or deterministic test reproduction.
Overview of Evolve SDK testing infrastructure including TestApp, SimTestApp, MockEnv, and transaction generators. Use when writing tests, setting up test harnesses, using test infrastructure, or choosing between testing approaches.
Analyze State Transition Function implementations for correctness, threading issues, non-determinism, and simplification opportunities. Use when analyzing STF code, checking threading model, finding non-determinism sources, or reviewing execution layer.