Skip to content

Commit

Permalink
Merge pull request #34 from cspr-rad/add-kairos-test-utils
Browse files Browse the repository at this point in the history
Add kairos test utils
  • Loading branch information
marijanp authored Mar 26, 2024
2 parents 5290216 + 68f3673 commit 5fd9c57
Show file tree
Hide file tree
Showing 10 changed files with 1,620 additions and 72 deletions.
1,186 changes: 1,118 additions & 68 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"kairos-cli",
"kairos-server",
"kairos-tx",
"kairos-test-utils",
]

[workspace.package]
Expand Down
78 changes: 78 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
advisory-db.flake = false;
risc0pkgs.url = "github:cspr-rad/risc0pkgs";
risc0pkgs.inputs.nixpkgs.follows = "nixpkgs";
csprpkgs.url = "github:cspr-rad/csprpkgs/add-cctl";
csprpkgs.inputs.nixpkgs.follows = "nixpkgs";
};

outputs = inputs@{ self, flake-parts, treefmt-nix, ... }:
Expand Down Expand Up @@ -58,6 +60,11 @@
openssl.dev
] ++ lib.optionals stdenv.isDarwin [
libiconv
darwin.apple_sdk.frameworks.Security
darwin.apple_sdk.frameworks.SystemConfiguration
];
checkInputs = [
inputs'.csprpkgs.packages.cctl
];
meta.mainProgram = "kairos-server";
};
Expand Down Expand Up @@ -93,11 +100,22 @@

coverage-report = craneLib.cargoTarpaulin (kairosNodeAttrs // {
cargoArtifacts = self'.packages.kairos-deps;
# Default values from https://crane.dev/API.html?highlight=tarpau#cranelibcargotarpaulin
# --avoid-cfg-tarpaulin fixes nom/bitvec issue https://github.com/xd009642/tarpaulin/issues/756#issuecomment-838769320
cargoTarpaulinExtraArgs = "--skip-clean --out xml --output-dir $out --avoid-cfg-tarpaulin";
# For some reason cargoTarpaulin runs the tests in the buildPhase
buildInputs = kairosNodeAttrs.buildInputs ++ [
inputs'.csprpkgs.packages.cctl
];
});

audit = craneLib.cargoAudit {
inherit (kairosNodeAttrs) src;
advisory-db = inputs.advisory-db;
# Default values from https://crane.dev/API.html?highlight=cargoAudit#cranelibcargoaudit
# FIXME --ignore RUSTSEC-2022-0093 ignores ed25519-dalek 1.0.1 vulnerability caused by introducing casper-client 2.0.0
# FIXME --ignore RUSTSEC-2024-0013 ignores libgit2-sys 0.14.2+1.5.1 vulnerability caused by introducing casper-client 2.0.0
cargoAuditExtraArgs = "--ignore yanked --ignore RUSTSEC-2022-0093 --ignore RUSTSEC-2024-0013";
};
};

Expand Down
1 change: 0 additions & 1 deletion kairos-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ axum-extra = { version = "0.9", features = [
"typed-header",
"json-deserializer",
] }
thiserror = "1"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
19 changes: 19 additions & 0 deletions kairos-test-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "kairos-test-utils"
version.workspace = true
edition.workspace = true
license.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]

[dependencies]

anyhow = "1"
backoff = { version = "0.4", features = ["tokio", "futures"]}
casper-client = "2"
nom = "7"
tokio = { version = "1", features = [ "full", "tracing", "macros" ] }
tempfile = "3"
tracing = "0.1"

153 changes: 153 additions & 0 deletions kairos-test-utils/src/cctl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
pub mod parsers;
use anyhow::anyhow;
use backoff::{self, backoff::Constant, future::retry};
use casper_client::{get_node_status, rpcs::results::ReactorState, Error, JsonRpcId, Verbosity};
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::Command;
use tempfile::{tempdir, TempDir};

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum NodeState {
Running,
Stopped,
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct CasperNodePorts {
pub consensus_port: u16,
pub rpc_port: u16,
pub rest_port: u16,
pub sse_port: u16,
pub speculative_exec_port: u16,
}

pub struct CasperNode {
pub id: u8,
pub validator_group_id: u8,
pub state: NodeState,
pub port: CasperNodePorts,
}

pub struct CCTLNetwork {
pub working_dir: TempDir,
pub assets_dir: PathBuf,
pub nodes: Vec<CasperNode>,
}

impl CCTLNetwork {
pub async fn run() -> Result<CCTLNetwork, io::Error> {
let working_dir = tempdir()?;
let assets_dir = working_dir.path().join("assets");

let output = Command::new("cctl-infra-net-setup")
.env("CCTL_ASSETS", &assets_dir)
.output()
.expect("Failed to setup network configuration");
let output = std::str::from_utf8(output.stdout.as_slice()).unwrap();
tracing::info!("{}", output);

let output = Command::new("cctl-infra-net-start")
.env("CCTL_ASSETS", &assets_dir)
.output()
.expect("Failed to start network");
let output = std::str::from_utf8(output.stdout.as_slice()).unwrap();
tracing::info!("{}", output);
let (_, nodes) = parsers::parse_cctl_infra_net_start_lines(output).unwrap();

let output = Command::new("cctl-infra-node-view-ports")
.env("CCTL_ASSETS", &assets_dir)
.output()
.expect("Failed to get the networks node ports");
let output = std::str::from_utf8(output.stdout.as_slice()).unwrap();
tracing::info!("{}", output);
let (_, node_ports) = parsers::parse_cctl_infra_node_view_port_lines(output).unwrap();

// Match the started nodes with their respective ports
let nodes: Vec<CasperNode> = nodes
.into_iter()
.map(|(validator_group_id, node_id, state)| {
if let Some(&(_, port)) = node_ports
.iter()
.find(|(node_id_ports, _)| *node_id_ports == node_id)
{
CasperNode {
validator_group_id,
state,
id: node_id,
port,
}
} else {
panic!("Can't find ports for node with id {}", node_id)
}
})
.collect();

tracing::info!("Waiting for network to pass genesis");
retry(
Constant::new(std::time::Duration::from_millis(100)),
|| async {
let node_port = nodes.first().unwrap().port.rpc_port;
get_node_status(
JsonRpcId::Number(1),
&format!("http://localhost:{}", node_port),
Verbosity::High,
)
.await
.map_err(|err| match &err {
Error::ResponseIsHttpError { .. } | Error::FailedToGetResponse { .. } => {
backoff::Error::transient(anyhow!(err))
}
_ => backoff::Error::permanent(anyhow!(err)),
})
.map(|success| match success.result.reactor_state {
ReactorState::Validate => Ok(()),
_ => Err(backoff::Error::transient(anyhow!(
"Node didn't reach the VALIDATE state yet"
))),
})?
},
)
.await
.expect("Waiting for network to pass genesis failed");

Ok(CCTLNetwork {
working_dir,
assets_dir,
nodes,
})
}
}

impl Drop for CCTLNetwork {
fn drop(&mut self) {
let output = Command::new("cctl-infra-net-stop")
.env("CCTL_ASSETS", &self.assets_dir)
.output()
.expect("Failed to stop the network");
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
}
}

#[cfg(test)]
mod tests {
use super::*;
use casper_client::{get_node_status, rpcs::results::ReactorState, JsonRpcId, Verbosity};
#[tokio::test]
async fn test_cctl_network_starts_and_terminates() {
let network = CCTLNetwork::run().await.unwrap();
for node in &network.nodes {
if node.state == NodeState::Running {
let node_status = get_node_status(
JsonRpcId::Number(1),
&format!("http://localhost:{}", node.port.rpc_port),
Verbosity::High,
)
.await
.unwrap();
assert_eq!(node_status.result.reactor_state, ReactorState::Validate);
}
}
}
}
Loading

0 comments on commit 5fd9c57

Please sign in to comment.