diff --git a/examples/tests/manual_cluster.rs b/examples/tests/manual_cluster.rs index 208e95c..7d50c0c 100644 --- a/examples/tests/manual_cluster.rs +++ b/examples/tests/manual_cluster.rs @@ -1,11 +1,11 @@ -use std::{env, time::Duration}; +use std::time::Duration; use anyhow::Result; use testing_framework_core::{ scenario::{PeerSelection, StartNodeOptions}, topology::config::TopologyConfig, }; -use testing_framework_runner_local::ManualCluster; +use testing_framework_runner_local::LocalDeployer; use tokio::time::sleep; use tracing_subscriber::fmt::try_init; @@ -15,18 +15,12 @@ const MAX_HEIGHT_DIFF: u64 = 5; #[ignore = "run manually with `cargo test -p runner-examples -- --ignored manual_cluster_two_clusters_merge`"] async fn manual_cluster_two_clusters_merge() -> Result<()> { let _ = try_init(); - let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .parent() - .expect("examples crate should live under the workspace root"); - let circuits_dir = workspace_root.join("testing-framework/assets/stack/circuits"); - unsafe { - env::set_var("LOGOS_BLOCKCHAIN_CIRCUITS", circuits_dir); - } // Required env vars (set on the command line when running this test): // - `POL_PROOF_DEV_MODE=true` // - `RUST_LOG=info` (optional) let config = TopologyConfig::with_node_numbers(2, 0); - let cluster = ManualCluster::from_config(config)?; + let deployer = LocalDeployer::new(); + let cluster = deployer.manual_cluster(config)?; // Nodes are stopped automatically when the cluster is dropped. println!("starting validator a"); diff --git a/testing-framework/core/src/lib.rs b/testing-framework/core/src/lib.rs index 672df4c..a83c6c9 100644 --- a/testing-framework/core/src/lib.rs +++ b/testing-framework/core/src/lib.rs @@ -1,4 +1,5 @@ pub mod kzg; +pub mod manual; pub mod nodes; pub mod scenario; pub mod topology; diff --git a/testing-framework/core/src/manual.rs b/testing-framework/core/src/manual.rs new file mode 100644 index 0000000..96ff684 --- /dev/null +++ b/testing-framework/core/src/manual.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; + +use crate::scenario::{DynError, StartNodeOptions, StartedNode}; + +/// Interface for imperative, deployer-backed manual clusters. +#[async_trait] +pub trait ManualClusterHandle: Send + Sync { + async fn start_validator_with( + &self, + name: &str, + options: StartNodeOptions, + ) -> Result; + + async fn start_executor_with( + &self, + name: &str, + options: StartNodeOptions, + ) -> Result; + + async fn wait_network_ready(&self) -> Result<(), DynError>; +} diff --git a/testing-framework/deployers/local/Cargo.toml b/testing-framework/deployers/local/Cargo.toml index 4a49a04..03b714f 100644 --- a/testing-framework/deployers/local/Cargo.toml +++ b/testing-framework/deployers/local/Cargo.toml @@ -13,15 +13,15 @@ version = "0.1.0" workspace = true [dependencies] -async-trait = "0.1" +async-trait = "0.1" logos-blockchain-executor = { workspace = true } -nomos-libp2p = { workspace = true } -nomos-network = { workspace = true } -nomos-node = { workspace = true } -nomos-utils = { workspace = true } -rand = { workspace = true } -testing-framework-config = { workspace = true } -testing-framework-core = { path = "../../core" } -thiserror = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } +nomos-libp2p = { workspace = true } +nomos-network = { workspace = true } +nomos-node = { workspace = true } +nomos-utils = { workspace = true } +rand = { workspace = true } +testing-framework-config = { workspace = true } +testing-framework-core = { path = "../../core" } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } diff --git a/testing-framework/deployers/local/src/lib.rs b/testing-framework/deployers/local/src/lib.rs index 60d9d8e..7bb7b39 100644 --- a/testing-framework/deployers/local/src/lib.rs +++ b/testing-framework/deployers/local/src/lib.rs @@ -2,6 +2,6 @@ mod manual; mod node_control; mod runner; -pub use manual::{ManualCluster, ManualClusterError}; +pub use manual::{LocalManualCluster, ManualClusterError}; pub use node_control::{LocalDynamicError, LocalDynamicNodes, LocalDynamicSeed}; pub use runner::{LocalDeployer, LocalDeployerError}; diff --git a/testing-framework/deployers/local/src/manual/mod.rs b/testing-framework/deployers/local/src/manual/mod.rs index 4d2d094..621c93d 100644 --- a/testing-framework/deployers/local/src/manual/mod.rs +++ b/testing-framework/deployers/local/src/manual/mod.rs @@ -1,6 +1,7 @@ use testing_framework_core::{ + manual::ManualClusterHandle, nodes::ApiClient, - scenario::{StartNodeOptions, StartedNode}, + scenario::{DynError, StartNodeOptions, StartedNode}, topology::{ config::{TopologyBuildError, TopologyBuilder, TopologyConfig}, readiness::{ReadinessCheck, ReadinessError}, @@ -26,12 +27,12 @@ pub enum ManualClusterError { } /// Imperative, in-process cluster that can start nodes on demand. -pub struct ManualCluster { +pub struct LocalManualCluster { nodes: LocalDynamicNodes, } -impl ManualCluster { - pub fn from_config(config: TopologyConfig) -> Result { +impl LocalManualCluster { + pub(crate) fn from_config(config: TopologyConfig) -> Result { let builder = TopologyBuilder::new(config); let descriptors = builder .build() @@ -100,8 +101,35 @@ impl ManualCluster { } } -impl Drop for ManualCluster { +impl Drop for LocalManualCluster { fn drop(&mut self) { self.stop_all(); } } + +#[async_trait::async_trait] +impl ManualClusterHandle for LocalManualCluster { + async fn start_validator_with( + &self, + name: &str, + options: StartNodeOptions, + ) -> Result { + self.start_validator_with(name, options) + .await + .map_err(|err| err.into()) + } + + async fn start_executor_with( + &self, + name: &str, + options: StartNodeOptions, + ) -> Result { + self.start_executor_with(name, options) + .await + .map_err(|err| err.into()) + } + + async fn wait_network_ready(&self) -> Result<(), DynError> { + self.wait_network_ready().await.map_err(|err| err.into()) + } +} diff --git a/testing-framework/deployers/local/src/runner.rs b/testing-framework/deployers/local/src/runner.rs index c440357..a89ae9c 100644 --- a/testing-framework/deployers/local/src/runner.rs +++ b/testing-framework/deployers/local/src/runner.rs @@ -7,6 +7,7 @@ use testing_framework_core::{ RunContext, Runner, Scenario, ScenarioError, spawn_block_feed, }, topology::{ + config::TopologyConfig, deployment::{SpawnTopologyError, Topology}, readiness::ReadinessError, }, @@ -14,7 +15,10 @@ use testing_framework_core::{ use thiserror::Error; use tracing::{debug, info}; -use crate::node_control::{LocalDynamicNodes, LocalDynamicSeed}; +use crate::{ + manual::{LocalManualCluster, ManualClusterError}, + node_control::{LocalDynamicNodes, LocalDynamicSeed}, +}; /// Spawns validators and executors as local processes, reusing the existing /// integration harness. #[derive(Clone)] @@ -130,13 +134,25 @@ impl LocalDeployer { Self::default() } - async fn prepare_topology(scenario: &Scenario) -> Result { + /// Build a manual cluster using this deployer's local implementation. + pub fn manual_cluster( + &self, + config: TopologyConfig, + ) -> Result { + LocalManualCluster::from_config(config) + } + + async fn prepare_topology( + scenario: &Scenario, + ) -> Result { let descriptors = scenario.topology(); + info!( validators = descriptors.validators().len(), executors = descriptors.executors().len(), "spawning local validators/executors" ); + let topology = descriptors .clone() .spawn_local() @@ -149,6 +165,7 @@ impl LocalDeployer { })?; info!("local nodes are ready"); + Ok(topology) } } @@ -161,6 +178,7 @@ impl Default for LocalDeployer { async fn wait_for_readiness(topology: &Topology) -> Result<(), ReadinessError> { info!("waiting for local network readiness"); + topology.wait_network_ready().await?; Ok(()) }