mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-01-02 13:23:13 +00:00
docs: add compilable doc-snippets crate
This commit is contained in:
parent
4141c98d8d
commit
de2e043e2a
31
.github/workflows/lint.yml
vendored
31
.github/workflows/lint.yml
vendored
@ -98,6 +98,37 @@ jobs:
|
||||
restore-keys: ${{ runner.os }}-target-clippy-
|
||||
- run: cargo +nightly-2025-09-14 clippy --all --all-targets --all-features -- -D warnings
|
||||
|
||||
doc_snippets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Load versions
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ ! -f versions.env ]; then
|
||||
echo "versions.env missing; populate VERSION, NOMOS_NODE_REV, NOMOS_BUNDLE_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
set -a
|
||||
. versions.env
|
||||
set +a
|
||||
# $GITHUB_ENV does not accept comments/blank lines; keep only KEY=VALUE exports.
|
||||
grep -E '^[A-Za-z_][A-Za-z0-9_]*=' versions.env >> "$GITHUB_ENV"
|
||||
: "${VERSION:?Missing VERSION}"
|
||||
: "${NOMOS_NODE_REV:?Missing NOMOS_NODE_REV}"
|
||||
: "${NOMOS_BUNDLE_VERSION:?Missing NOMOS_BUNDLE_VERSION}"
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly-2025-09-14
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
- run: cargo +nightly-2025-09-14 check -p doc-snippets
|
||||
|
||||
deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -1698,6 +1698,19 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-snippets"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"testing-framework-core",
|
||||
"testing-framework-runner-compose",
|
||||
"testing-framework-runner-k8s",
|
||||
"testing-framework-runner-local",
|
||||
"testing-framework-workflows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "1.0.10"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"examples",
|
||||
"examples/doc-snippets",
|
||||
"testing-framework/configs",
|
||||
"testing-framework/core",
|
||||
"testing-framework/runners/compose",
|
||||
|
||||
@ -58,23 +58,20 @@ These binaries use the framework API (`ScenarioBuilder`) to construct and execut
|
||||
Scenarios are defined using a fluent builder pattern:
|
||||
|
||||
```rust
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star() // Topology configuration
|
||||
.validators(3)
|
||||
.executors(2)
|
||||
})
|
||||
.wallets(50) // Wallet seeding
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5)
|
||||
.users(20)
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1)
|
||||
.blob_rate(2)
|
||||
})
|
||||
.expect_consensus_liveness() // Expectations
|
||||
.with_run_duration(Duration::from_secs(90))
|
||||
.build();
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn scenario_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| txs.rate(5).users(20))
|
||||
.da_with(|da| da.channel_rate(1).blob_rate(2))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(90))
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
**Key API Points:**
|
||||
|
||||
@ -20,26 +20,26 @@ recovery. The built-in restart workload lives in
|
||||
## Usage
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::workloads::chaos::RandomRestartWorkload;
|
||||
|
||||
let plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(2)
|
||||
.executors(1)
|
||||
})
|
||||
.enable_node_control()
|
||||
.with_workload(RandomRestartWorkload::new(
|
||||
Duration::from_secs(45), // min delay
|
||||
Duration::from_secs(75), // max delay
|
||||
Duration::from_secs(120), // target cooldown
|
||||
true, // include validators
|
||||
true, // include executors
|
||||
))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(150))
|
||||
.build();
|
||||
// deploy with a runner that supports node control and run the scenario
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::{ScenarioBuilderExt, workloads::chaos::RandomRestartWorkload};
|
||||
|
||||
pub fn random_restart_plan() -> testing_framework_core::scenario::Scenario<
|
||||
testing_framework_core::scenario::NodeControlCapability,
|
||||
> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.enable_node_control()
|
||||
.with_workload(RandomRestartWorkload::new(
|
||||
Duration::from_secs(45), // min delay
|
||||
Duration::from_secs(75), // max delay
|
||||
Duration::from_secs(120), // target cooldown
|
||||
true, // include validators
|
||||
true, // include executors
|
||||
))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(150))
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## Expectations to pair
|
||||
|
||||
@ -14,10 +14,10 @@ Key ideas:
|
||||
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{
|
||||
DynError, Expectation, RunContext, Workload, runtime::context::RunMetrics,
|
||||
use testing_framework_core::{
|
||||
scenario::{DynError, Expectation, RunContext, RunMetrics, Workload},
|
||||
topology::generation::GeneratedTopology,
|
||||
};
|
||||
use testing_framework_core::topology::generation::GeneratedTopology;
|
||||
|
||||
pub struct ReachabilityWorkload {
|
||||
target_idx: usize,
|
||||
@ -36,16 +36,23 @@ impl Workload for ReachabilityWorkload {
|
||||
}
|
||||
|
||||
fn expectations(&self) -> Vec<Box<dyn Expectation>> {
|
||||
vec![Box::new(ReachabilityExpectation::new(self.target_idx))]
|
||||
vec![Box::new(
|
||||
crate::custom_workload_example_expectation::ReachabilityExpectation::new(
|
||||
self.target_idx,
|
||||
),
|
||||
)]
|
||||
}
|
||||
|
||||
fn init(
|
||||
&mut self,
|
||||
topology: &GeneratedTopology,
|
||||
_metrics: &RunMetrics,
|
||||
_run_metrics: &RunMetrics,
|
||||
) -> Result<(), DynError> {
|
||||
if topology.validators().get(self.target_idx).is_none() {
|
||||
return Err("no validator at requested index".into());
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"no validator at requested index",
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -55,10 +62,19 @@ impl Workload for ReachabilityWorkload {
|
||||
.node_clients()
|
||||
.validator_clients()
|
||||
.get(self.target_idx)
|
||||
.ok_or("missing target client")?;
|
||||
.ok_or_else(|| {
|
||||
Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"missing target client",
|
||||
)) as DynError
|
||||
})?;
|
||||
|
||||
// Lightweight API call to prove reachability.
|
||||
client.consensus_info().await.map(|_| ()).map_err(|e| e.into())
|
||||
client
|
||||
.consensus_info()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -94,13 +110,18 @@ impl Expectation for ReachabilityExpectation {
|
||||
.node_clients()
|
||||
.validator_clients()
|
||||
.get(self.target_idx)
|
||||
.ok_or("missing target client")?;
|
||||
.ok_or_else(|| {
|
||||
Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"missing target client",
|
||||
)) as DynError
|
||||
})?;
|
||||
|
||||
client
|
||||
.consensus_info()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("target became unreachable during run: {e}").into())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -5,123 +5,199 @@ Quick reference for the scenario builder DSL. All methods are chainable.
|
||||
## Imports
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_runner_k8s::K8sDeployer;
|
||||
use testing_framework_workflows::{ScenarioBuilderExt, ChaosBuilderExt};
|
||||
use std::time::Duration;
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
```
|
||||
|
||||
## Topology
|
||||
|
||||
```rust
|
||||
ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star() // Star topology (all connect to seed node)
|
||||
.validators(3) // Number of validator nodes
|
||||
.executors(2) // Number of executor nodes
|
||||
}) // Finish topology configuration
|
||||
use testing_framework_core::scenario::{Builder, ScenarioBuilder};
|
||||
|
||||
pub fn topology() -> Builder<()> {
|
||||
ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star() // Star topology (all connect to seed node)
|
||||
.validators(3) // Number of validator nodes
|
||||
.executors(2) // Number of executor nodes
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Wallets
|
||||
|
||||
```rust
|
||||
.wallets(50) // Seed 50 funded wallet accounts
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn wallets_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.wallets(50) // Seed 50 funded wallet accounts
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## Transaction Workload
|
||||
|
||||
```rust
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(20) // Use 20 of the seeded wallets
|
||||
}) // Finish transaction workload config
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn transactions_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(20) // Use 20 of the seeded wallets
|
||||
}) // Finish transaction workload config
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## DA Workload
|
||||
|
||||
```rust
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // number of DA channels to run
|
||||
.blob_rate(2) // target 2 blobs per block (headroom applied)
|
||||
.headroom_percent(20)// optional headroom when sizing channels
|
||||
}) // Finish DA workload config
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn da_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(1))
|
||||
.wallets(50)
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // number of DA channels to run
|
||||
.blob_rate(2) // target 2 blobs per block (headroom applied)
|
||||
.headroom_percent(20) // optional headroom when sizing channels
|
||||
}) // Finish DA workload config
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## Chaos Workload (Requires `enable_node_control()`)
|
||||
|
||||
```rust
|
||||
.enable_node_control() // Enable node control capability
|
||||
.chaos_with(|c| {
|
||||
c.restart() // Random restart chaos
|
||||
.min_delay(Duration::from_secs(30)) // Min time between restarts
|
||||
.max_delay(Duration::from_secs(60)) // Max time between restarts
|
||||
.target_cooldown(Duration::from_secs(45)) // Cooldown after restart
|
||||
.apply() // Required for chaos configuration
|
||||
})
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::{NodeControlCapability, ScenarioBuilder};
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub fn chaos_plan() -> testing_framework_core::scenario::Scenario<NodeControlCapability> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.enable_node_control() // Enable node control capability
|
||||
.chaos_with(|c| {
|
||||
c.restart() // Random restart chaos
|
||||
.min_delay(Duration::from_secs(30)) // Min time between restarts
|
||||
.max_delay(Duration::from_secs(60)) // Max time between restarts
|
||||
.target_cooldown(Duration::from_secs(45)) // Cooldown after restart
|
||||
.apply() // Required for chaos configuration
|
||||
})
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## Expectations
|
||||
|
||||
```rust
|
||||
.expect_consensus_liveness() // Assert blocks are produced continuously
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn expectations_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.expect_consensus_liveness() // Assert blocks are produced continuously
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## Run Duration
|
||||
|
||||
```rust
|
||||
.with_run_duration(Duration::from_secs(120)) // Run for 120 seconds
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn run_duration_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.with_run_duration(Duration::from_secs(120)) // Run for 120 seconds
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```rust
|
||||
.build() // Construct the final Scenario
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn build_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)).build() // Construct the final Scenario
|
||||
}
|
||||
```
|
||||
|
||||
## Deployers
|
||||
|
||||
```rust
|
||||
// Local processes
|
||||
let deployer = LocalDeployer::default();
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_runner_k8s::K8sDeployer;
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
|
||||
// Docker Compose
|
||||
let deployer = ComposeDeployer::default();
|
||||
pub fn deployers() {
|
||||
// Local processes
|
||||
let _deployer = LocalDeployer::default();
|
||||
|
||||
// Kubernetes
|
||||
let deployer = K8sDeployer::default();
|
||||
// Docker Compose
|
||||
let _deployer = ComposeDeployer::default();
|
||||
|
||||
// Kubernetes
|
||||
let _deployer = K8sDeployer::default();
|
||||
}
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
```rust
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn execution() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
use std::time::Duration;
|
||||
|
||||
async fn run_test() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(3)
|
||||
.executors(2)
|
||||
})
|
||||
pub async fn run_test() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(20)
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // number of DA channels
|
||||
.blob_rate(2) // target 2 blobs per block
|
||||
.headroom_percent(20) // optional channel headroom
|
||||
da.channel_rate(1) // number of DA channels
|
||||
.blob_rate(2) // target 2 blobs per block
|
||||
.headroom_percent(20) // optional channel headroom
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(90))
|
||||
@ -130,7 +206,7 @@ async fn run_test() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@ -15,34 +15,30 @@ Realistic advanced scenarios demonstrating framework capabilities for production
|
||||
Test consensus under progressively increasing transaction load:
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
use std::time::Duration;
|
||||
|
||||
async fn load_progression_test() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn load_progression_test() -> Result<()> {
|
||||
for rate in [5, 10, 20, 30] {
|
||||
println!("Testing with rate: {}", rate);
|
||||
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(3)
|
||||
.executors(2)
|
||||
})
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(rate)
|
||||
.users(20)
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
|
||||
let mut plan =
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| txs.rate(rate).users(20))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@ -54,26 +50,18 @@ async fn load_progression_test() -> Result<(), Box<dyn std::error::Error + Send
|
||||
Run high transaction and DA load for extended duration:
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
use std::time::Duration;
|
||||
|
||||
async fn sustained_load_test() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(4)
|
||||
.executors(2)
|
||||
})
|
||||
pub async fn sustained_load_test() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(4).executors(2))
|
||||
.wallets(100)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(15)
|
||||
.users(50)
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(2)
|
||||
.blob_rate(3)
|
||||
})
|
||||
.transactions_with(|txs| txs.rate(15).users(50))
|
||||
.da_with(|da| da.channel_rate(2).blob_rate(3))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(300))
|
||||
.build();
|
||||
@ -81,7 +69,7 @@ async fn sustained_load_test() -> Result<(), Box<dyn std::error::Error + Send +
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@ -93,23 +81,18 @@ async fn sustained_load_test() -> Result<(), Box<dyn std::error::Error + Send +
|
||||
Frequent node restarts with active traffic:
|
||||
|
||||
```rust
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::{ScenarioBuilderExt, ChaosBuilderExt};
|
||||
use std::time::Duration;
|
||||
|
||||
async fn aggressive_chaos_test() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(4)
|
||||
.executors(2)
|
||||
})
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub async fn aggressive_chaos_test() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(4).executors(2))
|
||||
.enable_node_control()
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(10)
|
||||
.users(20)
|
||||
})
|
||||
.transactions_with(|txs| txs.rate(10).users(20))
|
||||
.chaos_with(|c| {
|
||||
c.restart()
|
||||
.min_delay(Duration::from_secs(10))
|
||||
@ -124,7 +107,7 @@ async fn aggressive_chaos_test() -> Result<(), Box<dyn std::error::Error + Send
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@ -21,17 +21,15 @@ and expectations.
|
||||
Minimal test that validates basic block production:
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
use std::time::Duration;
|
||||
|
||||
async fn simple_consensus() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(3)
|
||||
.executors(0)
|
||||
})
|
||||
pub async fn simple_consensus() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(30))
|
||||
.build();
|
||||
@ -39,7 +37,7 @@ async fn simple_consensus() -> Result<(), Box<dyn std::error::Error + Send + Syn
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@ -51,22 +49,17 @@ async fn simple_consensus() -> Result<(), Box<dyn std::error::Error + Send + Syn
|
||||
Test consensus under transaction load:
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
use std::time::Duration;
|
||||
|
||||
async fn transaction_workload() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(2)
|
||||
.executors(0)
|
||||
})
|
||||
pub async fn transaction_workload() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(0))
|
||||
.wallets(20)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5)
|
||||
.users(10)
|
||||
})
|
||||
.transactions_with(|txs| txs.rate(5).users(10))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
@ -74,7 +67,7 @@ async fn transaction_workload() -> Result<(), Box<dyn std::error::Error + Send +
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@ -86,26 +79,18 @@ async fn transaction_workload() -> Result<(), Box<dyn std::error::Error + Send +
|
||||
Combined test stressing both transaction and DA layers:
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
use std::time::Duration;
|
||||
|
||||
async fn da_and_transactions() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(3)
|
||||
.executors(2)
|
||||
})
|
||||
pub async fn da_and_transactions() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(30)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5)
|
||||
.users(15)
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(2)
|
||||
.blob_rate(2)
|
||||
})
|
||||
.transactions_with(|txs| txs.rate(5).users(15))
|
||||
.da_with(|da| da.channel_rate(2).blob_rate(2))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(90))
|
||||
.build();
|
||||
@ -113,7 +98,7 @@ async fn da_and_transactions() -> Result<(), Box<dyn std::error::Error + Send +
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@ -125,23 +110,18 @@ async fn da_and_transactions() -> Result<(), Box<dyn std::error::Error + Send +
|
||||
Test system resilience under node restarts:
|
||||
|
||||
```rust
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::{ScenarioBuilderExt, ChaosBuilderExt};
|
||||
use std::time::Duration;
|
||||
|
||||
async fn chaos_resilience() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(4)
|
||||
.executors(2)
|
||||
})
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub async fn chaos_resilience() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(4).executors(2))
|
||||
.enable_node_control()
|
||||
.wallets(20)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(3)
|
||||
.users(10)
|
||||
})
|
||||
.transactions_with(|txs| txs.rate(3).users(10))
|
||||
.chaos_with(|c| {
|
||||
c.restart()
|
||||
.min_delay(Duration::from_secs(20))
|
||||
@ -156,7 +136,7 @@ async fn chaos_resilience() -> Result<(), Box<dyn std::error::Error + Send + Syn
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@ -30,92 +30,142 @@ High-level roles of the crates that make up the framework:
|
||||
### Adding a New Workload
|
||||
|
||||
1. **Define the workload** in `testing-framework/workflows/src/workloads/your_workload.rs`:
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{Workload, RunContext, DynError};
|
||||
|
||||
pub struct YourWorkload {
|
||||
// config fields
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Workload for YourWorkload {
|
||||
fn name(&self) -> &'static str { "your_workload" }
|
||||
async fn start(&self, ctx: &RunContext) -> Result<(), DynError> {
|
||||
// implementation
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{DynError, RunContext, Workload};
|
||||
|
||||
pub struct YourWorkload;
|
||||
|
||||
#[async_trait]
|
||||
impl Workload for YourWorkload {
|
||||
fn name(&self) -> &'static str {
|
||||
"your_workload"
|
||||
}
|
||||
|
||||
async fn start(&self, _ctx: &RunContext) -> Result<(), DynError> {
|
||||
// implementation
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add builder extension** in `testing-framework/workflows/src/builder/mod.rs`:
|
||||
```rust
|
||||
pub trait ScenarioBuilderExt {
|
||||
fn your_workload(self) -> YourWorkloadBuilder;
|
||||
}
|
||||
```
|
||||
```rust
|
||||
pub struct YourWorkloadBuilder;
|
||||
|
||||
impl YourWorkloadBuilder {
|
||||
pub fn some_config(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ScenarioBuilderExt: Sized {
|
||||
fn your_workload(self) -> YourWorkloadBuilder;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Use in examples** in `examples/src/bin/your_scenario.rs`:
|
||||
```rust
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(3)
|
||||
.executors(0)
|
||||
})
|
||||
.your_workload_with(|w| { // Your new DSL method with closure
|
||||
w.some_config()
|
||||
})
|
||||
.build();
|
||||
```
|
||||
```rust
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub struct YourWorkloadBuilder;
|
||||
|
||||
impl YourWorkloadBuilder {
|
||||
pub fn some_config(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait YourWorkloadDslExt: Sized {
|
||||
fn your_workload_with<F>(self, configurator: F) -> Self
|
||||
where
|
||||
F: FnOnce(YourWorkloadBuilder) -> YourWorkloadBuilder;
|
||||
}
|
||||
|
||||
impl<Caps> YourWorkloadDslExt for testing_framework_core::scenario::Builder<Caps> {
|
||||
fn your_workload_with<F>(self, configurator: F) -> Self
|
||||
where
|
||||
F: FnOnce(YourWorkloadBuilder) -> YourWorkloadBuilder,
|
||||
{
|
||||
let _ = configurator(YourWorkloadBuilder);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_in_examples() {
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||
.your_workload_with(|w| w.some_config())
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
### Adding a New Expectation
|
||||
|
||||
1. **Define the expectation** in `testing-framework/workflows/src/expectations/your_expectation.rs`:
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{Expectation, RunContext, DynError};
|
||||
|
||||
pub struct YourExpectation {
|
||||
// config fields
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Expectation for YourExpectation {
|
||||
fn name(&self) -> &str { "your_expectation" }
|
||||
async fn evaluate(&mut self, ctx: &RunContext) -> Result<(), DynError> {
|
||||
// implementation
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{DynError, Expectation, RunContext};
|
||||
|
||||
pub struct YourExpectation;
|
||||
|
||||
#[async_trait]
|
||||
impl Expectation for YourExpectation {
|
||||
fn name(&self) -> &'static str {
|
||||
"your_expectation"
|
||||
}
|
||||
|
||||
async fn evaluate(&mut self, _ctx: &RunContext) -> Result<(), DynError> {
|
||||
// implementation
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add builder extension** in `testing-framework/workflows/src/builder/mod.rs`:
|
||||
```rust
|
||||
pub trait ScenarioBuilderExt {
|
||||
fn expect_your_condition(self) -> Self;
|
||||
}
|
||||
```
|
||||
```rust
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub trait YourExpectationDslExt: Sized {
|
||||
fn expect_your_condition(self) -> Self;
|
||||
}
|
||||
|
||||
impl<Caps> YourExpectationDslExt for testing_framework_core::scenario::Builder<Caps> {
|
||||
fn expect_your_condition(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_in_examples() {
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||
.expect_your_condition()
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
### Adding a New Deployer
|
||||
|
||||
1. **Implement `Deployer` trait** in `testing-framework/runners/your_runner/src/deployer.rs`:
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{Deployer, Runner, Scenario};
|
||||
|
||||
pub struct YourDeployer;
|
||||
|
||||
#[async_trait]
|
||||
impl Deployer for YourDeployer {
|
||||
type Error = YourError;
|
||||
|
||||
async fn deploy(&self, scenario: &Scenario) -> Result<Runner, Self::Error> {
|
||||
// Provision infrastructure
|
||||
// Wait for readiness
|
||||
// Return Runner
|
||||
}
|
||||
}
|
||||
```
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{Deployer, Runner, Scenario};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct YourError;
|
||||
|
||||
pub struct YourDeployer;
|
||||
|
||||
#[async_trait]
|
||||
impl Deployer for YourDeployer {
|
||||
type Error = YourError;
|
||||
|
||||
async fn deploy(&self, _scenario: &Scenario<()>) -> Result<Runner, Self::Error> {
|
||||
// Provision infrastructure
|
||||
// Wait for readiness
|
||||
// Return Runner
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Provide cleanup** and handle node control if supported.
|
||||
|
||||
|
||||
@ -39,7 +39,9 @@ struct RestartWorkload;
|
||||
|
||||
#[async_trait]
|
||||
impl Workload for RestartWorkload {
|
||||
fn name(&self) -> &str { "restart_workload" }
|
||||
fn name(&self) -> &str {
|
||||
"restart_workload"
|
||||
}
|
||||
|
||||
async fn start(&self, ctx: &RunContext) -> Result<(), DynError> {
|
||||
if let Some(control) = ctx.node_control() {
|
||||
@ -59,6 +61,10 @@ scenario builder and deploy with a runner that supports it.
|
||||
The `NodeControlHandle` trait currently provides:
|
||||
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::DynError;
|
||||
|
||||
#[async_trait]
|
||||
pub trait NodeControlHandle: Send + Sync {
|
||||
async fn restart_validator(&self, index: usize) -> Result<(), DynError>;
|
||||
async fn restart_executor(&self, index: usize) -> Result<(), DynError>;
|
||||
|
||||
@ -38,35 +38,37 @@ POL_PROOF_DEV_MODE=true cargo run -p runner-examples --bin local_runner
|
||||
**Core API Pattern** (simplified example):
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
use std::time::Duration;
|
||||
|
||||
// Define the scenario (1 validator + 1 executor, tx + DA workload)
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(1)
|
||||
.executors(1)
|
||||
})
|
||||
.wallets(1_000)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(500) // use 500 of the seeded wallets
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // 1 channel
|
||||
.blob_rate(1) // target 1 blob per block
|
||||
.headroom_percent(20) // default headroom when sizing channels
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
pub async fn run_local_demo() -> Result<()> {
|
||||
// Define the scenario (1 validator + 1 executor, tx + DA workload)
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(1))
|
||||
.wallets(1_000)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(500) // use 500 of the seeded wallets
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // 1 channel
|
||||
.blob_rate(1) // target 1 blob per block
|
||||
.headroom_percent(20) // default headroom when sizing channels
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
|
||||
// Deploy and run
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
// Deploy and run
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The examples are binaries with `#[tokio::main]`, not test functions. If you want to write integration tests, wrap this pattern in `#[tokio::test]` functions in your own test suite.
|
||||
@ -87,11 +89,15 @@ Let's unpack the code:
|
||||
### 1. Topology Configuration
|
||||
|
||||
```rust
|
||||
ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star() // Star topology: all nodes connect to seed
|
||||
.validators(1) // 1 validator node
|
||||
.executors(1) // 1 executor node (validator + DA dispersal)
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub fn step_1_topology() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star() // Star topology: all nodes connect to seed
|
||||
.validators(1) // 1 validator node
|
||||
.executors(1) // 1 executor node (validator + DA dispersal)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This defines **what** your test network looks like.
|
||||
@ -99,7 +105,12 @@ This defines **what** your test network looks like.
|
||||
### 2. Wallet Seeding
|
||||
|
||||
```rust
|
||||
.wallets(1_000) // Seed 1,000 funded wallet accounts
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn step_2_wallets() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1).wallets(1_000) // Seed 1,000 funded wallet accounts
|
||||
}
|
||||
```
|
||||
|
||||
Provides funded accounts for transaction submission.
|
||||
@ -107,15 +118,22 @@ Provides funded accounts for transaction submission.
|
||||
### 3. Workloads
|
||||
|
||||
```rust
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(500) // Use 500 of the 1,000 wallets
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // 1 DA channel (more spawned with headroom)
|
||||
.blob_rate(1) // target 1 blob per block
|
||||
.headroom_percent(20)// default headroom when sizing channels
|
||||
})
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn step_3_workloads() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1)
|
||||
.wallets(1_000)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(500) // Use 500 of the 1,000 wallets
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // 1 DA channel (more spawned with headroom)
|
||||
.blob_rate(1) // target 1 blob per block
|
||||
.headroom_percent(20) // default headroom when sizing channels
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Generates both transaction and DA traffic to stress both subsystems.
|
||||
@ -123,7 +141,12 @@ Generates both transaction and DA traffic to stress both subsystems.
|
||||
### 4. Expectation
|
||||
|
||||
```rust
|
||||
.expect_consensus_liveness()
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn step_4_expectation() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1).expect_consensus_liveness() // This says what success means: blocks must be produced continuously.
|
||||
}
|
||||
```
|
||||
|
||||
This says **what success means**: blocks must be produced continuously.
|
||||
@ -131,7 +154,13 @@ This says **what success means**: blocks must be produced continuously.
|
||||
### 5. Run Duration
|
||||
|
||||
```rust
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub fn step_5_run_duration() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1).with_run_duration(Duration::from_secs(60))
|
||||
}
|
||||
```
|
||||
|
||||
Run for 60 seconds (~27 blocks with default 2s slots, 0.9 coefficient). Framework ensures this is at least 2× the consensus slot duration.
|
||||
@ -139,9 +168,19 @@ Run for 60 seconds (~27 blocks with default 2s slots, 0.9 coefficient). Framewor
|
||||
### 6. Deploy and Execute
|
||||
|
||||
```rust
|
||||
let deployer = LocalDeployer::default(); // Use local process deployer
|
||||
let runner = deployer.deploy(&plan).await?; // Provision infrastructure
|
||||
let _handle = runner.run(&mut plan).await?; // Execute workloads & expectations
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
|
||||
pub async fn step_6_deploy_and_execute() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::with_node_counts(1, 1).build();
|
||||
|
||||
let deployer = LocalDeployer::default(); // Use local process deployer
|
||||
let runner = deployer.deploy(&plan).await?; // Provision infrastructure
|
||||
let _handle = runner.run(&mut plan).await?; // Execute workloads & expectations
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
**Deployer** provisions the infrastructure. **Runner** orchestrates execution.
|
||||
@ -207,13 +246,20 @@ cargo run -p runner-examples --bin compose_runner
|
||||
**In code:** Just swap the deployer:
|
||||
|
||||
```rust
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
|
||||
// ... same scenario definition ...
|
||||
pub async fn run_with_compose_deployer() -> Result<()> {
|
||||
// ... same scenario definition ...
|
||||
let mut plan = ScenarioBuilder::with_node_counts(1, 1).build();
|
||||
|
||||
let deployer = ComposeDeployer::default(); // Use Docker Compose
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
let deployer = ComposeDeployer::default(); // Use Docker Compose
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@ -9,21 +9,22 @@ interpret results correctly.
|
||||
Describe **what** you want to test, not **how** to orchestrate it:
|
||||
|
||||
```rust
|
||||
// Good: declarative
|
||||
ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(2)
|
||||
.executors(1)
|
||||
})
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
// Bad: imperative (framework doesn't work this way)
|
||||
// spawn_validator(); spawn_executor();
|
||||
// loop { submit_tx(); check_block(); }
|
||||
pub fn declarative_over_imperative() {
|
||||
// Good: declarative
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
// Bad: imperative (framework doesn't work this way)
|
||||
// spawn_validator(); spawn_executor();
|
||||
// loop { submit_tx(); check_block(); }
|
||||
}
|
||||
```
|
||||
|
||||
**Why it matters:** The framework handles deployment, readiness, and cleanup.
|
||||
@ -39,22 +40,25 @@ Reason in **blocks** and **consensus intervals**, not wall-clock seconds.
|
||||
- Expected rate: ~27 blocks per minute
|
||||
|
||||
```rust
|
||||
// Good: protocol-oriented thinking
|
||||
let plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(2)
|
||||
.executors(1)
|
||||
})
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.with_run_duration(Duration::from_secs(60)) // Let framework calculate expected blocks
|
||||
.expect_consensus_liveness() // "Did we produce the expected blocks?"
|
||||
.build();
|
||||
use std::time::Duration;
|
||||
|
||||
// Bad: wall-clock assumptions
|
||||
// "I expect exactly 30 blocks in 60 seconds"
|
||||
// This breaks on slow CI where slot timing might drift
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn protocol_time_not_wall_time() {
|
||||
// Good: protocol-oriented thinking
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.with_run_duration(Duration::from_secs(60)) // Let framework calculate expected blocks
|
||||
.expect_consensus_liveness() // "Did we produce the expected blocks?"
|
||||
.build();
|
||||
|
||||
// Bad: wall-clock assumptions
|
||||
// "I expect exactly 30 blocks in 60 seconds"
|
||||
// This breaks on slow CI where slot timing might drift
|
||||
}
|
||||
```
|
||||
|
||||
**Why it matters:** Slot timing is fixed (2s by default, NTP-synchronized), so the
|
||||
@ -73,37 +77,37 @@ not "blocks produced in exact wall-clock seconds".
|
||||
|
||||
**Chaos is opt-in:**
|
||||
```rust
|
||||
// Separate: functional test (deterministic)
|
||||
let plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(2)
|
||||
.executors(1)
|
||||
})
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
use std::time::Duration;
|
||||
|
||||
// Separate: chaos test (introduces randomness)
|
||||
let chaos_plan = ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star()
|
||||
.validators(3)
|
||||
.executors(2)
|
||||
})
|
||||
.enable_node_control()
|
||||
.chaos_with(|c| {
|
||||
c.restart()
|
||||
.min_delay(Duration::from_secs(30))
|
||||
.max_delay(Duration::from_secs(60))
|
||||
.target_cooldown(Duration::from_secs(45))
|
||||
.apply()
|
||||
})
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub fn determinism_first() {
|
||||
// Separate: functional test (deterministic)
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
// Separate: chaos test (introduces randomness)
|
||||
let _chaos_plan =
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.enable_node_control()
|
||||
.chaos_with(|c| {
|
||||
c.restart()
|
||||
.min_delay(Duration::from_secs(30))
|
||||
.max_delay(Duration::from_secs(60))
|
||||
.target_cooldown(Duration::from_secs(45))
|
||||
.apply()
|
||||
})
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
**Why it matters:** Mixing determinism with chaos creates noisy, hard-to-debug
|
||||
@ -132,11 +136,25 @@ perspective.
|
||||
Always run long enough for **meaningful block production**:
|
||||
|
||||
```rust
|
||||
// Bad: too short
|
||||
.with_run_duration(Duration::from_secs(5)) // ~2 blocks (with default 2s slots, 0.9 coeff)
|
||||
use std::time::Duration;
|
||||
|
||||
// Good: enough blocks for assertions
|
||||
.with_run_duration(Duration::from_secs(60)) // ~27 blocks (with default 2s slots, 0.9 coeff)
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn minimum_run_windows() {
|
||||
// Bad: too short (~2 blocks with default 2s slots, 0.9 coeff)
|
||||
let _too_short = ScenarioBuilder::with_node_counts(1, 0)
|
||||
.with_run_duration(Duration::from_secs(5))
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
// Good: enough blocks for assertions (~27 blocks with default 2s slots, 0.9
|
||||
// coeff)
|
||||
let _good = ScenarioBuilder::with_node_counts(1, 0)
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Block counts assume default consensus parameters:
|
||||
|
||||
23
examples/doc-snippets/Cargo.toml
Normal file
23
examples/doc-snippets/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
categories.workspace = true
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
name = "doc-snippets"
|
||||
publish = false
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
async-trait = { workspace = true }
|
||||
testing-framework-core = { workspace = true }
|
||||
testing-framework-runner-compose = { workspace = true }
|
||||
testing-framework-runner-k8s = { workspace = true }
|
||||
testing-framework-runner-local = { workspace = true }
|
||||
testing-framework-workflows = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@ -0,0 +1,14 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn scenario_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| txs.rate(5).users(20))
|
||||
.da_with(|da| da.channel_rate(1).blob_rate(2))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(90))
|
||||
.build()
|
||||
}
|
||||
21
examples/doc-snippets/src/chaos_workloads_random_restart.rs
Normal file
21
examples/doc-snippets/src/chaos_workloads_random_restart.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::{ScenarioBuilderExt, workloads::chaos::RandomRestartWorkload};
|
||||
|
||||
pub fn random_restart_plan() -> testing_framework_core::scenario::Scenario<
|
||||
testing_framework_core::scenario::NodeControlCapability,
|
||||
> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.enable_node_control()
|
||||
.with_workload(RandomRestartWorkload::new(
|
||||
Duration::from_secs(45), // min delay
|
||||
Duration::from_secs(75), // max delay
|
||||
Duration::from_secs(120), // target cooldown
|
||||
true, // include validators
|
||||
true, // include executors
|
||||
))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(150))
|
||||
.build()
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{DynError, Expectation, RunContext};
|
||||
|
||||
pub struct ReachabilityExpectation {
|
||||
target_idx: usize,
|
||||
}
|
||||
|
||||
impl ReachabilityExpectation {
|
||||
pub fn new(target_idx: usize) -> Self {
|
||||
Self { target_idx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Expectation for ReachabilityExpectation {
|
||||
fn name(&self) -> &str {
|
||||
"target_reachable"
|
||||
}
|
||||
|
||||
async fn evaluate(&mut self, ctx: &RunContext) -> Result<(), DynError> {
|
||||
let client = ctx
|
||||
.node_clients()
|
||||
.validator_clients()
|
||||
.get(self.target_idx)
|
||||
.ok_or_else(|| {
|
||||
Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"missing target client",
|
||||
)) as DynError
|
||||
})?;
|
||||
|
||||
client
|
||||
.consensus_info()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::{
|
||||
scenario::{DynError, Expectation, RunContext, RunMetrics, Workload},
|
||||
topology::generation::GeneratedTopology,
|
||||
};
|
||||
|
||||
pub struct ReachabilityWorkload {
|
||||
target_idx: usize,
|
||||
}
|
||||
|
||||
impl ReachabilityWorkload {
|
||||
pub fn new(target_idx: usize) -> Self {
|
||||
Self { target_idx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Workload for ReachabilityWorkload {
|
||||
fn name(&self) -> &str {
|
||||
"reachability_workload"
|
||||
}
|
||||
|
||||
fn expectations(&self) -> Vec<Box<dyn Expectation>> {
|
||||
vec![Box::new(
|
||||
crate::custom_workload_example_expectation::ReachabilityExpectation::new(
|
||||
self.target_idx,
|
||||
),
|
||||
)]
|
||||
}
|
||||
|
||||
fn init(
|
||||
&mut self,
|
||||
topology: &GeneratedTopology,
|
||||
_run_metrics: &RunMetrics,
|
||||
) -> Result<(), DynError> {
|
||||
if topology.validators().get(self.target_idx).is_none() {
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"no validator at requested index",
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start(&self, ctx: &RunContext) -> Result<(), DynError> {
|
||||
let client = ctx
|
||||
.node_clients()
|
||||
.validator_clients()
|
||||
.get(self.target_idx)
|
||||
.ok_or_else(|| {
|
||||
Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"missing target client",
|
||||
)) as DynError
|
||||
})?;
|
||||
|
||||
// Lightweight API call to prove reachability.
|
||||
client
|
||||
.consensus_info()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
6
examples/doc-snippets/src/dsl_cheat_sheet_build.rs
Normal file
6
examples/doc-snippets/src/dsl_cheat_sheet_build.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn build_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)).build() // Construct the final Scenario
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn run_test() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(20)
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // number of DA channels
|
||||
.blob_rate(2) // target 2 blobs per block
|
||||
.headroom_percent(20) // optional channel headroom
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(90))
|
||||
.build();
|
||||
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
14
examples/doc-snippets/src/dsl_cheat_sheet_deployers.rs
Normal file
14
examples/doc-snippets/src/dsl_cheat_sheet_deployers.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_runner_k8s::K8sDeployer;
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
|
||||
pub fn deployers() {
|
||||
// Local processes
|
||||
let _deployer = LocalDeployer::default();
|
||||
|
||||
// Docker Compose
|
||||
let _deployer = ComposeDeployer::default();
|
||||
|
||||
// Kubernetes
|
||||
let _deployer = K8sDeployer::default();
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn expectations_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.expect_consensus_liveness() // Assert blocks are produced continuously
|
||||
.build()
|
||||
}
|
||||
7
examples/doc-snippets/src/dsl_cheat_sheet_imports.rs
Normal file
7
examples/doc-snippets/src/dsl_cheat_sheet_imports.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_runner_k8s::K8sDeployer;
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
10
examples/doc-snippets/src/dsl_cheat_sheet_run_duration.rs
Normal file
10
examples/doc-snippets/src/dsl_cheat_sheet_run_duration.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn run_duration_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.with_run_duration(Duration::from_secs(120)) // Run for 120 seconds
|
||||
.build()
|
||||
}
|
||||
9
examples/doc-snippets/src/dsl_cheat_sheet_topology.rs
Normal file
9
examples/doc-snippets/src/dsl_cheat_sheet_topology.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use testing_framework_core::scenario::{Builder, ScenarioBuilder};
|
||||
|
||||
pub fn topology() -> Builder<()> {
|
||||
ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star() // Star topology (all connect to seed node)
|
||||
.validators(3) // Number of validator nodes
|
||||
.executors(2) // Number of executor nodes
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn transactions_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(20) // Use 20 of the seeded wallets
|
||||
}) // Finish transaction workload config
|
||||
.build()
|
||||
}
|
||||
8
examples/doc-snippets/src/dsl_cheat_sheet_wallets.rs
Normal file
8
examples/doc-snippets/src/dsl_cheat_sheet_wallets.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn wallets_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.wallets(50) // Seed 50 funded wallet accounts
|
||||
.build()
|
||||
}
|
||||
17
examples/doc-snippets/src/dsl_cheat_sheet_workload_chaos.rs
Normal file
17
examples/doc-snippets/src/dsl_cheat_sheet_workload_chaos.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::{NodeControlCapability, ScenarioBuilder};
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub fn chaos_plan() -> testing_framework_core::scenario::Scenario<NodeControlCapability> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.enable_node_control() // Enable node control capability
|
||||
.chaos_with(|c| {
|
||||
c.restart() // Random restart chaos
|
||||
.min_delay(Duration::from_secs(30)) // Min time between restarts
|
||||
.max_delay(Duration::from_secs(60)) // Max time between restarts
|
||||
.target_cooldown(Duration::from_secs(45)) // Cooldown after restart
|
||||
.apply() // Required for chaos configuration
|
||||
})
|
||||
.build()
|
||||
}
|
||||
13
examples/doc-snippets/src/dsl_cheat_sheet_workload_da.rs
Normal file
13
examples/doc-snippets/src/dsl_cheat_sheet_workload_da.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn da_plan() -> testing_framework_core::scenario::Scenario<()> {
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(1))
|
||||
.wallets(50)
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // number of DA channels to run
|
||||
.blob_rate(2) // target 2 blobs per block (headroom applied)
|
||||
.headroom_percent(20) // optional headroom when sizing channels
|
||||
}) // Finish DA workload config
|
||||
.build()
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn execution() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub async fn aggressive_chaos_test() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(4).executors(2))
|
||||
.enable_node_control()
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| txs.rate(10).users(20))
|
||||
.chaos_with(|c| {
|
||||
c.restart()
|
||||
.min_delay(Duration::from_secs(10))
|
||||
.max_delay(Duration::from_secs(20))
|
||||
.target_cooldown(Duration::from_secs(15))
|
||||
.apply()
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(180))
|
||||
.build();
|
||||
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn load_progression_test() -> Result<()> {
|
||||
for rate in [5, 10, 20, 30] {
|
||||
println!("Testing with rate: {}", rate);
|
||||
|
||||
let mut plan =
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(50)
|
||||
.transactions_with(|txs| txs.rate(rate).users(20))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn sustained_load_test() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(4).executors(2))
|
||||
.wallets(100)
|
||||
.transactions_with(|txs| txs.rate(15).users(50))
|
||||
.da_with(|da| da.channel_rate(2).blob_rate(3))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(300))
|
||||
.build();
|
||||
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
29
examples/doc-snippets/src/examples_chaos_resilience.rs
Normal file
29
examples/doc-snippets/src/examples_chaos_resilience.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub async fn chaos_resilience() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(4).executors(2))
|
||||
.enable_node_control()
|
||||
.wallets(20)
|
||||
.transactions_with(|txs| txs.rate(3).users(10))
|
||||
.chaos_with(|c| {
|
||||
c.restart()
|
||||
.min_delay(Duration::from_secs(20))
|
||||
.max_delay(Duration::from_secs(40))
|
||||
.target_cooldown(Duration::from_secs(30))
|
||||
.apply()
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(120))
|
||||
.build();
|
||||
|
||||
let deployer = ComposeDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
22
examples/doc-snippets/src/examples_da_and_transactions.rs
Normal file
22
examples/doc-snippets/src/examples_da_and_transactions.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn da_and_transactions() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.wallets(30)
|
||||
.transactions_with(|txs| txs.rate(5).users(15))
|
||||
.da_with(|da| da.channel_rate(2).blob_rate(2))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(90))
|
||||
.build();
|
||||
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
19
examples/doc-snippets/src/examples_simple_consensus.rs
Normal file
19
examples/doc-snippets/src/examples_simple_consensus.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn simple_consensus() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(30))
|
||||
.build();
|
||||
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
21
examples/doc-snippets/src/examples_transaction_workload.rs
Normal file
21
examples/doc-snippets/src/examples_transaction_workload.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn transaction_workload() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(0))
|
||||
.wallets(20)
|
||||
.transactions_with(|txs| txs.rate(5).users(10))
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{Deployer, Runner, Scenario};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct YourError;
|
||||
|
||||
pub struct YourDeployer;
|
||||
|
||||
#[async_trait]
|
||||
impl Deployer for YourDeployer {
|
||||
type Error = YourError;
|
||||
|
||||
async fn deploy(&self, _scenario: &Scenario<()>) -> Result<Runner, Self::Error> {
|
||||
// Provision infrastructure
|
||||
// Wait for readiness
|
||||
// Return Runner
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub trait YourExpectationDslExt: Sized {
|
||||
fn expect_your_condition(self) -> Self;
|
||||
}
|
||||
|
||||
impl<Caps> YourExpectationDslExt for testing_framework_core::scenario::Builder<Caps> {
|
||||
fn expect_your_condition(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_in_examples() {
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||
.expect_your_condition()
|
||||
.build();
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{DynError, Expectation, RunContext};
|
||||
|
||||
pub struct YourExpectation;
|
||||
|
||||
#[async_trait]
|
||||
impl Expectation for YourExpectation {
|
||||
fn name(&self) -> &'static str {
|
||||
"your_expectation"
|
||||
}
|
||||
|
||||
async fn evaluate(&mut self, _ctx: &RunContext) -> Result<(), DynError> {
|
||||
// implementation
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
pub struct YourWorkloadBuilder;
|
||||
|
||||
impl YourWorkloadBuilder {
|
||||
pub fn some_config(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ScenarioBuilderExt: Sized {
|
||||
fn your_workload(self) -> YourWorkloadBuilder;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{DynError, RunContext, Workload};
|
||||
|
||||
pub struct YourWorkload;
|
||||
|
||||
#[async_trait]
|
||||
impl Workload for YourWorkload {
|
||||
fn name(&self) -> &'static str {
|
||||
"your_workload"
|
||||
}
|
||||
|
||||
async fn start(&self, _ctx: &RunContext) -> Result<(), DynError> {
|
||||
// implementation
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub struct YourWorkloadBuilder;
|
||||
|
||||
impl YourWorkloadBuilder {
|
||||
pub fn some_config(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait YourWorkloadDslExt: Sized {
|
||||
fn your_workload_with<F>(self, configurator: F) -> Self
|
||||
where
|
||||
F: FnOnce(YourWorkloadBuilder) -> YourWorkloadBuilder;
|
||||
}
|
||||
|
||||
impl<Caps> YourWorkloadDslExt for testing_framework_core::scenario::Builder<Caps> {
|
||||
fn your_workload_with<F>(self, configurator: F) -> Self
|
||||
where
|
||||
F: FnOnce(YourWorkloadBuilder) -> YourWorkloadBuilder,
|
||||
{
|
||||
let _ = configurator(YourWorkloadBuilder);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_in_examples() {
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||
.your_workload_with(|w| w.some_config())
|
||||
.build();
|
||||
}
|
||||
46
examples/doc-snippets/src/lib.rs
Normal file
46
examples/doc-snippets/src/lib.rs
Normal file
@ -0,0 +1,46 @@
|
||||
#![allow(dead_code, unused_imports, unused_variables)]
|
||||
|
||||
mod architecture_overview_builder_api;
|
||||
mod chaos_workloads_random_restart;
|
||||
mod custom_workload_example_expectation;
|
||||
mod custom_workload_example_workload;
|
||||
mod dsl_cheat_sheet_build;
|
||||
mod dsl_cheat_sheet_build_complete_example;
|
||||
mod dsl_cheat_sheet_deployers;
|
||||
mod dsl_cheat_sheet_expectations;
|
||||
mod dsl_cheat_sheet_imports;
|
||||
mod dsl_cheat_sheet_run_duration;
|
||||
mod dsl_cheat_sheet_topology;
|
||||
mod dsl_cheat_sheet_transactions_workload;
|
||||
mod dsl_cheat_sheet_wallets;
|
||||
mod dsl_cheat_sheet_workload_chaos;
|
||||
mod dsl_cheat_sheet_workload_da;
|
||||
mod dsl_cheat_sheet_workload_execution;
|
||||
mod examples_advanced_aggressive_chaos_test;
|
||||
mod examples_advanced_load_progression_test;
|
||||
mod examples_advanced_sustained_load_test;
|
||||
mod examples_chaos_resilience;
|
||||
mod examples_da_and_transactions;
|
||||
mod examples_simple_consensus;
|
||||
mod examples_transaction_workload;
|
||||
mod internal_crate_reference_add_deployer;
|
||||
mod internal_crate_reference_add_expectation_builder_ext;
|
||||
mod internal_crate_reference_add_expectation_trait;
|
||||
mod internal_crate_reference_add_workload_builder_ext;
|
||||
mod internal_crate_reference_add_workload_trait;
|
||||
mod internal_crate_reference_add_workload_use_in_examples;
|
||||
mod node_control_accessing_control;
|
||||
mod node_control_trait;
|
||||
mod quickstart_adjust_topology;
|
||||
mod quickstart_core_api_pattern;
|
||||
mod quickstart_step_1_topology;
|
||||
mod quickstart_step_2_wallets;
|
||||
mod quickstart_step_3_workloads;
|
||||
mod quickstart_step_4_expectation;
|
||||
mod quickstart_step_5_run_duration;
|
||||
mod quickstart_step_6_deploy_and_execute;
|
||||
mod quickstart_swap_deployer_compose;
|
||||
mod testing_philosophy_declarative_over_imperative;
|
||||
mod testing_philosophy_determinism_first;
|
||||
mod testing_philosophy_minimum_run_windows;
|
||||
mod testing_philosophy_protocol_time_not_wall_time;
|
||||
19
examples/doc-snippets/src/node_control_accessing_control.rs
Normal file
19
examples/doc-snippets/src/node_control_accessing_control.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::{DynError, RunContext, Workload};
|
||||
|
||||
struct RestartWorkload;
|
||||
|
||||
#[async_trait]
|
||||
impl Workload for RestartWorkload {
|
||||
fn name(&self) -> &str {
|
||||
"restart_workload"
|
||||
}
|
||||
|
||||
async fn start(&self, ctx: &RunContext) -> Result<(), DynError> {
|
||||
if let Some(control) = ctx.node_control() {
|
||||
// Restart the first validator (index 0) if supported.
|
||||
control.restart_validator(0).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
8
examples/doc-snippets/src/node_control_trait.rs
Normal file
8
examples/doc-snippets/src/node_control_trait.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use async_trait::async_trait;
|
||||
use testing_framework_core::scenario::DynError;
|
||||
|
||||
#[async_trait]
|
||||
pub trait NodeControlHandle: Send + Sync {
|
||||
async fn restart_validator(&self, index: usize) -> Result<(), DynError>;
|
||||
async fn restart_executor(&self, index: usize) -> Result<(), DynError>;
|
||||
}
|
||||
16
examples/doc-snippets/src/quickstart_adjust_topology.rs
Normal file
16
examples/doc-snippets/src/quickstart_adjust_topology.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
|
||||
pub async fn run_with_env_overrides() -> Result<()> {
|
||||
// Uses NOMOS_DEMO_* env vars (or legacy *_DEMO_* vars)
|
||||
let mut plan = ScenarioBuilder::with_node_counts(3, 2)
|
||||
.with_run_duration(std::time::Duration::from_secs(120))
|
||||
.build();
|
||||
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
31
examples/doc-snippets/src/quickstart_core_api_pattern.rs
Normal file
31
examples/doc-snippets/src/quickstart_core_api_pattern.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub async fn run_local_demo() -> Result<()> {
|
||||
// Define the scenario (1 validator + 1 executor, tx + DA workload)
|
||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(1))
|
||||
.wallets(1_000)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(500) // use 500 of the seeded wallets
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // 1 channel
|
||||
.blob_rate(1) // target 1 blob per block
|
||||
.headroom_percent(20) // default headroom when sizing channels
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.build();
|
||||
|
||||
// Deploy and run
|
||||
let deployer = LocalDeployer::default();
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
9
examples/doc-snippets/src/quickstart_step_1_topology.rs
Normal file
9
examples/doc-snippets/src/quickstart_step_1_topology.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub fn step_1_topology() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::topology_with(|t| {
|
||||
t.network_star() // Star topology: all nodes connect to seed
|
||||
.validators(1) // 1 validator node
|
||||
.executors(1) // 1 executor node (validator + DA dispersal)
|
||||
})
|
||||
}
|
||||
6
examples/doc-snippets/src/quickstart_step_2_wallets.rs
Normal file
6
examples/doc-snippets/src/quickstart_step_2_wallets.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn step_2_wallets() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1).wallets(1_000) // Seed 1,000 funded wallet accounts
|
||||
}
|
||||
16
examples/doc-snippets/src/quickstart_step_3_workloads.rs
Normal file
16
examples/doc-snippets/src/quickstart_step_3_workloads.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn step_3_workloads() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1)
|
||||
.wallets(1_000)
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
.users(500) // Use 500 of the 1,000 wallets
|
||||
})
|
||||
.da_with(|da| {
|
||||
da.channel_rate(1) // 1 DA channel (more spawned with headroom)
|
||||
.blob_rate(1) // target 1 blob per block
|
||||
.headroom_percent(20) // default headroom when sizing channels
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn step_4_expectation() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1).expect_consensus_liveness() // This says what success means: blocks must be produced continuously.
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
|
||||
pub fn step_5_run_duration() -> testing_framework_core::scenario::Builder<()> {
|
||||
ScenarioBuilder::with_node_counts(1, 1).with_run_duration(Duration::from_secs(60))
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_local::LocalDeployer;
|
||||
|
||||
pub async fn step_6_deploy_and_execute() -> Result<()> {
|
||||
let mut plan = ScenarioBuilder::with_node_counts(1, 1).build();
|
||||
|
||||
let deployer = LocalDeployer::default(); // Use local process deployer
|
||||
let runner = deployer.deploy(&plan).await?; // Provision infrastructure
|
||||
let _handle = runner.run(&mut plan).await?; // Execute workloads & expectations
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
use anyhow::Result;
|
||||
use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
||||
use testing_framework_runner_compose::ComposeDeployer;
|
||||
|
||||
pub async fn run_with_compose_deployer() -> Result<()> {
|
||||
// ... same scenario definition ...
|
||||
let mut plan = ScenarioBuilder::with_node_counts(1, 1).build();
|
||||
|
||||
let deployer = ComposeDeployer::default(); // Use Docker Compose
|
||||
let runner = deployer.deploy(&plan).await?;
|
||||
let _handle = runner.run(&mut plan).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn declarative_over_imperative() {
|
||||
// Good: declarative
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
// Bad: imperative (framework doesn't work this way)
|
||||
// spawn_validator(); spawn_executor();
|
||||
// loop { submit_tx(); check_block(); }
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||
|
||||
pub fn determinism_first() {
|
||||
// Separate: functional test (deterministic)
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
// Separate: chaos test (introduces randomness)
|
||||
let _chaos_plan =
|
||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||
.enable_node_control()
|
||||
.chaos_with(|c| {
|
||||
c.restart()
|
||||
.min_delay(Duration::from_secs(30))
|
||||
.max_delay(Duration::from_secs(60))
|
||||
.target_cooldown(Duration::from_secs(45))
|
||||
.apply()
|
||||
})
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn minimum_run_windows() {
|
||||
// Bad: too short (~2 blocks with default 2s slots, 0.9 coeff)
|
||||
let _too_short = ScenarioBuilder::with_node_counts(1, 0)
|
||||
.with_run_duration(Duration::from_secs(5))
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
|
||||
// Good: enough blocks for assertions (~27 blocks with default 2s slots, 0.9
|
||||
// coeff)
|
||||
let _good = ScenarioBuilder::with_node_counts(1, 0)
|
||||
.with_run_duration(Duration::from_secs(60))
|
||||
.expect_consensus_liveness()
|
||||
.build();
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use testing_framework_core::scenario::ScenarioBuilder;
|
||||
use testing_framework_workflows::ScenarioBuilderExt;
|
||||
|
||||
pub fn protocol_time_not_wall_time() {
|
||||
// Good: protocol-oriented thinking
|
||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||
.transactions_with(|txs| {
|
||||
txs.rate(5) // 5 transactions per block
|
||||
})
|
||||
.with_run_duration(Duration::from_secs(60)) // Let framework calculate expected blocks
|
||||
.expect_consensus_liveness() // "Did we produce the expected blocks?"
|
||||
.build();
|
||||
|
||||
// Bad: wall-clock assumptions
|
||||
// "I expect exactly 30 blocks in 60 seconds"
|
||||
// This breaks on slow CI where slot timing might drift
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user