Skip to content

Commit 1d7c2c6

Browse files
committed
feat(testing/cli): add cli command and logic to run/replay a scenario
re: #188
1 parent 002e5d0 commit 1d7c2c6

File tree

4 files changed

+63
-15
lines changed

4 files changed

+63
-15
lines changed

node/testing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ lazy_static = "1.4.0"
1717
derive_more = "0.99.17"
1818
serde = "1.0.147"
1919
serde_json = { version = "1.0.82", features = ["unbounded_depth", "arbitrary_precision"] }
20+
thiserror = "1.0.37"
2021
anyhow = "1.0.70"
2122
bincode = "1.3.3"
2223
rand = "0.8"

node/testing/src/cluster/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ impl Cluster {
545545
.nodes
546546
.get_mut(node_id.index())
547547
.ok_or_else(|| anyhow::anyhow!("node {node_id:?} not found"))?;
548-
let timeout = tokio::time::sleep(Duration::from_secs(5));
548+
let timeout = tokio::time::sleep(Duration::from_secs(60));
549549
tokio::select! {
550550
res = node.wait_for_event_and_dispatch(&event) => res,
551551
_ = timeout => {

node/testing/src/main.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use clap::Parser;
22

3-
use openmina_node_testing::cluster::ClusterConfig;
3+
use openmina_node_testing::cluster::{Cluster, ClusterConfig};
4+
use openmina_node_testing::scenario::Scenario;
45
use openmina_node_testing::scenarios::Scenarios;
56
use openmina_node_testing::{exit_with_error, server, setup};
67

@@ -18,6 +19,7 @@ pub enum Command {
1819
Server(CommandServer),
1920

2021
ScenariosGenerate(CommandScenariosGenerate),
22+
ScenariosRun(CommandScenariosRun),
2123
}
2224

2325
#[derive(Debug, clap::Args)]
@@ -34,6 +36,16 @@ pub struct CommandScenariosGenerate {
3436
pub use_debugger: bool,
3537
}
3638

39+
/// Run scenario located at `res/scenarios`.
40+
#[derive(Debug, clap::Args)]
41+
pub struct CommandScenariosRun {
42+
/// Name of the scenario.
43+
///
44+
/// Must match filename in `res/scenarios` (without an extension).
45+
#[arg(long, short)]
46+
pub name: String,
47+
}
48+
3749
impl Command {
3850
pub fn run(self) -> Result<(), crate::CommandError> {
3951
let rt = setup();
@@ -101,6 +113,27 @@ impl Command {
101113
.to_owned()
102114
.into())
103115
}
116+
Self::ScenariosRun(cmd) => {
117+
let config = ClusterConfig::new(None).map_err(|err| {
118+
anyhow::anyhow!("failed to create cluster configuration: {err}")
119+
})?;
120+
121+
let id = cmd.name.parse()?;
122+
let fut = async move {
123+
let mut cluster = Cluster::new(config);
124+
cluster.start(Scenario::load(&id).await?).await?;
125+
cluster.exec_to_end().await?;
126+
Ok(())
127+
};
128+
rt.block_on(async {
129+
tokio::select! {
130+
res = fut => res,
131+
_ = shutdown_rx => {
132+
anyhow::bail!("Received ctrl-c signal! shutting down...");
133+
}
134+
}
135+
})
136+
}
104137
}
105138
}
106139
}

node/testing/src/scenario/id.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
use std::str::FromStr;
2+
13
use serde::{Deserialize, Deserializer, Serialize};
24

35
#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
46
pub struct ScenarioId(String);
57

8+
#[derive(thiserror::Error, Debug)]
9+
pub enum ScenarioIdParseError {
10+
#[error("scenario id can't contain upper-case characters")]
11+
ContainsUpperCaseCharacters,
12+
#[error("scenario id must match pattern /[a-z0-9_-]*/")]
13+
AcceptedPatternMismatch,
14+
}
15+
616
#[cfg(feature = "scenario-generators")]
717
impl From<crate::scenarios::Scenarios> for ScenarioId {
818
fn from(value: crate::scenarios::Scenarios) -> Self {
@@ -16,26 +26,30 @@ impl std::fmt::Display for ScenarioId {
1626
}
1727
}
1828

19-
impl<'de> Deserialize<'de> for ScenarioId {
20-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
21-
where
22-
D: Deserializer<'de>,
23-
{
24-
let s: String = Deserialize::deserialize(deserializer)?;
29+
impl FromStr for ScenarioId {
30+
type Err = ScenarioIdParseError;
31+
32+
fn from_str(s: &str) -> Result<Self, Self::Err> {
2533
for c in s.chars() {
2634
if ('A'..'Z').contains(&c) {
27-
return Err(serde::de::Error::custom(
28-
"scenario id can't contain upper-case characters",
29-
));
35+
return Err(ScenarioIdParseError::ContainsUpperCaseCharacters);
3036
}
3137
if ('a'..'z').contains(&c) || ('0'..'9').contains(&c) || c == '-' || c == '_' {
3238
continue;
3339
}
34-
return Err(serde::de::Error::custom(
35-
"scenario id must match pattern /[a-z0-9_-]*/",
36-
));
40+
return Err(ScenarioIdParseError::AcceptedPatternMismatch);
3741
}
42+
Ok(Self(s.to_owned()))
43+
}
44+
}
45+
46+
impl<'de> Deserialize<'de> for ScenarioId {
47+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
48+
where
49+
D: Deserializer<'de>,
50+
{
51+
let s: &str = Deserialize::deserialize(deserializer)?;
3852

39-
Ok(ScenarioId(s))
53+
Self::from_str(s).map_err(|err| serde::de::Error::custom(err))
4054
}
4155
}

0 commit comments

Comments
 (0)