Skip to content

Commit

Permalink
ENG-578: Refactor tests. Remove testnet
Browse files Browse the repository at this point in the history
  • Loading branch information
aakoshh committed Feb 23, 2024
1 parent 9178e12 commit 278f384
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 42 deletions.
49 changes: 47 additions & 2 deletions fendermint/testing/materializer/src/docker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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<ContainerSummary> = 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)
}
Expand Down
2 changes: 1 addition & 1 deletion fendermint/testing/materializer/src/docker/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 10 additions & 12 deletions fendermint/testing/materializer/src/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,15 +52,14 @@ where
M: Materials,
R: Materializer<M> + Sync + Send,
{
pub async fn new(m: &mut R, id: impl Into<TestnetId>) -> anyhow::Result<Self> {
let name = TestnetName::new(id);
pub async fn new(m: &mut R, name: &TestnetName) -> anyhow::Result<Self> {
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(),
Expand All @@ -74,6 +72,10 @@ where
})
}

pub fn name(&self) -> &TestnetName {
&self.name
}

pub fn root(&self) -> SubnetName {
self.name.root()
}
Expand All @@ -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<TestnetId>,
manifest: &Manifest,
) -> anyhow::Result<Self> {
let mut t = Self::new(m, id).await?;
pub async fn setup(m: &mut R, name: &TestnetName, manifest: &Manifest) -> anyhow::Result<Self> {
let mut t = Self::new(m, name).await?;
let root_name = t.root();

// Create keys for accounts.
Expand Down
12 changes: 6 additions & 6 deletions fendermint/testing/materializer/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(())
}
Expand Down Expand Up @@ -363,7 +362,7 @@ fn parent_name(subnet: &SubnetName) -> anyhow::Result<SubnetName> {
#[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:
Expand All @@ -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
}
}
79 changes: 58 additions & 21 deletions fendermint/testing/materializer/tests/docker.rs
Original file line number Diff line number Diff line change
@@ -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<F>(manifest_name: &str, f: F) -> anyhow::Result<()>
where
F: FnOnce(
Testnet<DockerMaterials, DockerMaterializer>,
Manifest,
) -> Pin<Box<dyn Future<Output = anyhow::Result<()>>>>,
{
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()
}

0 comments on commit 278f384

Please sign in to comment.