1 use crate::core::compiler::{Compilation, CompileKind, Doctest, UnitOutput};
2 use crate::core::shell::Verbosity;
3 use crate::core::{TargetKind, Workspace};
4 use crate::ops;
5 use crate::util::errors::CargoResult;
6 use crate::util::{add_path_args, CargoTestError, Config, Test};
7 use cargo_util::ProcessError;
8 use std::ffi::OsString;
9 
10 pub struct TestOptions {
11     pub compile_opts: ops::CompileOptions,
12     pub no_run: bool,
13     pub no_fail_fast: bool,
14 }
15 
run_tests( ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str], ) -> CargoResult<Option<CargoTestError>>16 pub fn run_tests(
17     ws: &Workspace<'_>,
18     options: &TestOptions,
19     test_args: &[&str],
20 ) -> CargoResult<Option<CargoTestError>> {
21     let compilation = compile_tests(ws, options)?;
22 
23     if options.no_run {
24         return Ok(None);
25     }
26     let (test, mut errors) = run_unit_tests(ws.config(), options, test_args, &compilation)?;
27 
28     // If we have an error and want to fail fast, then return.
29     if !errors.is_empty() && !options.no_fail_fast {
30         return Ok(Some(CargoTestError::new(test, errors)));
31     }
32 
33     let (doctest, docerrors) = run_doc_tests(ws, options, test_args, &compilation)?;
34     let test = if docerrors.is_empty() { test } else { doctest };
35     errors.extend(docerrors);
36     if errors.is_empty() {
37         Ok(None)
38     } else {
39         Ok(Some(CargoTestError::new(test, errors)))
40     }
41 }
42 
run_benches( ws: &Workspace<'_>, options: &TestOptions, args: &[&str], ) -> CargoResult<Option<CargoTestError>>43 pub fn run_benches(
44     ws: &Workspace<'_>,
45     options: &TestOptions,
46     args: &[&str],
47 ) -> CargoResult<Option<CargoTestError>> {
48     let compilation = compile_tests(ws, options)?;
49 
50     if options.no_run {
51         return Ok(None);
52     }
53 
54     let mut args = args.to_vec();
55     args.push("--bench");
56 
57     let (test, errors) = run_unit_tests(ws.config(), options, &args, &compilation)?;
58 
59     match errors.len() {
60         0 => Ok(None),
61         _ => Ok(Some(CargoTestError::new(test, errors))),
62     }
63 }
64 
compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<Compilation<'a>>65 fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<Compilation<'a>> {
66     let mut compilation = ops::compile(ws, &options.compile_opts)?;
67     compilation.tests.sort();
68     Ok(compilation)
69 }
70 
71 /// Runs the unit and integration tests of a package.
run_unit_tests( config: &Config, options: &TestOptions, test_args: &[&str], compilation: &Compilation<'_>, ) -> CargoResult<(Test, Vec<ProcessError>)>72 fn run_unit_tests(
73     config: &Config,
74     options: &TestOptions,
75     test_args: &[&str],
76     compilation: &Compilation<'_>,
77 ) -> CargoResult<(Test, Vec<ProcessError>)> {
78     let cwd = config.cwd();
79     let mut errors = Vec::new();
80 
81     for UnitOutput {
82         unit,
83         path,
84         script_meta,
85     } in compilation.tests.iter()
86     {
87         let test = unit.target.name().to_string();
88 
89         let test_path = unit.target.src_path().path().unwrap();
90         let exe_display = if let TargetKind::Test = unit.target.kind() {
91             format!(
92                 "{} ({})",
93                 test_path
94                     .strip_prefix(unit.pkg.root())
95                     .unwrap_or(test_path)
96                     .display(),
97                 path.strip_prefix(cwd).unwrap_or(path).display()
98             )
99         } else {
100             format!(
101                 "unittests ({})",
102                 path.strip_prefix(cwd).unwrap_or(path).display()
103             )
104         };
105 
106         let mut cmd = compilation.target_process(path, unit.kind, &unit.pkg, *script_meta)?;
107         cmd.args(test_args);
108         if unit.target.harness() && config.shell().verbosity() == Verbosity::Quiet {
109             cmd.arg("--quiet");
110         }
111         config
112             .shell()
113             .concise(|shell| shell.status("Running", &exe_display))?;
114         config
115             .shell()
116             .verbose(|shell| shell.status("Running", &cmd))?;
117 
118         let result = cmd.exec();
119 
120         match result {
121             Err(e) => {
122                 let e = e.downcast::<ProcessError>()?;
123                 errors.push((
124                     unit.target.kind().clone(),
125                     test.clone(),
126                     unit.pkg.name().to_string(),
127                     e,
128                 ));
129                 if !options.no_fail_fast {
130                     break;
131                 }
132             }
133             Ok(()) => {}
134         }
135     }
136 
137     if errors.len() == 1 {
138         let (kind, name, pkg_name, e) = errors.pop().unwrap();
139         Ok((
140             Test::UnitTest {
141                 kind,
142                 name,
143                 pkg_name,
144             },
145             vec![e],
146         ))
147     } else {
148         Ok((
149             Test::Multiple,
150             errors.into_iter().map(|(_, _, _, e)| e).collect(),
151         ))
152     }
153 }
154 
run_doc_tests( ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str], compilation: &Compilation<'_>, ) -> CargoResult<(Test, Vec<ProcessError>)>155 fn run_doc_tests(
156     ws: &Workspace<'_>,
157     options: &TestOptions,
158     test_args: &[&str],
159     compilation: &Compilation<'_>,
160 ) -> CargoResult<(Test, Vec<ProcessError>)> {
161     let config = ws.config();
162     let mut errors = Vec::new();
163     let doctest_xcompile = config.cli_unstable().doctest_xcompile;
164     let doctest_in_workspace = config.cli_unstable().doctest_in_workspace;
165 
166     for doctest_info in &compilation.to_doc_test {
167         let Doctest {
168             args,
169             unstable_opts,
170             unit,
171             linker,
172             script_meta,
173         } = doctest_info;
174 
175         if !doctest_xcompile {
176             match unit.kind {
177                 CompileKind::Host => {}
178                 CompileKind::Target(target) => {
179                     if target.short_name() != compilation.host {
180                         // Skip doctests, -Zdoctest-xcompile not enabled.
181                         continue;
182                     }
183                 }
184             }
185         }
186 
187         config.shell().status("Doc-tests", unit.target.name())?;
188         let mut p = compilation.rustdoc_process(unit, *script_meta)?;
189         p.arg("--crate-name").arg(&unit.target.crate_name());
190         p.arg("--test");
191 
192         if doctest_in_workspace {
193             add_path_args(ws, unit, &mut p);
194             // FIXME(swatinem): remove the `unstable-options` once rustdoc stabilizes the `test-run-directory` option
195             p.arg("-Z").arg("unstable-options");
196             p.arg("--test-run-directory")
197                 .arg(unit.pkg.root().to_path_buf());
198         } else {
199             p.arg(unit.target.src_path().path().unwrap());
200         }
201 
202         if doctest_xcompile {
203             if let CompileKind::Target(target) = unit.kind {
204                 // use `rustc_target()` to properly handle JSON target paths
205                 p.arg("--target").arg(target.rustc_target());
206             }
207             p.arg("-Zunstable-options");
208             p.arg("--enable-per-target-ignores");
209             if let Some((runtool, runtool_args)) = compilation.target_runner(unit.kind) {
210                 p.arg("--runtool").arg(runtool);
211                 for arg in runtool_args {
212                     p.arg("--runtool-arg").arg(arg);
213                 }
214             }
215             if let Some(linker) = linker {
216                 let mut joined = OsString::from("linker=");
217                 joined.push(linker);
218                 p.arg("-C").arg(joined);
219             }
220         }
221 
222         for &rust_dep in &[
223             &compilation.deps_output[&unit.kind],
224             &compilation.deps_output[&CompileKind::Host],
225         ] {
226             let mut arg = OsString::from("dependency=");
227             arg.push(rust_dep);
228             p.arg("-L").arg(arg);
229         }
230 
231         for native_dep in compilation.native_dirs.iter() {
232             p.arg("-L").arg(native_dep);
233         }
234 
235         for arg in test_args {
236             p.arg("--test-args").arg(arg);
237         }
238 
239         p.args(args);
240 
241         if *unstable_opts {
242             p.arg("-Zunstable-options");
243         }
244 
245         config
246             .shell()
247             .verbose(|shell| shell.status("Running", p.to_string()))?;
248         if let Err(e) = p.exec() {
249             let e = e.downcast::<ProcessError>()?;
250             errors.push(e);
251             if !options.no_fail_fast {
252                 return Ok((Test::Doc, errors));
253             }
254         }
255     }
256     Ok((Test::Doc, errors))
257 }
258