firedbg_rust_parser/def/
workspace.rs

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    //     Finished test [unoptimized + debuginfo] target(s) in 0.01s
189    //      Running unittests src/lib.rs (target/debug/deps/quick_sort-c42cff5519f79ed2)
190    // conquer::test::test_partition: test
191    // conquer::test::test_partition2: test
192    // test::test_quicksort: test
193    // test::test_quicksort2: test
194    //
195    // 4 tests, 0 benchmarks
196    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    //     Finished test [unoptimized + debuginfo] target(s) in 0.02s
213    //   Executable unittests src/lib.rs (target/debug/deps/quick_sort-c42cff5519f79ed2)
214    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    /// Get path of unit test executable.
224    ///
225    /// # Panics
226    ///
227    /// Panic if it fail to parse unit test executable
228    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        // /Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/target/debug/deps/quick_sort-c42cff5519f79ed2
242        Ok(format!("{target_dir}/{test_binary_prefix}{suffix}"))
243    }
244}
245
246impl Binary {
247    // $ cargo build --manifest-path /Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/Cargo.toml --bin main-one
248    //     Finished dev [unoptimized + debuginfo] target(s) in 0.03s
249    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    // /Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/target/debug/main-one
270    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    // $ cargo test --manifest-path /Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/Cargo.toml --test simple_tests --no-run
279    //     Finished test [unoptimized + debuginfo] target(s) in 0.03s
280    //   Executable tests/simple_tests.rs (tests/example-workspace/target/debug/deps/simple_tests-eb63f31f8208c8a4)
281    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    /// Get path of integration test executable.
291    ///
292    /// # Panics
293    ///
294    /// Panic if it fail to parse integration test executable
295    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        // /Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/target/debug/deps/simple_tests-eb63f31f8208c8a4
309        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    //     Finished test [unoptimized + debuginfo] target(s) in 0.03s
325    //      Running tests/simple_tests.rs (/Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/target/debug/deps/simple_tests-69a7a6c625a5bad6)
326    // main: test
327    // main2: test
328    //
329    // 2 tests, 0 benchmarks
330    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    // $ cargo build --manifest-path /Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/Cargo.toml --example demo
349    //     Finished dev [unoptimized + debuginfo] target(s) in 0.03s
350    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    // /Applications/MAMP/htdocs/FireDBG.for.Rust.Internal/parser/tests/example-workspace/target/debug/examples/demo
371    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}