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