1pub mod raw;
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use std::process::{Command, Output, Stdio};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct Workspace {
9 pub packages: Vec<Package>,
10 pub target_dir: String,
11 pub root_dir: String,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Package {
16 pub name: String,
17 pub version: String,
18 pub root_dir: String,
19 pub dependencies: Vec<Dependency>,
20 pub binaries: Vec<Binary>,
21 pub tests: Vec<Test>,
22 pub examples: Vec<Example>,
23 pub has_lib: bool,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct Dependency {
28 pub name: String,
29 pub default_features: bool,
30 pub features: Vec<String>,
31 pub root_dir: String,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct Binary {
36 pub name: String,
37 pub src_path: String,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct Test {
42 pub name: String,
43 pub src_path: String,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47pub struct Example {
48 pub name: String,
49 pub src_path: String,
50}
51
52impl Workspace {
53 const FIREDBG_DIR: &'static str = "firedbg";
54 const FIREDBG_TARGET_DIR: &'static str = "target";
55
56 pub fn get_firedbg_dir(&self) -> String {
57 format!("{}/{}", self.root_dir, Self::FIREDBG_DIR)
58 }
59
60 pub fn get_firedbg_target_dir(&self) -> String {
61 format!(
62 "{}/{}/{}",
63 self.root_dir,
64 Self::FIREDBG_DIR,
65 Self::FIREDBG_TARGET_DIR,
66 )
67 }
68
69 pub fn get_version_path(&self) -> String {
70 format!("{}/{}/version.toml", self.root_dir, Self::FIREDBG_DIR)
71 }
72
73 pub fn find_binary(&self, binary_name: &str) -> Option<(&Package, &Binary)> {
74 let mut res = None;
75 'package_iter: for package in self.packages.iter() {
76 for binary in package.binaries.iter() {
77 if binary.name == binary_name {
78 res = Some((package, binary));
79 break 'package_iter;
80 }
81 }
82 }
83 res
84 }
85
86 pub fn find_test(&self, test_name: &str) -> Option<(&Package, &Test)> {
87 let mut res = None;
88 'package_iter: for package in self.packages.iter() {
89 for test in package.tests.iter() {
90 if test.name == test_name {
91 res = Some((package, test));
92 break 'package_iter;
93 }
94 }
95 }
96 res
97 }
98
99 pub fn find_example(&self, example_name: &str) -> Option<(&Package, &Example)> {
100 let mut res = None;
101 'package_iter: for package in self.packages.iter() {
102 for example in package.examples.iter() {
103 if example.name == example_name {
104 res = Some((package, example));
105 break 'package_iter;
106 }
107 }
108 }
109 res
110 }
111
112 pub fn find_package(&self, package_name: &str) -> Option<&Package> {
113 self.packages
114 .iter()
115 .find(|&package| package.name == package_name)
116 }
117
118 pub fn package_names(&self) -> Vec<&str> {
119 self.packages
120 .iter()
121 .map(|package| package.name.as_str())
122 .collect()
123 }
124
125 pub fn binary_names(&self) -> Vec<&str> {
126 self.packages
127 .iter()
128 .flat_map(|package| {
129 package
130 .binaries
131 .iter()
132 .map(|binary| binary.name.as_str())
133 .collect::<Vec<_>>()
134 })
135 .collect()
136 }
137
138 pub fn test_names(&self) -> Vec<&str> {
139 self.packages
140 .iter()
141 .flat_map(|package| {
142 package
143 .tests
144 .iter()
145 .map(|test| test.name.as_str())
146 .collect::<Vec<_>>()
147 })
148 .collect()
149 }
150
151 pub fn example_names(&self) -> Vec<&str> {
152 self.packages
153 .iter()
154 .flat_map(|package| {
155 package
156 .examples
157 .iter()
158 .map(|example| example.name.as_str())
159 .collect::<Vec<_>>()
160 })
161 .collect()
162 }
163}
164
165impl Package {
166 pub fn get_crate_name(&self) -> String {
167 self.name.replace('-', "_")
168 }
169
170 pub fn get_firedbg_dir(&self, workspace: &Workspace) -> String {
171 let package_rel_path = &self.root_dir[(workspace.root_dir.len() + 1)..];
172 format!("{}/{}", workspace.get_firedbg_dir(), package_rel_path)
173 }
174
175 pub fn get_unit_test_cmd(&self) -> Command {
176 let root_dir = &self.root_dir;
177 let package_name = &self.name;
178 let mut cmd = Command::new("cargo");
179 cmd.arg("test")
180 .arg("--manifest-path")
181 .arg(format!("{root_dir}/Cargo.toml"))
182 .arg("--lib")
183 .arg("--package")
184 .arg(package_name);
185 cmd
186 }
187
188 pub fn get_unit_test_names(&self) -> Result<Vec<String>> {
197 let output = self
198 .get_unit_test_cmd()
199 .arg("--color")
200 .arg("always")
201 .arg("--")
202 .arg("--list")
203 .output()?;
204 if !output.status.success() {
205 eprint!("{}", std::str::from_utf8(&output.stderr)?);
206 std::process::exit(1);
207 }
208 let stdout = String::from_utf8(output.stdout)?;
209 Ok(parse_test_testcases(stdout))
210 }
211
212 pub fn build_unit_test(&self) -> Result<Output> {
215 let output = self
216 .get_unit_test_cmd()
217 .arg("--no-run")
218 .stderr(Stdio::inherit())
219 .output()?;
220 Ok(output)
221 }
222
223 pub fn get_unit_test_path(&self, workspace: &Workspace) -> Result<String> {
229 let output = self.get_unit_test_cmd().arg("--no-run").output()?;
230 if !output.status.success() {
231 eprint!("{}", std::str::from_utf8(&output.stderr)?);
232 panic!("Fail to compile unit test");
233 }
234
235 let target_dir = &workspace.target_dir;
236 let test_binary_prefix = "debug/deps/";
237 let suffix = String::from_utf8(output.stderr)?
238 .split_once(test_binary_prefix)
239 .map(|(_, after)| after.chars().take_while(|c| ')'.ne(c)).collect::<String>())
240 .expect("Fail to parse unit test executable path");
241 Ok(format!("{target_dir}/{test_binary_prefix}{suffix}"))
243 }
244}
245
246impl Binary {
247 pub fn get_build_cmd(&self, package: &Package) -> Command {
250 let root_dir = &package.root_dir;
251 let binary_name = &self.name;
252 let mut cmd = Command::new("cargo");
253 cmd.arg("build")
254 .arg("--manifest-path")
255 .arg(format!("{root_dir}/Cargo.toml"))
256 .arg("--bin")
257 .arg(binary_name);
258 cmd
259 }
260
261 pub fn build(&self, package: &Package) -> Result<Output> {
262 let output = self
263 .get_build_cmd(package)
264 .stderr(Stdio::inherit())
265 .output()?;
266 Ok(output)
267 }
268
269 pub fn get_binary_path(&self, workspace: &Workspace) -> String {
271 let target_dir = &workspace.target_dir;
272 let binary_name = &self.name;
273 format!("{target_dir}/debug/{binary_name}")
274 }
275}
276
277impl Test {
278 pub fn build(&self, package: &Package) -> Result<Output> {
282 let output = self
283 .get_run_cmd(package)
284 .arg("--no-run")
285 .stderr(Stdio::inherit())
286 .output()?;
287 Ok(output)
288 }
289
290 pub fn get_test_path(&self, workspace: &Workspace, package: &Package) -> Result<String> {
296 let output = self.get_run_cmd(package).arg("--no-run").output()?;
297 if !output.status.success() {
298 eprint!("{}", std::str::from_utf8(&output.stderr)?);
299 panic!("Fail to compile integration test");
300 }
301
302 let target_dir = &workspace.target_dir;
303 let test_binary_prefix = "debug/deps/";
304 let suffix = String::from_utf8(output.stderr)?
305 .split_once(test_binary_prefix)
306 .map(|(_, after)| after.chars().take_while(|c| ')'.ne(c)).collect::<String>())
307 .expect("Fail to parse integration test executable path");
308 Ok(format!("{target_dir}/{test_binary_prefix}{suffix}"))
310 }
311
312 pub fn get_run_cmd(&self, package: &Package) -> Command {
313 let root_dir = &package.root_dir;
314 let test_name = &self.name;
315 let mut cmd = Command::new("cargo");
316 cmd.arg("test")
317 .arg("--manifest-path")
318 .arg(format!("{root_dir}/Cargo.toml"))
319 .arg("--test")
320 .arg(test_name);
321 cmd
322 }
323
324 pub fn get_testcases(&self, package: &Package) -> Result<Vec<String>> {
331 let output = self
332 .get_run_cmd(package)
333 .arg("--color")
334 .arg("always")
335 .arg("--")
336 .arg("--list")
337 .output()?;
338 if !output.status.success() {
339 eprint!("{}", std::str::from_utf8(&output.stderr)?);
340 std::process::exit(1);
341 }
342 let stdout = String::from_utf8(output.stdout)?;
343 Ok(parse_test_testcases(stdout))
344 }
345}
346
347impl Example {
348 pub fn get_build_cmd(&self, package: &Package) -> Command {
351 let root_dir = &package.root_dir;
352 let example_name = &self.name;
353 let mut cmd = Command::new("cargo");
354 cmd.arg("build")
355 .arg("--manifest-path")
356 .arg(format!("{root_dir}/Cargo.toml"))
357 .arg("--example")
358 .arg(example_name);
359 cmd
360 }
361
362 pub fn build(&self, package: &Package) -> Result<Output> {
363 let output = self
364 .get_build_cmd(package)
365 .stderr(Stdio::inherit())
366 .output()?;
367 Ok(output)
368 }
369
370 pub fn get_example_path(&self, workspace: &Workspace) -> String {
372 let target_dir = &workspace.target_dir;
373 let example_name = &self.name;
374 format!("{target_dir}/debug/examples/{example_name}")
375 }
376}
377
378fn parse_test_testcases(text: String) -> Vec<String> {
379 let suffix = ": test";
380 text.lines()
381 .filter(|line| line.ends_with(suffix))
382 .filter_map(|line| line.split_once(suffix).map(|(before, _)| before.to_owned()))
383 .collect()
384}
385
386#[cfg(test)]
387mod test {
388 use super::*;
389 use pretty_assertions::assert_eq;
390
391 #[test]
392 fn test_parse_test_testcases() {
393 assert_eq!(
394 parse_test_testcases(
395 r#"
396 Finished test [unoptimized + debuginfo] target(s) in 0.03s
397 Running tests/simple_tests.rs (target/debug/deps/simple_tests-69a7a6c625a5bad6)
398main: test
399main2: test
400
4012 tests, 0 benchmarks"#
402 .into()
403 ),
404 ["main", "main2"]
405 );
406
407 assert_eq!(
408 parse_test_testcases(
409 r#"
410 Finished test [unoptimized + debuginfo] target(s) in 0.03s
411 Running unittests src/lib.rs (target/debug/deps/quick_sort-c42cff5519f79ed2)
412conquer::test::test_partition: test
413conquer::test::test_partition2: test
414test::test_quicksort: test
415test::test_quicksort2: test
416
4174 tests, 0 benchmarks"#
418 .into()
419 ),
420 [
421 "conquer::test::test_partition",
422 "conquer::test::test_partition2",
423 "test::test_quicksort",
424 "test::test_quicksort2",
425 ]
426 );
427 }
428}