cargo_test_all/
command.rs1use 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}