From 278f384958d0e1c3ea66614d0524d49d871c9bcf Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Fri, 23 Feb 2024 20:22:52 +0000 Subject: [PATCH] ENG-578: Refactor tests. Remove testnet --- .../testing/materializer/src/docker/mod.rs | 49 +++++++++++- .../testing/materializer/src/docker/node.rs | 2 +- .../testing/materializer/src/testnet.rs | 22 +++--- .../testing/materializer/src/validation.rs | 12 +-- .../testing/materializer/tests/docker.rs | 79 ++++++++++++++----- 5 files changed, 122 insertions(+), 42 deletions(-) diff --git a/fendermint/testing/materializer/src/docker/mod.rs b/fendermint/testing/materializer/src/docker/mod.rs index 0b78c4938..c63a2acaa 100644 --- a/fendermint/testing/materializer/src/docker/mod.rs +++ b/fendermint/testing/materializer/src/docker/mod.rs @@ -3,7 +3,11 @@ use anyhow::Context; use async_trait::async_trait; -use bollard::Docker; +use bollard::{ + container::{ListContainersOptions, RemoveContainerOptions}, + secret::ContainerSummary, + Docker, +}; use ethers::{ core::rand::{rngs::StdRng, SeedableRng}, types::H160, @@ -18,7 +22,7 @@ use fvm_shared::{bigint::Zero, econ::TokenAmount, version::NetworkVersion}; use ipc_api::subnet_id::SubnetID; use serde::{Deserialize, Serialize}; use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, path::{Path, PathBuf}, }; use tendermint_rpc::Url; @@ -176,6 +180,47 @@ impl DockerMaterializer { Ok(m) } + /// Remove all traces of a testnet. + pub async fn remove(&mut self, testnet: &TestnetName) -> anyhow::Result<()> { + let mut filters = HashMap::new(); + filters.insert( + "testnet".to_string(), + vec![testnet.path().to_string_lossy().to_string()], + ); + + let containers: Vec = self + .docker + .list_containers(Some(ListContainersOptions { + all: true, + filters, + ..Default::default() + })) + .await + .context("failed to list docker containers")?; + + let ids = containers.into_iter().filter_map(|c| c.id); + + for id in ids { + self.docker + .remove_container( + &id, + Some(RemoveContainerOptions { + force: true, + ..Default::default() + }), + ) + .await?; + } + + self.docker + .remove_network(&testnet.path().to_string_lossy().to_string()) + .await?; + + std::fs::remove_dir_all(self.dir.join(testnet.path()))?; + + Ok(()) + } + fn docker_with_drop_handle(&self) -> DockerWithDropHandle { DockerWithDropHandle::from_runtime(self.docker.clone(), &self.drop_runtime) } diff --git a/fendermint/testing/materializer/src/docker/node.rs b/fendermint/testing/materializer/src/docker/node.rs index e7b4ddf3a..138acae61 100644 --- a/fendermint/testing/materializer/src/docker/node.rs +++ b/fendermint/testing/materializer/src/docker/node.rs @@ -690,7 +690,7 @@ mod tests { let runner = DockerRunner::new(&dh, &nn, &COMETBFT_IMAGE, 0, Vec::new()); // Based on my manual testing, this will initialise the config and then show the ID: - // `docker run cometbft/cometbft:v0.37.x show-node-id` + // `docker run --rm cometbft/cometbft:v0.37.x show-node-id` let logs = runner .run_cmd("show-node-id") .await diff --git a/fendermint/testing/materializer/src/testnet.rs b/fendermint/testing/materializer/src/testnet.rs index f9876e6c1..fe9fb620d 100644 --- a/fendermint/testing/materializer/src/testnet.rs +++ b/fendermint/testing/materializer/src/testnet.rs @@ -18,8 +18,7 @@ use crate::{ Materializer, NodeConfig, ParentConfig, SubmitConfig, SubnetConfig, TargetConfig, }, materials::Materials, - AccountId, NodeId, NodeName, RelayerName, ResourceHash, SubnetId, SubnetName, TestnetId, - TestnetName, + AccountId, NodeId, NodeName, RelayerName, ResourceHash, SubnetId, SubnetName, TestnetName, }; /// The `Testnet` parses a [Manifest] and is able to derive the steps @@ -53,15 +52,14 @@ where M: Materials, R: Materializer + Sync + Send, { - pub async fn new(m: &mut R, id: impl Into) -> anyhow::Result { - let name = TestnetName::new(id); + pub async fn new(m: &mut R, name: &TestnetName) -> anyhow::Result { let network = m - .create_network(&name) + .create_network(name) .await .context("failed to create the network")?; Ok(Self { - name, + name: name.clone(), network, externals: Default::default(), accounts: Default::default(), @@ -74,6 +72,10 @@ where }) } + pub fn name(&self) -> &TestnetName { + &self.name + } + pub fn root(&self) -> SubnetName { self.name.root() } @@ -82,12 +84,8 @@ where /// /// To validate a manifest, we can first create a testnet with a [Materializer] /// that only creates symbolic resources. - pub async fn setup( - m: &mut R, - id: impl Into, - manifest: &Manifest, - ) -> anyhow::Result { - let mut t = Self::new(m, id).await?; + pub async fn setup(m: &mut R, name: &TestnetName, manifest: &Manifest) -> anyhow::Result { + let mut t = Self::new(m, name).await?; let root_name = t.root(); // Create keys for accounts. diff --git a/fendermint/testing/materializer/src/validation.rs b/fendermint/testing/materializer/src/validation.rs index 377a75e05..852a2ece7 100644 --- a/fendermint/testing/materializer/src/validation.rs +++ b/fendermint/testing/materializer/src/validation.rs @@ -18,8 +18,7 @@ use crate::{ materializer::{Materializer, NodeConfig, SubmitConfig, SubnetConfig}, materials::Materials, testnet::Testnet, - AccountName, NodeName, RelayerName, ResourceHash, ResourceName, SubnetName, TestnetId, - TestnetName, + AccountName, NodeName, RelayerName, ResourceHash, ResourceName, SubnetName, TestnetName, }; const DEFAULT_FAUCET_FIL: u64 = 100; @@ -28,11 +27,11 @@ const DEFAULT_FAUCET_FIL: u64 = 100; /// * we are not over allocating the balances /// * relayers have balances on the parent to submit transactions /// * subnet creators have balances on the parent to submit transactions -pub async fn validate_manifest(id: &TestnetId, manifest: &Manifest) -> anyhow::Result<()> { +pub async fn validate_manifest(name: &TestnetName, manifest: &Manifest) -> anyhow::Result<()> { let m = ValidatingMaterializer::default(); // Wrap with logging so that we can debug the tests easier. let mut m = LoggingMaterializer::new(m, "validation".to_string()); - let _ = Testnet::setup(&mut m, id, manifest).await?; + let _ = Testnet::setup(&mut m, name, manifest).await?; // We could check here that all subnets have enough validators for a quorum. Ok(()) } @@ -363,7 +362,7 @@ fn parent_name(subnet: &SubnetName) -> anyhow::Result { #[cfg(test)] mod tests { - use crate::{manifest::Manifest, validation::validate_manifest, TestnetId}; + use crate::{manifest::Manifest, validation::validate_manifest, TestnetId, TestnetName}; // Unfortunately doesn't seem to work with quickcheck_async // /// Run the tests with `RUST_LOG=info` to see the logs, for example: @@ -378,6 +377,7 @@ mod tests { /// Check that the random manifests we generate would pass validation. #[quickcheck_async::tokio] async fn prop_validation(id: TestnetId, manifest: Manifest) -> anyhow::Result<()> { - validate_manifest(&id, &manifest).await + let name = TestnetName::new(id); + validate_manifest(&name, &manifest).await } } diff --git a/fendermint/testing/materializer/tests/docker.rs b/fendermint/testing/materializer/tests/docker.rs index f517cea0d..40834b4fd 100644 --- a/fendermint/testing/materializer/tests/docker.rs +++ b/fendermint/testing/materializer/tests/docker.rs @@ -1,34 +1,71 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use std::{env::current_dir, time::Duration}; +use std::{env::current_dir, path::PathBuf, pin::Pin, time::Duration}; -use fendermint_testing_materializer::{docker::DockerMaterializer, testnet::Testnet}; +use anyhow::Context; +use fendermint_testing_materializer::{ + docker::{DockerMaterializer, DockerMaterials}, + manifest::Manifest, + testnet::Testnet, + TestnetName, +}; +use futures::Future; -mod manifests { - use fendermint_testing_materializer::manifest::Manifest; - - pub const ROOT_ONLY: &str = include_str!("../manifests/root-only.yaml"); - - pub fn parse_yaml(yaml: &str) -> Manifest { - serde_yaml::from_str(yaml).expect("failed to parse manifest") - } +/// Want to keep the testnet artifacts in the `tests/testnets` directory. +fn materializer_dir() -> PathBuf { + let dir = current_dir().unwrap(); + debug_assert!( + dir.ends_with("materializer"), + "expected the current directory to be the crate" + ); + dir } -#[tokio::test] -async fn test_root_only() { - let manifest = manifests::parse_yaml(manifests::ROOT_ONLY); +/// Parse a manifest file in the `manifests` directory, clean up any corresponding +/// testnet resources, then materialize a testnet and run some tests. +async fn with_testnet(manifest_name: &str, f: F) -> anyhow::Result<()> +where + F: FnOnce( + Testnet, + Manifest, + ) -> Pin>>>, +{ + let root = materializer_dir(); + let manifest = root.join("manifests").join(format!("{manifest_name}.yaml")); + let manifest = std::fs::read_to_string(manifest).context("failed to read manifest")?; + let manifest = serde_yaml::from_str(&manifest).context("failed to parse manifest")?; + + let testnet_name = TestnetName::new(manifest_name); - // The current directory should be this crate. - let root_dir = current_dir().unwrap().join("tests"); - let mut materializer = DockerMaterializer::new(&root_dir, 0).unwrap(); + let mut materializer = DockerMaterializer::new(&root, 0).unwrap(); + materializer + .remove(&testnet_name) + .await + .context("failed to remove testnet")?; - let testnet = Testnet::setup(&mut materializer, "test-root-only", &manifest) + let testnet = Testnet::setup(&mut materializer, &testnet_name, &manifest) .await - .unwrap(); + .context("failed to set up testnet")?; - let node1 = testnet.root().node("node-1"); - let _dnode1 = testnet.node(&node1).unwrap(); + let res = f(testnet, manifest).await; - tokio::time::sleep(Duration::from_secs(60)).await; + // Allow some time for resources to be freed. + // TODO: Remove the runtime handle becuase as it goes out of scope it causes panics. + tokio::time::sleep(Duration::from_secs(1)).await; + + res +} + +#[tokio::test] +async fn test_root_only() { + with_testnet("root-only", |testnet, _manifest| { + Box::pin(async move { + let node1 = testnet.root().node("node-1"); + let _dnode1 = testnet.node(&node1)?; + Ok(()) + }) + }) + .await + .unwrap() }