diff --git a/src/bitcoin.rs b/src/bitcoin.rs index 3ef09c5..09b1368 100644 --- a/src/bitcoin.rs +++ b/src/bitcoin.rs @@ -22,7 +22,7 @@ use super::{ traits::{NodeT, Restart, SpawnOutput}, Result, }; -use crate::{log_provider::LogPathProvider, node::NodeKind}; +use crate::{config::ConfigBounds, log_provider::LogPathProvider, node::NodeKind}; pub const FINALITY_DEPTH: u64 = 30; @@ -293,7 +293,12 @@ pub struct BitcoinNodeCluster { } impl BitcoinNodeCluster { - pub async fn new(ctx: &TestContext) -> Result { + pub async fn new(ctx: &TestContext) -> Result + where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, + { let n_nodes = ctx.config.test_case.n_nodes; let mut cluster = Self { inner: Vec::with_capacity(n_nodes), diff --git a/src/config/docker.rs b/src/config/docker.rs index c349af4..33c3842 100644 --- a/src/config/docker.rs +++ b/src/config/docker.rs @@ -1,9 +1,8 @@ use std::{fmt::Debug, path::PathBuf}; -use serde::Serialize; use tracing::debug; -use super::{BitcoinConfig, FullL2NodeConfig}; +use super::{BitcoinConfig, ConfigBounds, FullL2NodeConfig}; use crate::{ node::{get_citrea_args, NodeKind}, utils::get_genesis_path, @@ -61,7 +60,7 @@ impl From<&BitcoinConfig> for DockerConfig { impl From> for DockerConfig where - T: Clone + Debug + Serialize + Send + Sync, + T: ConfigBounds, { fn from(config: FullL2NodeConfig) -> Self { let kind = config.kind(); diff --git a/src/config/mod.rs b/src/config/mod.rs index 8992616..d3ab5ec 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -25,6 +25,9 @@ pub use crate::citrea_config::{ }; use crate::{log_provider::LogPathProvider, node::NodeKind, Result}; +pub trait ConfigBounds: Clone + Serialize + Debug + Send + Sync + Default {} +impl ConfigBounds for T where T: Clone + Serialize + Debug + Send + Sync + Default {} + #[derive(Clone, Debug, Default)] pub enum DaLayer { #[default] @@ -63,7 +66,7 @@ where impl FullL2NodeConfig where - T: Clone + Serialize + Debug + Send + Sync, + T: ConfigBounds, { pub fn new( kind: NodeKind, @@ -100,18 +103,14 @@ where } } -pub type FullSequencerConfig = FullL2NodeConfig; -pub type FullBatchProverConfig = FullL2NodeConfig; -pub type FullLightClientProverConfig = FullL2NodeConfig; - -#[derive(Serialize, Clone, Debug)] +#[derive(Serialize, Clone, Debug, Default)] pub struct EmptyConfig; pub type FullFullNodeConfig = FullL2NodeConfig; impl FullL2NodeConfig where - T: Clone + Serialize + Debug + Send + Sync, + T: ConfigBounds, { pub fn dir(&self) -> &PathBuf { &self.base.dir @@ -183,7 +182,7 @@ where impl LogPathProvider for FullL2NodeConfig where - T: Clone + Serialize + Debug + Send + Sync, + T: ConfigBounds, { fn kind(&self) -> NodeKind { self.kind() diff --git a/src/config/test.rs b/src/config/test.rs index f67755d..9910463 100644 --- a/src/config/test.rs +++ b/src/config/test.rs @@ -1,14 +1,19 @@ use super::{ - bitcoin::BitcoinConfig, test_case::TestCaseConfig, FullBatchProverConfig, FullFullNodeConfig, - FullLightClientProverConfig, FullSequencerConfig, + bitcoin::BitcoinConfig, test_case::TestCaseConfig, ConfigBounds, FullFullNodeConfig, + FullL2NodeConfig, }; #[derive(Clone)] -pub struct TestConfig { +pub struct TestConfig +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ pub test_case: TestCaseConfig, pub bitcoin: Vec, - pub sequencer: FullSequencerConfig, - pub batch_prover: FullBatchProverConfig, - pub light_client_prover: FullLightClientProverConfig, + pub sequencer: FullL2NodeConfig, + pub batch_prover: FullL2NodeConfig, + pub light_client_prover: FullL2NodeConfig, pub full_node: FullFullNodeConfig, } diff --git a/src/framework.rs b/src/framework.rs index ad7ff2b..cc44cee 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -12,13 +12,13 @@ use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, Env use crate::{ bitcoin::BitcoinNodeCluster, config::{ - BitcoinConfig, BitcoinServiceConfig, EmptyConfig, FullBatchProverConfig, - FullFullNodeConfig, FullLightClientProverConfig, FullSequencerConfig, RollupConfig, - RpcConfig, RunnerConfig, StorageConfig, TestCaseConfig, TestConfig, + BitcoinConfig, BitcoinServiceConfig, ConfigBounds, EmptyConfig, FullFullNodeConfig, + FullL2NodeConfig, RollupConfig, RpcConfig, RunnerConfig, StorageConfig, TestCaseConfig, + TestConfig, }, docker::DockerEnv, log_provider::{LogPathProvider, LogPathProviderErased}, - node::{BatchProver, FullNode, LightClientProver, NodeKind, Sequencer}, + node::{FullNode, Node, NodeKind}, test_case::TestCase, traits::NodeT, utils::{ @@ -27,13 +27,23 @@ use crate::{ Result, }; -pub struct TestContext { - pub config: TestConfig, +pub struct TestContext +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ + pub config: TestConfig, pub docker: Arc>, } -impl TestContext { - fn new(config: TestConfig, docker: Option) -> Self { +impl TestContext +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ + fn new(config: TestConfig, docker: Option) -> Self { Self { config, docker: Arc::new(docker), @@ -41,12 +51,17 @@ impl TestContext { } } -pub struct TestFramework { - ctx: TestContext, +pub struct TestFramework +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ + ctx: TestContext, pub bitcoin_nodes: BitcoinNodeCluster, - pub sequencer: Option, - pub batch_prover: Option, - pub light_client_prover: Option, + pub sequencer: Option>, + pub batch_prover: Option>, + pub light_client_prover: Option>, pub full_node: Option, pub initial_da_height: u64, } @@ -59,8 +74,13 @@ async fn create_optional(pred: bool, f: impl Future>) -> R } } -impl TestFramework { - pub async fn new() -> Result { +impl TestFramework +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ + pub async fn new>() -> Result { setup_logging(); let test_case = T::test_config(); @@ -69,7 +89,7 @@ impl TestFramework { } else { None }; - let config = generate_test_config::(test_case, &docker)?; + let config = generate_test_config::(test_case, &docker)?; anyhow::ensure!( config.test_case.n_nodes > 0, @@ -99,7 +119,7 @@ impl TestFramework { // Has to initialize sequencer first since provers and full node depend on it self.sequencer = create_optional( self.ctx.config.test_case.with_sequencer, - Sequencer::new( + Node::::new( &self.ctx.config.sequencer, bitcoin_config, Arc::clone(&self.ctx.docker), @@ -110,7 +130,7 @@ impl TestFramework { (self.batch_prover, self.light_client_prover, self.full_node) = tokio::try_join!( create_optional( self.ctx.config.test_case.with_batch_prover, - BatchProver::new( + Node::::new( &self.ctx.config.batch_prover, bitcoin_config, Arc::clone(&self.ctx.docker) @@ -118,7 +138,7 @@ impl TestFramework { ), create_optional( self.ctx.config.test_case.with_light_client_prover, - LightClientProver::new( + Node::::new( &self.ctx.config.light_client_prover, bitcoin_config, Arc::clone(&self.ctx.docker) @@ -260,10 +280,15 @@ impl TestFramework { } } -fn generate_test_config( +fn generate_test_config, S, BP, LP>( test_case: TestCaseConfig, docker: &Option, -) -> Result { +) -> Result> +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ let env = T::test_env(); let bitcoin = T::bitcoin_config(); let batch_prover = T::batch_prover_config(); @@ -430,7 +455,7 @@ fn generate_test_config( let citrea_docker_image = std::env::var("CITREA_DOCKER_IMAGE").ok(); Ok(TestConfig { bitcoin: bitcoin_confs, - sequencer: FullSequencerConfig::new( + sequencer: FullL2NodeConfig::::new( NodeKind::Sequencer, sequencer, sequencer_rollup, @@ -438,7 +463,7 @@ fn generate_test_config( sequencer_dir, env.sequencer(), )?, - batch_prover: FullBatchProverConfig::new( + batch_prover: FullL2NodeConfig::::new( NodeKind::BatchProver, batch_prover, batch_prover_rollup, @@ -446,7 +471,7 @@ fn generate_test_config( batch_prover_dir, env.batch_prover(), )?, - light_client_prover: FullLightClientProverConfig::new( + light_client_prover: FullL2NodeConfig::::new( NodeKind::LightClientProver, light_client_prover, light_client_prover_rollup, diff --git a/src/node.rs b/src/node.rs index 26c6e14..dc37a73 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,5 +1,5 @@ use std::{ - fmt::{self, Debug}, + fmt::{self}, fs::File, process::Stdio, sync::{ @@ -12,7 +12,6 @@ use std::{ use anyhow::{bail, Context}; use async_trait::async_trait; use bitcoincore_rpc::{Auth, Client as BitcoinClient}; -use serde::Serialize; use tokio::{ process::Command, time::{sleep, Instant}, @@ -22,8 +21,8 @@ use tracing::{debug, info, trace}; use crate::{ client::Client, config::{ - BatchProverConfig, BitcoinConfig, DockerConfig, EmptyConfig, FullL2NodeConfig, - LightClientProverConfig, SequencerConfig, + BatchProverConfig, BitcoinConfig, ConfigBounds, DockerConfig, EmptyConfig, + FullL2NodeConfig, LightClientProverConfig, SequencerConfig, }, docker::DockerEnv, log_provider::LogPathProvider, @@ -72,7 +71,7 @@ pub type BatchProver = Node; pub struct Node where - C: Clone + Debug + Serialize + Send + Sync, + C: ConfigBounds, { spawn_output: SpawnOutput, pub config: FullL2NodeConfig, @@ -83,7 +82,7 @@ where impl Node where - C: Clone + Debug + Serialize + Send + Sync, + C: ConfigBounds, { pub async fn new( config: &FullL2NodeConfig, @@ -192,7 +191,7 @@ where #[async_trait] impl NodeT for Node where - C: Clone + Debug + Serialize + Send + Sync, + C: ConfigBounds, DockerConfig: From>, { type Config = FullL2NodeConfig; @@ -246,7 +245,7 @@ where #[async_trait] impl Restart for Node where - C: Clone + Serialize + Debug + Send + Sync, + C: ConfigBounds, DockerConfig: From>, { async fn wait_until_stopped(&mut self) -> Result<()> { @@ -286,7 +285,7 @@ where pub fn get_citrea_args(config: &FullL2NodeConfig) -> Vec where - C: Clone + Debug + Serialize + Send + Sync, + C: ConfigBounds, { let node_config_args = config.get_node_config_args().unwrap_or_default(); let rollup_config_args = config.get_rollup_config_args(); diff --git a/src/test_case.rs b/src/test_case.rs index e1aa153..0eafe5d 100644 --- a/src/test_case.rs +++ b/src/test_case.rs @@ -3,6 +3,7 @@ use std::{ io::Write, + marker::PhantomData, panic::{self}, path::Path, time::Duration, @@ -18,10 +19,7 @@ use super::{ framework::TestFramework, Result, }; -use crate::{ - config::{BatchProverConfig, LightClientProverConfig, SequencerConfig}, - traits::NodeT, -}; +use crate::{config::ConfigBounds, traits::NodeT}; const CITREA_ENV: &str = "CITREA_E2E_TEST_BINARY"; const BITCOIN_ENV: &str = "BITCOIN_E2E_TEST_BINARY"; @@ -31,16 +29,32 @@ const CLEMENTINE_ENV: &str = "CLEMENTINE_E2E_TEST_BINARY"; /// It creates a test framework with the associated configs, spawns required nodes, connects them, /// runs the test case, and performs cleanup afterwards. The `run` method handles any panics that /// might occur during test execution and takes care of cleaning up and stopping the child processes. -pub struct TestCaseRunner(T); +pub struct TestCaseRunner, S, BP, LP> +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ + test_case: T, + _marker: PhantomData<(S, BP, LP)>, +} -impl TestCaseRunner { +impl, S, BP, LP> TestCaseRunner +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ /// Creates a new `TestCaseRunner`` with the given test case. pub fn new(test_case: T) -> Self { - Self(test_case) + Self { + test_case, + _marker: PhantomData, + } } /// Internal method to fund the wallets, connect the nodes, wait for them to be ready. - async fn prepare(&self, f: &mut TestFramework) -> Result<()> { + async fn prepare(&self, f: &mut TestFramework) -> Result<()> { f.fund_da_wallets().await?; f.init_nodes().await?; f.bitcoin_nodes.connect_nodes().await?; @@ -69,10 +83,10 @@ impl TestCaseRunner { Ok(()) } - async fn run_test_case(&mut self, f: &mut TestFramework) -> Result<()> { + async fn run_test_case(&mut self, f: &mut TestFramework) -> Result<()> { self.prepare(f).await?; - self.0.setup(f).await?; - self.0.run_test(f).await + self.test_case.setup(f).await?; + self.test_case.run_test(f).await } /// Executes the test case, handling any panics and performing cleanup. @@ -112,7 +126,7 @@ impl TestCaseRunner { f.stop().await?; // Additional test cleanup - self.0.cleanup().await?; + self.test_case.cleanup().await?; std::io::stdout().flush()?; std::io::stderr().flush()?; @@ -124,7 +138,7 @@ impl TestCaseRunner { } } - pub fn set_binary_path, P: AsRef>(self, env_var: S, path: P) -> Self { + pub fn set_binary_path, P: AsRef>(self, env_var: V, path: P) -> Self { std::env::set_var(env_var.as_ref(), path.as_ref().display().to_string()); self } @@ -166,7 +180,12 @@ impl TestCaseRunner { /// and inner test logic. It provides default configurations that should be sane for most test cases, /// which can be overridden by implementing the associated methods. #[async_trait] -pub trait TestCase: Send + Sync + 'static { +pub trait TestCase: Send + Sync + 'static +where + S: ConfigBounds, + BP: ConfigBounds, + LP: ConfigBounds, +{ /// Returns the test case configuration. /// Override this method to provide custom test configurations. fn test_config() -> TestCaseConfig { @@ -187,25 +206,25 @@ pub trait TestCase: Send + Sync + 'static { /// Returns the sequencer configuration for the test. /// Override this method to provide a custom sequencer configuration. - fn sequencer_config() -> SequencerConfig { - SequencerConfig::default() + fn sequencer_config() -> S { + S::default() } /// Returns the batch prover configuration for the test. /// Override this method to provide a custom batch prover configuration. - fn batch_prover_config() -> BatchProverConfig { - BatchProverConfig::default() + fn batch_prover_config() -> BP { + BP::default() } /// Returns the light client prover configuration for the test. /// Override this method to provide a custom light client prover configuration. - fn light_client_prover_config() -> LightClientProverConfig { - LightClientProverConfig::default() + fn light_client_prover_config() -> LP { + LP::default() } /// Returns the test setup /// Override this method to add custom initialization logic - async fn setup(&self, _framework: &mut TestFramework) -> Result<()> { + async fn setup(&self, _framework: &mut TestFramework) -> Result<()> { Ok(()) } @@ -216,7 +235,7 @@ pub trait TestCase: Send + Sync + 'static { /// /// # Arguments /// * `framework` - A reference to the TestFramework instance - async fn run_test(&mut self, framework: &mut TestFramework) -> Result<()>; + async fn run_test(&mut self, framework: &mut TestFramework) -> Result<()>; async fn cleanup(&self) -> Result<()> { Ok(()) diff --git a/tests/bitcoin.rs b/tests/bitcoin.rs index 424fbf6..f7a4cff 100644 --- a/tests/bitcoin.rs +++ b/tests/bitcoin.rs @@ -4,17 +4,22 @@ use anyhow::bail; use async_trait::async_trait; use bitcoincore_rpc::{json::IndexStatus, RpcApi}; use citrea_e2e::{ - config::{BitcoinConfig, TestCaseConfig, TestCaseDockerConfig}, - framework::TestFramework, + config::{ + BatchProverConfig, BitcoinConfig, LightClientProverConfig, SequencerConfig, TestCaseConfig, + TestCaseDockerConfig, + }, test_case::{TestCase, TestCaseRunner}, traits::Restart, Result, }; +#[path = "common/mod.rs"] +mod common; +use common::*; struct BasicSyncTest; #[async_trait] -impl TestCase for BasicSyncTest { +impl TestCase for BasicSyncTest { fn test_config() -> TestCaseConfig { TestCaseConfig { with_sequencer: false, @@ -75,7 +80,7 @@ async fn test_basic_sync() -> Result<()> { struct RestartBitcoinTest; #[async_trait] -impl TestCase for RestartBitcoinTest { +impl TestCase for RestartBitcoinTest { fn test_config() -> TestCaseConfig { TestCaseConfig { with_sequencer: false, diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..ac3b425 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,7 @@ +use citrea_e2e::config::{BatchProverConfig, LightClientProverConfig, SequencerConfig}; + +pub type TestFramework = citrea_e2e::framework::TestFramework< + SequencerConfig, + BatchProverConfig, + LightClientProverConfig, +>; diff --git a/tests/docker.rs b/tests/docker.rs index ebbdea8..23089aa 100644 --- a/tests/docker.rs +++ b/tests/docker.rs @@ -2,16 +2,24 @@ use async_trait::async_trait; use bitcoincore_rpc::RpcApi; use citrea_e2e::{ bitcoin::FINALITY_DEPTH, - config::{TestCaseConfig, TestCaseDockerConfig}, - framework::TestFramework, + config::{ + BatchProverConfig, LightClientProverConfig, SequencerConfig, TestCaseConfig, + TestCaseDockerConfig, + }, test_case::{TestCase, TestCaseRunner}, Result, }; +#[path = "common/mod.rs"] +mod common; +use common::*; + struct DockerIntegrationTest; #[async_trait] -impl TestCase for DockerIntegrationTest { +impl TestCase + for DockerIntegrationTest +{ fn test_config() -> TestCaseConfig { TestCaseConfig { with_batch_prover: true,