Skip to main content

cargo_test_all/
command.rs

1use std::env::{current_dir, set_current_dir};
2use std::fs::create_dir_all;
3use std::path::Path;
4use std::sync::mpsc::channel;
5
6use cargo::core::{Dependency, GitReference};
7use rm_rf::remove as remove_dir_all;
8use workerpool::thunk::{Thunk, ThunkWorker};
9use workerpool::Pool;
10
11use crate::error::{ErrorKind, Result};
12use crate::util::{get_project_location, load_cargo_toml};
13use crate::worker::run_crate_tests;
14use failure::ResultExt;
15
16#[derive(Debug, Clone)]
17pub enum DependencyTypeEnum {
18    CratesIo(String),
19    Git(SourceOptions),
20    Local,
21}
22
23#[derive(Debug, Clone, Default)]
24pub struct SourceOptions {
25    branch: Option<String>,
26    tag: Option<String>,
27    commit: Option<String>,
28}
29
30impl SourceOptions {
31    pub fn get_branch(&self) -> Option<String> {
32        self.branch.clone()
33    }
34
35    pub fn get_tag(&self) -> Option<String> {
36        self.tag.clone()
37    }
38
39    pub fn get_commit(&self) -> Option<String> {
40        self.commit.clone()
41    }
42}
43
44#[derive(Debug, Clone)]
45pub struct Crate {
46    name: String,
47    path: String,
48    dependency_type: DependencyTypeEnum,
49}
50
51impl Crate {
52    pub fn get_name(&self) -> String {
53        self.name.clone()
54    }
55
56    pub fn get_path(&self) -> String {
57        self.path.clone()
58    }
59
60    pub fn get_dependency_type(&self) -> DependencyTypeEnum {
61        self.dependency_type.clone()
62    }
63}
64
65impl From<Dependency> for Crate {
66    fn from(dependency: Dependency) -> Self {
67        let name = dependency.package_name().to_string();
68        let source_id = dependency.source_id();
69        let mut path = source_id.url().to_string();
70
71        let is_git = source_id.is_git();
72        let is_registry = source_id.is_registry();
73        let is_local = source_id.is_path();
74        let dependency_type = match (is_registry, is_git, is_local) {
75            (true, _, _) => {
76                let version = extract_version_from_dependency(&dependency);
77                DependencyTypeEnum::CratesIo(version)
78            }
79            (_, true, _) => {
80                let mut branch = None;
81                let mut tag = None;
82                let mut commit = None;
83                match source_id.git_reference() {
84                    Some(GitReference::Branch(value)) => branch = Some(value.to_owned()),
85                    Some(GitReference::Tag(value)) => tag = Some(value.to_owned()),
86                    Some(GitReference::Rev(value)) => commit = Some(value.to_owned()),
87                    _ => (),
88                };
89                DependencyTypeEnum::Git(SourceOptions {
90                    branch,
91                    tag,
92                    commit,
93                })
94            }
95            (_, _, true) => {
96                path = path.trim_start_matches("file://").to_string();
97                DependencyTypeEnum::Local
98            }
99            (_, _, _) => unreachable!(),
100        };
101
102        Crate {
103            name,
104            path,
105            dependency_type,
106        }
107    }
108}
109
110fn extract_version_from_dependency(dependency: &Dependency) -> String {
111    let version = dependency
112        .version_req()
113        .to_string()
114        .replace("^", "")
115        .replace("~", "")
116        .replace("<", "")
117        .replace(">", "")
118        .replace("=", "");
119
120    match version.matches(".").count() {
121        0 => format!("{}.0.0", version),
122        1 => format!("{}.0", version),
123        _ => version,
124    }
125}
126
127#[derive(Debug, Clone)]
128pub struct CrateList {
129    all: Box<Vec<Crate>>,
130    failed: Box<Vec<ErrorKind>>,
131}
132
133impl CrateList {
134    pub fn load(path: &Path) -> Result<Self> {
135        let cargo_toml_path = path.join("Cargo.toml");
136        let cargo_toml = load_cargo_toml(&cargo_toml_path)?;
137
138        let used_crates = cargo_toml
139            .dependencies()
140            .into_iter()
141            .map(|dependency| Crate::from(dependency.to_owned()))
142            .collect::<Vec<Crate>>();
143
144        Ok(CrateList {
145            all: Box::new(used_crates),
146            failed: Box::new(Vec::new()),
147        })
148    }
149
150    pub fn with_filter_crates(mut self, test_only: &Vec<String>) -> Self {
151        match test_only.is_empty() {
152            true => (),
153            false => {
154                self.all = Box::new(
155                    self.all
156                        .into_iter()
157                        .filter(|obj| test_only.contains(&obj.name))
158                        .collect(),
159                );
160            }
161        };
162
163        self
164    }
165
166    pub fn get_tested_crates_list(&self) -> &Box<Vec<Crate>> {
167        &self.all
168    }
169
170    pub fn get_failed_crates(&self) -> &Box<Vec<ErrorKind>> {
171        &self.failed
172    }
173
174    pub fn append_error(&mut self, error: &ErrorKind) {
175        self.failed.push(error.clone());
176    }
177
178    pub fn has_failed_tests(&self) -> bool {
179        !self.failed.is_empty()
180    }
181}
182
183#[derive(Debug, Clone)]
184pub struct TestOptions {
185    pub threads: usize,
186    pub test_only: Vec<String>,
187}
188
189pub fn test_crates(options: &TestOptions) -> Result<()> {
190    let project_location = get_project_location()?;
191    let mut crate_list =
192        CrateList::load(project_location.as_path())?.with_filter_crates(&options.test_only);
193
194    let parent_directory = current_dir()?;
195    let temp_directory = parent_directory.join("target/testing/deps");
196    create_dir_all(temp_directory.clone())?;
197    set_current_dir(temp_directory.clone())?;
198
199    let tested_crates = crate_list.get_tested_crates_list();
200    let total_crates = tested_crates.len();
201    let pool = Pool::<ThunkWorker<Result<Crate>>>::new(options.threads);
202    let (tx, rx) = channel();
203    for used_crate in tested_crates.clone().into_iter() {
204        pool.execute_to(tx.clone(), Thunk::of(move || run_crate_tests(used_crate)));
205    }
206
207    rx.iter()
208        .take(tested_crates.len())
209        .filter(|response| response.is_err())
210        .for_each(|response| {
211            let error = response.unwrap_err();
212            let error_kind = error.kind();
213            crate_list.append_error(error_kind);
214        });
215
216    match crate_list.has_failed_tests() {
217        true => {
218            let failed_crates = crate_list.get_failed_crates();
219            println!("Failed {} of {} crates.", failed_crates.len(), total_crates);
220            for error in failed_crates.iter() {
221                let message = format!("{}", error);
222                println!("{}", message);
223            }
224        }
225        false => println!("Well done! All crates work correctly."),
226    }
227
228    set_current_dir(parent_directory)?;
229    let temp_parent_directory = temp_directory.parent().unwrap();
230    remove_dir_all(temp_parent_directory).with_context(|err| ErrorKind::Io {
231        reason: format!("{}", err),
232    })?;
233    Ok(())
234}