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_path = unit.target.src_path().path().unwrap();
88         let exe_display = if let TargetKind::Test = unit.target.kind() {
89             format!(
90                 "{} ({})",
91                 test_path
92                     .strip_prefix(unit.pkg.root())
93                     .unwrap_or(test_path)
94                     .display(),
95                 path.strip_prefix(cwd).unwrap_or(path).display()
96             )
97         } else {
98             format!(
99                 "unittests ({})",
100                 path.strip_prefix(cwd).unwrap_or(path).display()
101             )
102         };
103 
104         let mut cmd = compilation.target_process(path, unit.kind, &unit.pkg, *script_meta)?;
105         cmd.args(test_args);
106         if unit.target.harness() && config.shell().verbosity() == Verbosity::Quiet {
107             cmd.arg("--quiet");
108         }
109         config
110             .shell()
111             .concise(|shell| shell.status("Running", &exe_display))?;
112         config
113             .shell()
114             .verbose(|shell| shell.status("Running", &cmd))?;
115 
116         let result = cmd.exec();
117 
118         if let Err(e) = result {
119             let e = e.downcast::<ProcessError>()?;
120             errors.push((
121                 unit.target.kind().clone(),
122                 unit.target.name().to_string(),
123                 unit.pkg.name().to_string(),
124                 e,
125             ));
126             if !options.no_fail_fast {
127                 break;
128             }
129         }
130     }
131 
132     if errors.len() == 1 {
133         let (kind, name, pkg_name, e) = errors.pop().unwrap();
134         Ok((
135             Test::UnitTest {
136                 kind,
137                 name,
138                 pkg_name,
139             },
140             vec![e],
141         ))
142     } else {
143         Ok((
144             Test::Multiple,
145             errors.into_iter().map(|(_, _, _, e)| e).collect(),
146         ))
147     }
148 }
149 
run_doc_tests( ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str], compilation: &Compilation<'_>, ) -> CargoResult<(Test, Vec<ProcessError>)>150 fn run_doc_tests(
151     ws: &Workspace<'_>,
152     options: &TestOptions,
153     test_args: &[&str],
154     compilation: &Compilation<'_>,
155 ) -> CargoResult<(Test, Vec<ProcessError>)> {
156     let config = ws.config();
157     let mut errors = Vec::new();
158     let doctest_xcompile = config.cli_unstable().doctest_xcompile;
159     let doctest_in_workspace = config.cli_unstable().doctest_in_workspace;
160 
161     for doctest_info in &compilation.to_doc_test {
162         let Doctest {
163             args,
164             unstable_opts,
165             unit,
166             linker,
167             script_meta,
168         } = doctest_info;
169 
170         if !doctest_xcompile {
171             match unit.kind {
172                 CompileKind::Host => {}
173                 CompileKind::Target(target) => {
174                     if target.short_name() != compilation.host {
175                         // Skip doctests, -Zdoctest-xcompile not enabled.
176                         continue;
177                     }
178                 }
179             }
180         }
181 
182         config.shell().status("Doc-tests", unit.target.name())?;
183         let mut p = compilation.rustdoc_process(unit, *script_meta)?;
184         p.arg("--crate-name").arg(&unit.target.crate_name());
185         p.arg("--test");
186 
187         if doctest_in_workspace {
188             add_path_args(ws, unit, &mut p);
189             // FIXME(swatinem): remove the `unstable-options` once rustdoc stabilizes the `test-run-directory` option
190             p.arg("-Z").arg("unstable-options");
191             p.arg("--test-run-directory")
192                 .arg(unit.pkg.root().to_path_buf());
193         } else {
194             p.arg(unit.target.src_path().path().unwrap());
195         }
196 
197         if doctest_xcompile {
198             if let CompileKind::Target(target) = unit.kind {
199                 // use `rustc_target()` to properly handle JSON target paths
200                 p.arg("--target").arg(target.rustc_target());
201             }
202             p.arg("-Zunstable-options");
203             p.arg("--enable-per-target-ignores");
204             if let Some((runtool, runtool_args)) = compilation.target_runner(unit.kind) {
205                 p.arg("--runtool").arg(runtool);
206                 for arg in runtool_args {
207                     p.arg("--runtool-arg").arg(arg);
208                 }
209             }
210             if let Some(linker) = linker {
211                 let mut joined = OsString::from("linker=");
212                 joined.push(linker);
213                 p.arg("-C").arg(joined);
214             }
215         }
216 
217         for &rust_dep in &[
218             &compilation.deps_output[&unit.kind],
219             &compilation.deps_output[&CompileKind::Host],
220         ] {
221             let mut arg = OsString::from("dependency=");
222             arg.push(rust_dep);
223             p.arg("-L").arg(arg);
224         }
225 
226         for native_dep in compilation.native_dirs.iter() {
227             p.arg("-L").arg(native_dep);
228         }
229 
230         for arg in test_args {
231             p.arg("--test-args").arg(arg);
232         }
233 
234         if config.shell().verbosity() == Verbosity::Quiet {
235             p.arg("--test-args").arg("--quiet");
236         }
237 
238         p.args(args);
239 
240         if *unstable_opts {
241             p.arg("-Zunstable-options");
242         }
243 
244         config
245             .shell()
246             .verbose(|shell| shell.status("Running", p.to_string()))?;
247         if let Err(e) = p.exec() {
248             let e = e.downcast::<ProcessError>()?;
249             errors.push(e);
250             if !options.no_fail_fast {
251                 return Ok((Test::Doc, errors));
252             }
253         }
254     }
255     Ok((Test::Doc, errors))
256 }
257