1use crate::{
8 executer,
9 randomizer::Randomizer,
10 step::{self, StepTrait},
11 Error, Result,
12};
13use colored::Colorize;
14use std::time::Instant;
15
16pub struct Runner {
18 steps: Vec<Box<dyn StepTrait>>,
19 init: Option<Box<dyn StepTrait>>,
20 randomizer: Randomizer,
21}
22
23#[must_use]
25pub fn new(steps: Vec<Box<dyn StepTrait>>) -> Runner {
26 Runner {
27 steps,
28 init: None,
29 randomizer: Randomizer::default(),
30 }
31}
32
33impl Runner {
34 #[must_use]
36 pub fn init_step(mut self, step: Box<dyn StepTrait>) -> Self {
37 self.init = Some(step);
38 self
39 }
40
41 #[must_use]
43 pub fn randomizer(mut self, randomizer: Randomizer) -> Self {
44 self.randomizer = randomizer;
45 self
46 }
47
48 pub fn dump_plan(&self) -> Result<String> {
54 let mut output: Vec<String> = Vec::new();
55
56 output.push("====================================".to_string());
57 output.push(" Execution Plan Dump ".green().to_string());
58 output.push("====================================".to_string());
59 output.push(format!("{}: {}", "Step Count".bold(), &self.steps.len()));
60 output.push(format!("{}: {}", "Seed".bold(), &self.randomizer.seed));
61 output.push("------------------------------------".to_string());
62
63 for (i, step) in self.steps.iter().enumerate() {
64 let execution_plan = step.plan(&self.randomizer)?;
65 output.push(
66 format!("Step {}: {}", i + 1, execution_plan.id)
67 .green()
68 .to_string(),
69 );
70 output.push("------------------------------------".to_string());
71 output.push("Command:".bold().to_string());
72 output.push(execution_plan.command.clone());
73 output.push("State:".bold().to_string());
74 output.push("---".to_string());
75
76 let state = serde_yaml::to_string(&step.to_yaml()).unwrap_or_default();
77 output.push(state);
78
79 output.push("------------------------------------".to_string());
80 }
81
82 Ok(output.join("\n"))
83 }
84
85 pub fn run(&self) -> Result<()> {
90 println!("{}", self.dump_plan()?);
91 for step in &self.steps {
92 let step_plan = step.plan(&self.randomizer)?;
93
94 println!();
95 println!("{}", format!("Run step: {}", step_plan.id).yellow());
96 println!();
97
98 step.setup()?;
99 let start = Instant::now();
100 println!("{}", "Execute plan...".yellow());
101 let result = step.plan(&self.randomizer)?.execute()?;
102 println!(
103 "{}",
104 format!("Execute plan finished in {:?}", start.elapsed()).yellow()
105 );
106 let is_success =
107 step.is_success(&result, &step_plan.ctx)
108 .map_err(|err| Error::StepError {
109 kind: step::Kind::Plan,
110 description: err.to_string(),
111 command_output: result,
112 })?;
113
114 if !is_success {
115 continue;
116 }
117
118 if let Some(check_command) = step.run_check() {
119 let start = Instant::now();
120 println!("{}", "Execute check...".yellow());
121 let output = executer::run_sh(&check_command)?;
122 println!(
123 "{}",
124 format!("Execute check finished in {:?}", start.elapsed()).yellow()
125 );
126 if output.status_code != Some(0) {
127 return Err(Error::StepError {
128 kind: step::Kind::Check,
129 description: "check not finish with status code 0".to_string(),
130 command_output: output,
131 });
132 }
133 }
134
135 if let Some(test_command) = step.run_test() {
136 let start = Instant::now();
137 println!("{}", "Execute test...".yellow());
138 let output = executer::run_sh(&test_command)?;
139 println!(
140 "{}",
141 format!("Execute tests finished in {:?}", start.elapsed()).yellow()
142 );
143 if output.status_code != Some(0) {
144 return Err(Error::StepError {
145 kind: step::Kind::Test,
146 description: "test command not finish with status code 0".to_string(),
147 command_output: output,
148 });
149 }
150 }
151 }
152
153 println!("{}", "Execution plan is pass successfully".green());
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160
161 use std::{collections::HashMap, path::PathBuf};
162
163 use serde::{Deserialize, Serialize};
164 use step::PlanCtx;
165
166 use super::*;
167 use crate::{executer::Output, generator::StringDef, step::Plan};
168
169 #[derive(Serialize, Deserialize)]
170 struct TestStepOne {
171 location: PathBuf,
172 }
173
174 #[derive(Serialize, Deserialize)]
175 struct TestStepTwo {
176 location: PathBuf,
177 }
178
179 impl StepTrait for TestStepOne {
180 fn setup(&self) -> crate::errors::Result<()> {
181 Ok(std::fs::create_dir_all(&self.location)?)
182 }
183
184 fn plan(&self, randomizer: &Randomizer) -> Result<Plan> {
185 let eco_string = randomizer.string(StringDef::default()).to_string();
186 Ok(Plan::with_vars::<Self>(
187 format!(
188 "echo {eco_string} >> {}",
189 self.location.join("test.txt").display()
190 ),
191 HashMap::from([("foo".to_string(), "bar".to_string())]),
192 ))
193 }
194
195 fn is_success(
196 &self,
197 execution_result: &Output,
198 plan_ctx: &PlanCtx,
199 ) -> Result<bool, &'static str> {
200 if let Some(foo_var) = plan_ctx.vars.get("foo") {
201 if foo_var != "bar" {
202 return Err("foo value should be equal to var");
203 }
204 } else {
205 return Err("foo plan ctx var not found");
206 };
207
208 if execution_result.status_code == Some(0) {
209 Ok(true)
210 } else {
211 Err("status code should be 0")
212 }
213 }
214
215 fn run_check(&self) -> Option<String> {
216 Some(format!(
217 "test -f {}",
218 self.location.join("test.txt").display()
219 ))
220 }
221
222 fn run_test(&self) -> Option<String> {
223 Some(format!(
224 "test -f {}",
225 self.location.join("test.txt").display()
226 ))
227 }
228
229 fn to_yaml(&self) -> serde_yaml::Value {
230 serde_yaml::to_value(self).expect("serialize")
231 }
232 }
233
234 impl StepTrait for TestStepTwo {
235 fn setup(&self) -> crate::errors::Result<()> {
236 Ok(std::fs::create_dir_all(&self.location)?)
237 }
238
239 fn plan(&self, randomizer: &Randomizer) -> Result<Plan> {
240 let eco_string = randomizer.string(StringDef::default()).to_string();
241 Ok(Plan::with_vars::<Self>(
242 format!(
243 "cat {eco_string} >> {}",
244 self.location.join("test.txt").display()
245 ),
246 HashMap::from([("foo".to_string(), "bar".to_string())]),
247 ))
248 }
249
250 fn is_success(
251 &self,
252 execution_result: &Output,
253 _plan_ctx: &PlanCtx,
254 ) -> Result<bool, &'static str> {
255 if execution_result.status_code == Some(1) {
256 Ok(true)
257 } else {
258 Err("status code should be 1")
259 }
260 }
261
262 fn to_yaml(&self) -> serde_yaml::Value {
263 serde_yaml::to_value(self).expect("serialize")
264 }
265 }
266
267 #[test]
268 fn can_run() {
269 let base_location = std::env::temp_dir().join("crazy-train");
270 let location_step_1 = base_location.join("step-1");
271 let location_step_2 = base_location.join("step-2");
272
273 let step_one = TestStepOne {
274 location: location_step_1,
275 };
276 let step_two = TestStepTwo {
277 location: location_step_2,
278 };
279 let randomaizer = Randomizer::with_seed(42);
280 let runner = new(vec![Box::new(step_one), Box::new(step_two)]).randomizer(randomaizer);
281
282 assert!(runner.run().is_ok());
283 }
284}