trdelnik_sandbox_client/
test_generator.rs1use crate::{
2 commander::{Commander, Error as CommanderError},
3 config::{Config, CARGO_TOML, TRDELNIK_TOML},
4};
5use fehler::throws;
6use std::{
7 env, io,
8 path::{Path, PathBuf},
9};
10use thiserror::Error;
11use tokio::fs;
12use toml::{value::Table, Value};
13
14const TESTS_WORKSPACE: &str = "trdelnik-tests";
15const TESTS_DIRECTORY: &str = "tests";
16const TESTS_FILE_NAME: &str = "test.rs";
17
18#[derive(Error, Debug)]
19pub enum Error {
20 #[error("cannot parse Cargo.toml")]
21 CannotParseCargoToml,
22 #[error("{0:?}")]
23 Io(#[from] io::Error),
24 #[error("{0:?}")]
25 Toml(#[from] toml::de::Error),
26 #[error("{0:?}")]
27 Commander(#[from] CommanderError),
28}
29
30pub struct TestGenerator;
31impl Default for TestGenerator {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36impl TestGenerator {
37 pub fn new() -> Self {
38 Self
39 }
40
41 #[throws]
79 pub async fn generate(&self) {
80 let root = Config::discover_root().expect("failed to find the root folder");
81 let root_path = root.to_str().unwrap().to_string();
82 let commander = Commander::with_root(root_path);
83 commander.create_program_client_crate().await?;
84 self.generate_test_files(&root).await?;
85 self.update_workspace(&root).await?;
86 self.build_program_client(&commander).await?;
87 }
88
89 #[throws]
91 async fn build_program_client(&self, commander: &Commander) {
92 commander.build_programs().await?;
93 commander.generate_program_client_deps().await?;
94 commander.generate_program_client_lib_rs().await?;
95 }
96
97 #[throws]
101 async fn generate_test_files(&self, root: &Path) {
102 let workspace_path = root.join(TESTS_WORKSPACE);
103 self.create_directory(&workspace_path, TESTS_WORKSPACE)
104 .await?;
105
106 let tests_path = workspace_path.join(TESTS_DIRECTORY);
107 self.create_directory(&tests_path, TESTS_DIRECTORY).await?;
108 let test_path = tests_path.join(TESTS_FILE_NAME);
109 let test_content = include_str!(concat!(
110 env!("CARGO_MANIFEST_DIR"),
111 "/src/templates/trdelnik-tests/test.rs"
112 ));
113 self.create_file(&test_path, TESTS_FILE_NAME, test_content)
114 .await?;
115
116 let cargo_toml_path = workspace_path.join(CARGO_TOML);
117 let cargo_toml_content = include_str!(concat!(
118 env!("CARGO_MANIFEST_DIR"),
119 "/src/templates/trdelnik-tests/Cargo.toml.tmpl"
120 ));
121 self.create_file(&cargo_toml_path, CARGO_TOML, cargo_toml_content)
122 .await?;
123 self.add_program_dev_deps(root, &cargo_toml_path).await?;
124
125 let trdelnik_toml_path = root.join(TRDELNIK_TOML);
126 let trdelnik_toml_content = include_str!(concat!(
127 env!("CARGO_MANIFEST_DIR"),
128 "/src/templates/Trdelnik.toml.tmpl"
129 ));
130 self.create_file(&trdelnik_toml_path, TRDELNIK_TOML, trdelnik_toml_content)
131 .await?;
132 }
133
134 async fn create_file<'a>(
137 &self,
138 path: &'a PathBuf,
139 name: &str,
140 content: &str,
141 ) -> Result<&'a PathBuf, Error> {
142 match path.exists() {
143 true => println!("Skipping creating the {} file", name),
144 false => {
145 println!("Creating the {} file ...", name);
146 fs::write(path, content).await?;
147 }
148 };
149 Ok(path)
150 }
151
152 async fn create_directory<'a>(
155 &self,
156 path: &'a PathBuf,
157 name: &str,
158 ) -> Result<&'a PathBuf, Error> {
159 match path.exists() {
160 true => println!("Skipping creating the {} directory", name),
161 false => {
162 println!("Creating the {} directory ...", name);
163 fs::create_dir(path).await?;
164 }
165 };
166 Ok(path)
167 }
168
169 #[throws]
171 async fn update_workspace(&self, root: &PathBuf) {
172 let cargo = Path::new(&root).join(CARGO_TOML);
173 let mut content: Value = fs::read_to_string(&cargo).await?.parse()?;
174 let test_workspace_value = Value::String(String::from(TESTS_WORKSPACE));
175 let members = content
176 .as_table_mut()
177 .ok_or(Error::CannotParseCargoToml)?
178 .entry("workspace")
179 .or_insert(Value::Table(Table::default()))
180 .as_table_mut()
181 .ok_or(Error::CannotParseCargoToml)?
182 .entry("members")
183 .or_insert(Value::Array(vec![test_workspace_value.clone()]))
184 .as_array_mut()
185 .ok_or(Error::CannotParseCargoToml)?;
186 match members.iter().find(|&x| x.eq(&test_workspace_value)) {
187 Some(_) => println!("Skipping updating project workspace"),
188 None => {
189 members.push(test_workspace_value);
190 println!("Project workspace successfully updated");
191 }
192 };
193 fs::write(cargo, content.to_string()).await?;
194 }
195
196 #[throws]
198 async fn add_program_dev_deps(&self, root: &Path, cargo_toml_path: &Path) {
199 let programs = self.get_programs(root).await?;
200 if !programs.is_empty() {
201 println!("Adding programs to Cargo.toml ...");
202 let mut content: Value = fs::read_to_string(cargo_toml_path).await?.parse()?;
203 let dev_deps = content
204 .get_mut("dev-dependencies")
205 .and_then(Value::as_table_mut)
206 .ok_or(Error::CannotParseCargoToml)?;
207 for dep in programs {
208 if let Value::Table(table) = dep {
209 let (name, value) = table.into_iter().next().unwrap();
210 dev_deps.entry(name).or_insert(value);
211 }
212 }
213 fs::write(cargo_toml_path, content.to_string()).await?;
214 }
215 }
216
217 async fn get_programs(&self, root: &Path) -> Result<Vec<Value>, Error> {
219 let programs = root.join("programs");
220 if !programs.exists() {
221 println!("Programs folder does not exist. Skipping adding dev dependencies.");
222 return Ok(Vec::new());
223 }
224 println!("Searching for programs ...");
225 let mut program_names: Vec<Value> = vec![];
226 let programs = std::fs::read_dir(programs)?;
227 for program in programs {
228 let file = program?;
229 let file_name = file.file_name();
230 if file.path().is_dir() {
231 let path = file.path().join(CARGO_TOML);
232 if path.exists() {
233 let name = file_name.to_str().unwrap();
234 let dependency = self.get_program_dep(&path, name).await?;
235 program_names.push(dependency);
236 }
237 }
238 }
239 Ok(program_names)
240 }
241
242 #[throws]
244 async fn get_program_dep<'a>(&self, dir: &Path, dir_name: &'a str) -> Value {
245 let content: Value = fs::read_to_string(&dir).await?.parse()?;
246 let name = content
247 .get("package")
248 .and_then(Value::as_table)
249 .and_then(|table| table.get("name"))
250 .and_then(Value::as_str)
251 .ok_or(Error::CannotParseCargoToml)?;
252 format!("{} = {{ path = \"../programs/{}\" }}", name, dir_name)
253 .parse()
254 .unwrap()
255 }
256}