Full stack test framework

This commit is contained in:
Luke Parker
2023-08-27 18:37:12 -04:00
parent a3649b2062
commit 1838c37ecf
9 changed files with 271 additions and 0 deletions

131
tests/full-stack/src/lib.rs Normal file
View File

@@ -0,0 +1,131 @@
use std::{
sync::{OnceLock, Mutex},
fs,
};
use serai_client::primitives::NetworkId;
use dockertest::{
LogAction, LogPolicy, LogSource, LogOptions, StartPolicy, Composition, DockerOperations,
};
#[cfg(test)]
mod tests;
static UNIQUE_ID: OnceLock<Mutex<u16>> = OnceLock::new();
use serai_processor_tests::{network_instance, processor_instance};
use serai_message_queue_tests::instance as message_queue_instance;
use serai_coordinator_tests::{coordinator_instance, serai_composition};
#[allow(unused)]
pub struct Handles {
bitcoin: (String, u32),
bitcoin_processor: String,
monero: (String, u32),
monero_processor: String,
message_queue: String,
coordinator: String,
serai: String,
}
pub fn full_stack(name: &str) -> (Handles, Vec<Composition>) {
let (coord_key, message_queue_keys, message_queue_composition) = message_queue_instance();
let (bitcoin_composition, bitcoin_port) = network_instance(NetworkId::Bitcoin);
let bitcoin_processor_composition =
processor_instance(NetworkId::Bitcoin, bitcoin_port, message_queue_keys[&NetworkId::Bitcoin]);
let (monero_composition, monero_port) = network_instance(NetworkId::Monero);
let monero_processor_composition =
processor_instance(NetworkId::Monero, monero_port, message_queue_keys[&NetworkId::Monero]);
let coordinator_composition = coordinator_instance(name, coord_key);
let serai_composition = serai_composition(name);
// Give every item in this stack a unique ID
// Uses a Mutex as we can't generate a 8-byte random ID without hitting hostname length limits
let (first, unique_id) = {
let unique_id_mutex = UNIQUE_ID.get_or_init(|| Mutex::new(0));
let mut unique_id_lock = unique_id_mutex.lock().unwrap();
let first = *unique_id_lock == 0;
let unique_id = hex::encode(unique_id_lock.to_be_bytes());
*unique_id_lock += 1;
(first, unique_id)
};
let logs_path = [std::env::current_dir().unwrap().to_str().unwrap(), ".test-logs", "full-stack"]
.iter()
.collect::<std::path::PathBuf>();
if first {
let _ = fs::remove_dir_all(&logs_path);
fs::create_dir_all(&logs_path).expect("couldn't create logs directory");
assert!(
fs::read_dir(&logs_path).expect("couldn't read the logs folder").next().is_none(),
"logs folder wasn't empty, despite removing it at the start of the run",
);
}
let logs_path = logs_path.to_str().unwrap().to_string();
let mut compositions = vec![];
let mut handles = vec![];
for composition in [
message_queue_composition,
bitcoin_composition,
bitcoin_processor_composition,
monero_composition,
monero_processor_composition,
coordinator_composition,
serai_composition,
] {
let name = format!("{}-{}", composition.handle(), &unique_id);
compositions.push(
composition
.with_start_policy(StartPolicy::Strict)
.with_container_name(name.clone())
.with_log_options(Some(LogOptions {
action: if std::env::var("GITHUB_CI") == Ok("true".to_string()) {
LogAction::Forward
} else {
LogAction::ForwardToFile { path: logs_path.clone() }
},
policy: LogPolicy::Always,
source: LogSource::Both,
})),
);
handles.push(compositions.last().unwrap().handle());
}
let handles = Handles {
message_queue: handles.remove(0),
bitcoin: (handles.remove(0), bitcoin_port),
bitcoin_processor: handles.remove(0),
monero: (handles.remove(0), monero_port),
monero_processor: handles.remove(0),
coordinator: handles.remove(0),
serai: handles.remove(0),
};
{
let bitcoin_processor_composition = compositions.get_mut(2).unwrap();
bitcoin_processor_composition
.inject_container_name(handles.message_queue.clone(), "MESSAGE_QUEUE_RPC");
bitcoin_processor_composition
.inject_container_name(handles.bitcoin.0.clone(), "NETWORK_RPC_HOSTNAME");
}
{
let monero_processor_composition = compositions.get_mut(4).unwrap();
monero_processor_composition
.inject_container_name(handles.message_queue.clone(), "MESSAGE_QUEUE_RPC");
monero_processor_composition
.inject_container_name(handles.monero.0.clone(), "NETWORK_RPC_HOSTNAME");
}
let coordinator_composition = compositions.get_mut(5).unwrap();
coordinator_composition.inject_container_name(handles.message_queue.clone(), "MESSAGE_QUEUE_RPC");
coordinator_composition.inject_container_name(handles.serai.clone(), "SERAI_HOSTNAME");
(handles, compositions)
}

View File

@@ -0,0 +1,15 @@
use std::{sync::Mutex, time::Duration};
use crate::tests::*;
#[tokio::test]
async fn mint_and_burn_test() {
let _one_at_a_time = ONE_AT_A_TIME.get_or_init(|| Mutex::new(())).lock();
let (handles, test) = new_test();
test
.run_async(|_ops| async move {
tokio::time::sleep(Duration::from_secs(300)).await;
})
.await;
}

View File

@@ -0,0 +1,33 @@
use std::sync::OnceLock;
use dockertest::DockerTest;
use crate::*;
mod mint_and_burn;
pub(crate) const VALIDATORS: usize = 4;
// pub(crate) const THRESHOLD: usize = ((VALIDATORS * 2) / 3) + 1;
pub(crate) static ONE_AT_A_TIME: OnceLock<Mutex<()>> = OnceLock::new();
pub(crate) fn new_test() -> (Vec<Handles>, DockerTest) {
let mut validators = vec![];
let mut test = DockerTest::new();
for i in 0 .. VALIDATORS {
let (handles, compositions) = full_stack(match i {
0 => "Alice",
1 => "Bob",
2 => "Charlie",
3 => "Dave",
4 => "Eve",
5 => "Ferdie",
_ => panic!("needed a 7th name for a serai node"),
});
validators.push(handles);
for composition in compositions {
test.add_composition(composition);
}
}
(validators, test)
}