-
Notifications
You must be signed in to change notification settings - Fork 449
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
hivesim-rs: add hivesim-rs a rust implementation of hivesim
- Loading branch information
Showing
11 changed files
with
850 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "hivesim" | ||
version = "0.1.0-alpha.1" | ||
authors = ["Kolby ML (Moroz Liebl) <kolbydml@gmail.com>", "Ognyan Genev <ognian.genev@gmail.com>"] | ||
edition = "2021" | ||
|
||
|
||
[dependencies] | ||
async-trait = "0.1.59" | ||
dyn-clone = "1.0.11" | ||
jsonrpsee = {version="0.20.0", features = ["client"]} | ||
regex = "1.10.5" | ||
reqwest = { version = "0.11.12", default-features = false, features = ["json", "multipart"] } | ||
serde = { version = "1.0.147", features = ["derive"] } | ||
serde_json = "1.0.87" | ||
tokio = { version = "1", features = ["full"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#![allow(dead_code)] | ||
#![warn(clippy::unwrap_used)] | ||
mod macros; | ||
mod simulation; | ||
mod testapi; | ||
mod testmatch; | ||
pub mod types; | ||
pub mod utils; | ||
|
||
pub use simulation::Simulation; | ||
pub use testapi::{run_suite, Client, NClientTestSpec, Suite, Test, TestSpec}; | ||
pub use testmatch::TestMatcher; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#[macro_export] | ||
macro_rules! dyn_async {( | ||
$( #[$attr:meta] )* // includes doc strings | ||
$pub:vis | ||
async | ||
fn $fname:ident<$lt:lifetime> ( $($args:tt)* ) $(-> $Ret:ty)? | ||
{ | ||
$($body:tt)* | ||
} | ||
) => ( | ||
$( #[$attr] )* | ||
#[allow(unused_parens)] | ||
$pub | ||
fn $fname<$lt> ( $($args)* ) -> ::std::pin::Pin<::std::boxed::Box< | ||
dyn $lt + Send + ::std::future::Future<Output = ($($Ret)?)> | ||
>> | ||
{ | ||
Box::pin(async move { $($body)* }) | ||
} | ||
)} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
use crate::types::{ClientDefinition, StartNodeResponse, SuiteID, TestID, TestRequest, TestResult}; | ||
use crate::TestMatcher; | ||
use std::collections::HashMap; | ||
use std::env; | ||
use std::net::IpAddr; | ||
use std::str::FromStr; | ||
|
||
/// Wraps the simulation HTTP API provided by hive. | ||
#[derive(Clone, Debug)] | ||
pub struct Simulation { | ||
pub url: String, | ||
pub test_matcher: Option<TestMatcher>, | ||
} | ||
|
||
impl Default for Simulation { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
// A struct in the structure of the JSON config shown in simulators.md | ||
// it is used to pass information to the Hive Simulators | ||
#[derive(serde::Serialize, serde::Deserialize)] | ||
struct SimulatorConfig { | ||
client: String, | ||
environment: HashMap<String, String>, | ||
} | ||
|
||
impl SimulatorConfig { | ||
pub fn new() -> Self { | ||
Self { | ||
client: "".to_string(), | ||
environment: Default::default(), | ||
} | ||
} | ||
} | ||
|
||
impl Simulation { | ||
/// New looks up the hive host URI using the HIVE_SIMULATOR environment variable | ||
/// and connects to it. It will panic if HIVE_SIMULATOR is not set. | ||
pub fn new() -> Self { | ||
let url = env::var("HIVE_SIMULATOR").expect("HIVE_SIMULATOR environment variable not set"); | ||
let test_matcher = match env::var("HIVE_TEST_PATTERN") { | ||
Ok(pattern) => { | ||
if pattern.is_empty() { | ||
None | ||
} else { | ||
Some(TestMatcher::new(&pattern)) | ||
} | ||
} | ||
Err(_) => None, | ||
}; | ||
|
||
if url.is_empty() { | ||
panic!("HIVE_SIMULATOR environment variable is empty") | ||
} | ||
|
||
Self { url, test_matcher } | ||
} | ||
|
||
pub async fn start_suite( | ||
&self, | ||
name: String, | ||
description: String, | ||
_sim_log: String, | ||
) -> SuiteID { | ||
let url = format!("{}/testsuite", self.url); | ||
let client = reqwest::Client::new(); | ||
let body = TestRequest { name, description }; | ||
|
||
client | ||
.post(url) | ||
.json(&body) | ||
.send() | ||
.await | ||
.expect("Failed to send start suite request") | ||
.json::<SuiteID>() | ||
.await | ||
.expect("Failed to convert start suite response to json") | ||
} | ||
|
||
pub async fn end_suite(&self, test_suite: SuiteID) { | ||
let url = format!("{}/testsuite/{}", self.url, test_suite); | ||
let client = reqwest::Client::new(); | ||
client | ||
.delete(url) | ||
.send() | ||
.await | ||
.expect("Failed to send an end suite request"); | ||
} | ||
|
||
/// Starts a new test case, returning the testcase id as a context identifier | ||
pub async fn start_test( | ||
&self, | ||
test_suite: SuiteID, | ||
name: String, | ||
description: String, | ||
) -> TestID { | ||
let url = format!("{}/testsuite/{}/test", self.url, test_suite); | ||
let client = reqwest::Client::new(); | ||
let body = TestRequest { name, description }; | ||
|
||
client | ||
.post(url) | ||
.json(&body) | ||
.send() | ||
.await | ||
.expect("Failed to send start test request") | ||
.json::<TestID>() | ||
.await | ||
.expect("Failed to convert start test response to json") | ||
} | ||
|
||
/// Finishes the test case, cleaning up everything, logging results, and returning | ||
/// an error if the process could not be completed. | ||
pub async fn end_test(&self, test_suite: SuiteID, test: TestID, test_result: TestResult) { | ||
let url = format!("{}/testsuite/{}/test/{}", self.url, test_suite, test); | ||
let client = reqwest::Client::new(); | ||
|
||
client | ||
.post(url) | ||
.json(&test_result) | ||
.send() | ||
.await | ||
.expect("Failed to send end test request"); | ||
} | ||
|
||
/// Starts a new node (or other container). | ||
/// Returns container id and ip. | ||
pub async fn start_client( | ||
&self, | ||
test_suite: SuiteID, | ||
test: TestID, | ||
client_type: String, | ||
environment: Option<HashMap<String, String>>, | ||
) -> (String, IpAddr) { | ||
let url = format!("{}/testsuite/{}/test/{}/node", self.url, test_suite, test); | ||
let client = reqwest::Client::new(); | ||
|
||
let mut config = SimulatorConfig::new(); | ||
config.client = client_type; | ||
if let Some(environment) = environment { | ||
config.environment = environment; | ||
} | ||
|
||
let config = serde_json::to_string(&config).expect("Failed to parse config to serde_json"); | ||
let form = reqwest::multipart::Form::new().text("config", config); | ||
|
||
let resp = client | ||
.post(url) | ||
.multipart(form) | ||
.send() | ||
.await | ||
.expect("Failed to send start client request") | ||
.json::<StartNodeResponse>() | ||
.await | ||
.expect("Failed to convert start node response to json"); | ||
|
||
let ip = IpAddr::from_str(&resp.ip).expect("Failed to decode IpAddr from string"); | ||
|
||
(resp.id, ip) | ||
} | ||
|
||
/// Returns all client types available to this simulator run. This depends on | ||
/// both the available client set and the command line filters. | ||
pub async fn client_types(&self) -> Vec<ClientDefinition> { | ||
let url = format!("{}/clients", self.url); | ||
let client = reqwest::Client::new(); | ||
client | ||
.get(&url) | ||
.send() | ||
.await | ||
.expect("Failed to send get client types request") | ||
.json::<Vec<ClientDefinition>>() | ||
.await | ||
.expect("Failed to convert client types response to json") | ||
} | ||
} |
Oops, something went wrong.