mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Use dockertest for the newly added serai-client-serai test
This commit is contained in:
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@@ -95,6 +95,7 @@ jobs:
|
|||||||
-p serai-in-instructions-pallet \
|
-p serai-in-instructions-pallet \
|
||||||
-p serai-runtime \
|
-p serai-runtime \
|
||||||
-p serai-node
|
-p serai-node
|
||||||
|
-p serai-substrate-tests
|
||||||
|
|
||||||
test-serai-client:
|
test-serai-client:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,7 +6,6 @@ Cargo.lock
|
|||||||
|
|
||||||
# Don't commit any `Dockerfile`, as they're auto-generated, except the only one which isn't
|
# Don't commit any `Dockerfile`, as they're auto-generated, except the only one which isn't
|
||||||
Dockerfile
|
Dockerfile
|
||||||
Dockerfile.fast-epoch
|
|
||||||
!orchestration/runtime/Dockerfile
|
!orchestration/runtime/Dockerfile
|
||||||
|
|
||||||
.test-logs
|
.test-logs
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ members = [
|
|||||||
"tests/message-queue",
|
"tests/message-queue",
|
||||||
# TODO "tests/processor",
|
# TODO "tests/processor",
|
||||||
# TODO "tests/coordinator",
|
# TODO "tests/coordinator",
|
||||||
|
"tests/substrate",
|
||||||
# TODO "tests/full-stack",
|
# TODO "tests/full-stack",
|
||||||
"tests/reproducible-runtime",
|
"tests/reproducible-runtime",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ exceptions = [
|
|||||||
{ allow = ["AGPL-3.0-only"], name = "serai-message-queue-tests" },
|
{ allow = ["AGPL-3.0-only"], name = "serai-message-queue-tests" },
|
||||||
{ allow = ["AGPL-3.0-only"], name = "serai-processor-tests" },
|
{ allow = ["AGPL-3.0-only"], name = "serai-processor-tests" },
|
||||||
{ allow = ["AGPL-3.0-only"], name = "serai-coordinator-tests" },
|
{ allow = ["AGPL-3.0-only"], name = "serai-coordinator-tests" },
|
||||||
|
{ allow = ["AGPL-3.0-only"], name = "serai-substrate-tests" },
|
||||||
{ allow = ["AGPL-3.0-only"], name = "serai-full-stack-tests" },
|
{ allow = ["AGPL-3.0-only"], name = "serai-full-stack-tests" },
|
||||||
{ allow = ["AGPL-3.0-only"], name = "serai-reproducible-runtime-tests" },
|
{ allow = ["AGPL-3.0-only"], name = "serai-reproducible-runtime-tests" },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ pub fn serai(
|
|||||||
) {
|
) {
|
||||||
// Always builds in release for performance reasons
|
// Always builds in release for performance reasons
|
||||||
let setup = mimalloc(Os::Debian).to_string() + &build_serai_service("", true, "", "serai-node");
|
let setup = mimalloc(Os::Debian).to_string() + &build_serai_service("", true, "", "serai-node");
|
||||||
let setup_fast_epoch =
|
|
||||||
mimalloc(Os::Debian).to_string() + &build_serai_service("", true, "fast-epoch", "serai-node");
|
|
||||||
|
|
||||||
let env_vars = [("KEY", hex::encode(serai_key.to_repr()))];
|
let env_vars = [("KEY", hex::encode(serai_key.to_repr()))];
|
||||||
let mut env_vars_str = String::new();
|
let mut env_vars_str = String::new();
|
||||||
@@ -39,16 +37,9 @@ CMD {env_vars_str} "/run.sh"
|
|||||||
|
|
||||||
let run = os(Os::Debian, "", "serai") + &run_serai;
|
let run = os(Os::Debian, "", "serai") + &run_serai;
|
||||||
let res = setup + &run;
|
let res = setup + &run;
|
||||||
let res_fast_epoch = setup_fast_epoch + &run;
|
|
||||||
|
|
||||||
let mut serai_path = orchestration_path.to_path_buf();
|
let mut serai_path = orchestration_path.to_path_buf();
|
||||||
serai_path.push("serai");
|
serai_path.push("serai");
|
||||||
|
|
||||||
let mut serai_fast_epoch_path = serai_path.clone();
|
|
||||||
|
|
||||||
serai_path.push("Dockerfile");
|
serai_path.push("Dockerfile");
|
||||||
serai_fast_epoch_path.push("Dockerfile.fast-epoch");
|
|
||||||
|
|
||||||
write_dockerfile(serai_path, &res);
|
write_dockerfile(serai_path, &res);
|
||||||
write_dockerfile(serai_fast_epoch_path, &res_fast_epoch);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,6 @@ async-lock = "3"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", default-features = false, features = ["rt", "macros"] }
|
tokio = { version = "1", default-features = false, features = ["rt", "macros"] }
|
||||||
|
dockertest = "0.5"
|
||||||
|
serai-docker-tests = { path = "../../../tests/docker" }
|
||||||
|
serai-substrate-tests = { path = "../../../tests/substrate" }
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
use serai_client_serai::*;
|
use serai_client_serai::*;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn main() {
|
async fn blockchain() {
|
||||||
let serai = Serai::new("http://127.0.0.1:9944".to_string()).unwrap();
|
let mut test = dockertest::DockerTest::new();
|
||||||
|
let (composition, handle) = serai_substrate_tests::composition(
|
||||||
|
"alice",
|
||||||
|
serai_docker_tests::fresh_logs_folder(true, "serai-client/blockchain"),
|
||||||
|
);
|
||||||
|
test.provide_container(composition);
|
||||||
|
test
|
||||||
|
.run_async(async |ops| {
|
||||||
|
let serai = serai_substrate_tests::rpc(&ops, handle).await;
|
||||||
let block = serai.block_by_number(0).await.unwrap();
|
let block = serai.block_by_number(0).await.unwrap();
|
||||||
assert_eq!(serai.block(block.header.hash()).await.unwrap(), block);
|
assert_eq!(serai.block(block.header.hash()).await.unwrap(), block);
|
||||||
assert!(serai.finalized(block.header.hash()).await.unwrap());
|
assert!(serai.finalized(block.header.hash()).await.unwrap());
|
||||||
|
})
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
dockertest = "0.5"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{Mutex, OnceLock},
|
sync::{Mutex, LazyLock},
|
||||||
collections::{HashSet, HashMap},
|
collections::{HashSet, HashMap},
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
@@ -7,6 +7,16 @@ use std::{
|
|||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dockertest::{LogSource, LogAction, LogPolicy, LogOptions};
|
||||||
|
|
||||||
|
pub fn handle(desc: &str) -> String {
|
||||||
|
static UNIQUE_ID: LazyLock<Mutex<u16>> = LazyLock::new(|| Mutex::new(0));
|
||||||
|
let mut unique_id_lock = UNIQUE_ID.lock().unwrap();
|
||||||
|
let unique_id = *unique_id_lock;
|
||||||
|
*unique_id_lock += 1;
|
||||||
|
format!("{desc}-{unique_id}")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fresh_logs_folder(first: bool, label: &str) -> String {
|
pub fn fresh_logs_folder(first: bool, label: &str) -> String {
|
||||||
let logs_path = [std::env::current_dir().unwrap().to_str().unwrap(), ".test-logs", label]
|
let logs_path = [std::env::current_dir().unwrap().to_str().unwrap(), ".test-logs", label]
|
||||||
.iter()
|
.iter()
|
||||||
@@ -22,30 +32,33 @@ pub fn fresh_logs_folder(first: bool, label: &str) -> String {
|
|||||||
logs_path.to_str().unwrap().to_string()
|
logs_path.to_str().unwrap().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Merge this with what's in serai-orchestrator/have serai-orchestrator perform building
|
pub fn log_options(path: String) -> LogOptions {
|
||||||
static BUILT: OnceLock<Mutex<HashMap<String, bool>>> = OnceLock::new();
|
LogOptions {
|
||||||
|
action: if std::env::var("GITHUB_CI") == Ok("true".to_string()) {
|
||||||
|
LogAction::Forward
|
||||||
|
} else {
|
||||||
|
LogAction::ForwardToFile { path }
|
||||||
|
},
|
||||||
|
policy: LogPolicy::Always,
|
||||||
|
source: LogSource::Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should `serai-orchestrator` handle building?
|
||||||
pub fn build(name: String) {
|
pub fn build(name: String) {
|
||||||
let built = BUILT.get_or_init(|| Mutex::new(HashMap::new()));
|
static BUILT: LazyLock<Mutex<HashMap<String, bool>>> =
|
||||||
|
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||||
// Only one call to build will acquire this lock
|
// Only one call to build will acquire this lock
|
||||||
let mut built_lock = built.lock().unwrap();
|
let mut built_lock = BUILT.lock().unwrap();
|
||||||
if built_lock.contains_key(&name) {
|
if built_lock.contains_key(&name) {
|
||||||
// If it was built, return
|
// If it was built, return
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else, hold the lock while we build
|
// Else, hold the lock while we build
|
||||||
let mut repo_path = env::current_exe().unwrap();
|
|
||||||
repo_path.pop();
|
|
||||||
assert!(repo_path.as_path().ends_with("deps"));
|
|
||||||
repo_path.pop();
|
|
||||||
assert!(repo_path.as_path().ends_with("debug"));
|
|
||||||
repo_path.pop();
|
|
||||||
assert!(repo_path.as_path().ends_with("target"));
|
|
||||||
repo_path.pop();
|
|
||||||
|
|
||||||
// Run the orchestrator to ensure the most recent files exist
|
// Run the orchestrator to ensure the most recent files exist
|
||||||
if !Command::new("cargo")
|
if !Command::new("cargo")
|
||||||
.current_dir(&repo_path)
|
|
||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("-p")
|
.arg("-p")
|
||||||
.arg("serai-orchestrator")
|
.arg("serai-orchestrator")
|
||||||
@@ -62,7 +75,6 @@ pub fn build(name: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !Command::new("cargo")
|
if !Command::new("cargo")
|
||||||
.current_dir(&repo_path)
|
|
||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("-p")
|
.arg("-p")
|
||||||
.arg("serai-orchestrator")
|
.arg("serai-orchestrator")
|
||||||
@@ -78,6 +90,18 @@ pub fn build(name: String) {
|
|||||||
panic!("failed to run the orchestrator");
|
panic!("failed to run the orchestrator");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut repo_path = PathBuf::from(
|
||||||
|
core::str::from_utf8(
|
||||||
|
&Command::new("cargo")
|
||||||
|
.args(["locate-project", "--workspace", "--message-format", "plain"])
|
||||||
|
.output()
|
||||||
|
.expect("couldn't locate workspace with `cargo`")
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
|
.expect("`cargo` outputted non-UTF-8 bytes to `stdout`"),
|
||||||
|
);
|
||||||
|
repo_path.pop(); // Pop the `Cargo.toml` term
|
||||||
|
|
||||||
let mut orchestration_path = repo_path.clone();
|
let mut orchestration_path = repo_path.clone();
|
||||||
orchestration_path.push("orchestration");
|
orchestration_path.push("orchestration");
|
||||||
if name != "runtime" {
|
if name != "runtime" {
|
||||||
@@ -91,8 +115,6 @@ pub fn build(name: String) {
|
|||||||
if name.contains("-processor") {
|
if name.contains("-processor") {
|
||||||
dockerfile_path =
|
dockerfile_path =
|
||||||
dockerfile_path.join("processor").join(name.split('-').next().unwrap()).join("Dockerfile");
|
dockerfile_path.join("processor").join(name.split('-').next().unwrap()).join("Dockerfile");
|
||||||
} else if name == "serai-fast-epoch" {
|
|
||||||
dockerfile_path = dockerfile_path.join("serai").join("Dockerfile.fast-epoch");
|
|
||||||
} else {
|
} else {
|
||||||
dockerfile_path = dockerfile_path.join(&name).join("Dockerfile");
|
dockerfile_path = dockerfile_path.join(&name).join("Dockerfile");
|
||||||
}
|
}
|
||||||
@@ -150,7 +172,7 @@ pub fn build(name: String) {
|
|||||||
meta(repo_path.join("message-queue")),
|
meta(repo_path.join("message-queue")),
|
||||||
meta(repo_path.join("coordinator")),
|
meta(repo_path.join("coordinator")),
|
||||||
],
|
],
|
||||||
"runtime" | "serai" | "serai-fast-epoch" => vec![
|
"runtime" | "serai" => vec![
|
||||||
meta(repo_path.join("common")),
|
meta(repo_path.join("common")),
|
||||||
meta(repo_path.join("crypto")),
|
meta(repo_path.join("crypto")),
|
||||||
meta(repo_path.join("substrate")),
|
meta(repo_path.join("substrate")),
|
||||||
|
|||||||
25
tests/substrate/Cargo.toml
Normal file
25
tests/substrate/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "serai-substrate-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Tests for Serai's node"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/tests/substrate"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = []
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serai-client-serai = { path = "../../substrate/client/serai" }
|
||||||
|
|
||||||
|
tokio = { version = "1", features = ["time"] }
|
||||||
|
|
||||||
|
dockertest = "0.5"
|
||||||
|
serai-docker-tests = { path = "../docker" }
|
||||||
15
tests/substrate/LICENSE
Normal file
15
tests/substrate/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2023-2025 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
42
tests/substrate/src/lib.rs
Normal file
42
tests/substrate/src/lib.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use serai_client_serai::Serai;
|
||||||
|
|
||||||
|
use dockertest::{StartPolicy, PullPolicy, Image, TestBodySpecification, DockerOperations};
|
||||||
|
|
||||||
|
pub struct Handle(String);
|
||||||
|
|
||||||
|
pub fn composition(name: &str, logs_path: String) -> (TestBodySpecification, Handle) {
|
||||||
|
let handle = serai_docker_tests::handle(&format!("serai-{name}"));
|
||||||
|
serai_docker_tests::build("serai".to_string());
|
||||||
|
(
|
||||||
|
TestBodySpecification::with_image(
|
||||||
|
Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never),
|
||||||
|
)
|
||||||
|
.replace_env(
|
||||||
|
[("SERAI_NAME".to_string(), name.to_lowercase()), ("KEY".to_string(), " ".to_string())]
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.set_start_policy(StartPolicy::Strict)
|
||||||
|
.set_publish_all_ports(true)
|
||||||
|
.set_handle(handle.clone())
|
||||||
|
.set_log_options(Some(serai_docker_tests::log_options(logs_path))),
|
||||||
|
Handle(handle),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rpc(ops: &DockerOperations, handle: Handle) -> Serai {
|
||||||
|
let serai_rpc = ops.handle(&handle.0).host_port(9944).unwrap();
|
||||||
|
let serai_rpc = format!("http://{}:{}", serai_rpc.0, serai_rpc.1);
|
||||||
|
|
||||||
|
// If the RPC server has yet to start, sleep for up to 60s until it does
|
||||||
|
let client = Serai::new(serai_rpc.clone()).unwrap();
|
||||||
|
for _ in 0 .. 180 {
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
if client.block_by_number(0).await.is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
panic!("serai RPC server wasn't available after 60s");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user