1use crate::core::compiler::{Compilation, CompileKind, Doctest, Unit, UnitHash, UnitOutput};
2use crate::core::profiles::PanicStrategy;
3use crate::core::shell::ColorChoice;
4use crate::core::shell::Verbosity;
5use crate::core::{TargetKind, Workspace};
6use crate::ops;
7use crate::util::errors::CargoResult;
8use crate::util::{add_path_args, CliError, CliResult, GlobalContext};
9use anyhow::format_err;
10use cargo_util::{ProcessBuilder, ProcessError};
11use std::ffi::OsString;
12use std::fmt::Write;
13use std::path::{Path, PathBuf};
14
15pub struct TestOptions {
16 pub compile_opts: ops::CompileOptions,
17 pub no_run: bool,
18 pub no_fail_fast: bool,
19}
20
21#[derive(Copy, Clone)]
26enum TestKind {
27 Test,
28 Bench,
29 Doctest,
30}
31
32struct UnitTestError {
34 unit: Unit,
35 kind: TestKind,
36}
37
38impl UnitTestError {
39 fn cli_args(&self, ws: &Workspace<'_>, opts: &ops::CompileOptions) -> String {
41 let mut args = if opts.spec.needs_spec_flag(ws) {
42 format!("-p {} ", self.unit.pkg.name())
43 } else {
44 String::new()
45 };
46 let mut add = |which| write!(args, "--{which} {}", self.unit.target.name()).unwrap();
47
48 match self.kind {
49 TestKind::Test | TestKind::Bench => match self.unit.target.kind() {
50 TargetKind::Lib(_) => args.push_str("--lib"),
51 TargetKind::Bin => add("bin"),
52 TargetKind::Test => add("test"),
53 TargetKind::Bench => add("bench"),
54 TargetKind::ExampleLib(_) | TargetKind::ExampleBin => add("example"),
55 TargetKind::CustomBuild => panic!("unexpected CustomBuild kind"),
56 },
57 TestKind::Doctest => args.push_str("--doc"),
58 }
59 args
60 }
61}
62
63pub fn run_tests(ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str]) -> CliResult {
68 let compilation = compile_tests(ws, options)?;
69
70 if options.no_run {
71 if !options.compile_opts.build_config.emit_json() {
72 display_no_run_information(ws, test_args, &compilation, "unittests")?;
73 }
74 return Ok(());
75 }
76 let mut errors = run_unit_tests(ws, options, test_args, &compilation, TestKind::Test)?;
77
78 let doctest_errors = run_doc_tests(ws, options, test_args, &compilation)?;
79 errors.extend(doctest_errors);
80 no_fail_fast_err(ws, &options.compile_opts, &errors)
81}
82
83pub fn run_benches(ws: &Workspace<'_>, options: &TestOptions, args: &[&str]) -> CliResult {
88 let compilation = compile_tests(ws, options)?;
89
90 if options.no_run {
91 if !options.compile_opts.build_config.emit_json() {
92 display_no_run_information(ws, args, &compilation, "benches")?;
93 }
94 return Ok(());
95 }
96
97 let mut args = args.to_vec();
98 args.push("--bench");
99
100 let errors = run_unit_tests(ws, options, &args, &compilation, TestKind::Bench)?;
101 no_fail_fast_err(ws, &options.compile_opts, &errors)
102}
103
104fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<Compilation<'a>> {
105 let mut compilation = ops::compile(ws, &options.compile_opts)?;
106 compilation.tests.sort();
107 Ok(compilation)
108}
109
110fn run_unit_tests(
115 ws: &Workspace<'_>,
116 options: &TestOptions,
117 test_args: &[&str],
118 compilation: &Compilation<'_>,
119 test_kind: TestKind,
120) -> Result<Vec<UnitTestError>, CliError> {
121 let gctx = ws.gctx();
122 let cwd = gctx.cwd();
123 let mut errors = Vec::new();
124
125 for UnitOutput {
126 unit,
127 path,
128 script_meta,
129 } in compilation.tests.iter()
130 {
131 let (exe_display, mut cmd) = cmd_builds(
132 gctx,
133 cwd,
134 unit,
135 path,
136 script_meta,
137 test_args,
138 compilation,
139 "unittests",
140 )?;
141
142 if gctx.extra_verbose() {
143 cmd.display_env_vars();
144 }
145
146 gctx.shell()
147 .concise(|shell| shell.status("Running", &exe_display))?;
148 gctx.shell()
149 .verbose(|shell| shell.status("Running", &cmd))?;
150
151 if let Err(e) = cmd.exec() {
152 let code = fail_fast_code(&e);
153 let unit_err = UnitTestError {
154 unit: unit.clone(),
155 kind: test_kind,
156 };
157 report_test_error(ws, test_args, &options.compile_opts, &unit_err, e);
158 errors.push(unit_err);
159 if !options.no_fail_fast {
160 return Err(CliError::code(code));
161 }
162 }
163 }
164 Ok(errors)
165}
166
167fn run_doc_tests(
172 ws: &Workspace<'_>,
173 options: &TestOptions,
174 test_args: &[&str],
175 compilation: &Compilation<'_>,
176) -> Result<Vec<UnitTestError>, CliError> {
177 let gctx = ws.gctx();
178 let mut errors = Vec::new();
179 let doctest_xcompile = gctx.cli_unstable().doctest_xcompile;
180 let color = gctx.shell().color_choice();
181
182 for doctest_info in &compilation.to_doc_test {
183 let Doctest {
184 args,
185 unstable_opts,
186 unit,
187 linker,
188 script_meta,
189 env,
190 } = doctest_info;
191
192 if !doctest_xcompile {
193 match unit.kind {
194 CompileKind::Host => {}
195 CompileKind::Target(target) => {
196 if target.short_name() != compilation.host {
197 gctx.shell().verbose(|shell| {
199 shell.note(format!(
200 "skipping doctests for {} ({}), \
201 cross-compilation doctests are not yet supported\n\
202 See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#doctest-xcompile \
203 for more information.",
204 unit.pkg,
205 unit.target.description_named()
206 ))
207 })?;
208 continue;
209 }
210 }
211 }
212 }
213
214 gctx.shell().status("Doc-tests", unit.target.name())?;
215 let mut p = compilation.rustdoc_process(unit, *script_meta)?;
216
217 for (var, value) in env {
218 p.env(var, value);
219 }
220
221 let color_arg = match color {
222 ColorChoice::Always => "always",
223 ColorChoice::Never => "never",
224 ColorChoice::CargoAuto => "auto",
225 };
226 p.arg("--color").arg(color_arg);
227
228 p.arg("--crate-name").arg(&unit.target.crate_name());
229 p.arg("--test");
230
231 add_path_args(ws, unit, &mut p);
232 p.arg("--test-run-directory")
233 .arg(unit.pkg.root().to_path_buf());
234
235 if let CompileKind::Target(target) = unit.kind {
236 p.arg("--target").arg(target.rustc_target());
238 }
239
240 if doctest_xcompile {
241 p.arg("-Zunstable-options");
242 p.arg("--enable-per-target-ignores");
243 if let Some((runtool, runtool_args)) = compilation.target_runner(unit.kind) {
244 p.arg("--runtool").arg(runtool);
245 for arg in runtool_args {
246 p.arg("--runtool-arg").arg(arg);
247 }
248 }
249 if let Some(linker) = linker {
250 let mut joined = OsString::from("linker=");
251 joined.push(linker);
252 p.arg("-C").arg(joined);
253 }
254 }
255
256 if unit.profile.panic != PanicStrategy::Unwind {
257 p.arg("-C").arg(format!("panic={}", unit.profile.panic));
258 }
259
260 for &rust_dep in &[
261 &compilation.deps_output[&unit.kind],
262 &compilation.deps_output[&CompileKind::Host],
263 ] {
264 let mut arg = OsString::from("dependency=");
265 arg.push(rust_dep);
266 p.arg("-L").arg(arg);
267 }
268
269 for native_dep in compilation.native_dirs.iter() {
270 p.arg("-L").arg(native_dep);
271 }
272
273 for arg in test_args {
274 p.arg("--test-args").arg(arg);
275 }
276
277 if gctx.shell().verbosity() == Verbosity::Quiet {
278 p.arg("--test-args").arg("--quiet");
279 }
280
281 p.args(unit.pkg.manifest().lint_rustflags());
282
283 p.args(args);
284
285 if *unstable_opts {
286 p.arg("-Zunstable-options");
287 }
288
289 if gctx.extra_verbose() {
290 p.display_env_vars();
291 }
292
293 gctx.shell()
294 .verbose(|shell| shell.status("Running", p.to_string()))?;
295
296 if let Err(e) = p.exec() {
297 let code = fail_fast_code(&e);
298 let unit_err = UnitTestError {
299 unit: unit.clone(),
300 kind: TestKind::Doctest,
301 };
302 report_test_error(ws, test_args, &options.compile_opts, &unit_err, e);
303 errors.push(unit_err);
304 if !options.no_fail_fast {
305 return Err(CliError::code(code));
306 }
307 }
308 }
309 Ok(errors)
310}
311
312fn display_no_run_information(
316 ws: &Workspace<'_>,
317 test_args: &[&str],
318 compilation: &Compilation<'_>,
319 exec_type: &str,
320) -> CargoResult<()> {
321 let gctx = ws.gctx();
322 let cwd = gctx.cwd();
323 for UnitOutput {
324 unit,
325 path,
326 script_meta,
327 } in compilation.tests.iter()
328 {
329 let (exe_display, cmd) = cmd_builds(
330 gctx,
331 cwd,
332 unit,
333 path,
334 script_meta,
335 test_args,
336 compilation,
337 exec_type,
338 )?;
339 gctx.shell()
340 .concise(|shell| shell.status("Executable", &exe_display))?;
341 gctx.shell()
342 .verbose(|shell| shell.status("Executable", &cmd))?;
343 }
344
345 return Ok(());
346}
347
348fn cmd_builds(
354 gctx: &GlobalContext,
355 cwd: &Path,
356 unit: &Unit,
357 path: &PathBuf,
358 script_meta: &Option<UnitHash>,
359 test_args: &[&str],
360 compilation: &Compilation<'_>,
361 exec_type: &str,
362) -> CargoResult<(String, ProcessBuilder)> {
363 let test_path = unit.target.src_path().path().unwrap();
364 let short_test_path = test_path
365 .strip_prefix(unit.pkg.root())
366 .unwrap_or(test_path)
367 .display();
368
369 let exe_display = match unit.target.kind() {
370 TargetKind::Test | TargetKind::Bench => format!(
371 "{} ({})",
372 short_test_path,
373 path.strip_prefix(cwd).unwrap_or(path).display()
374 ),
375 _ => format!(
376 "{} {} ({})",
377 exec_type,
378 short_test_path,
379 path.strip_prefix(cwd).unwrap_or(path).display()
380 ),
381 };
382
383 let mut cmd = compilation.target_process(path, unit.kind, &unit.pkg, *script_meta)?;
384 cmd.args(test_args);
385 if unit.target.harness() && gctx.shell().verbosity() == Verbosity::Quiet {
386 cmd.arg("--quiet");
387 }
388
389 Ok((exe_display, cmd))
390}
391
392fn fail_fast_code(error: &anyhow::Error) -> i32 {
401 if let Some(proc_err) = error.downcast_ref::<ProcessError>() {
402 if let Some(code) = proc_err.code {
403 return code;
404 }
405 }
406 101
407}
408
409fn no_fail_fast_err(
412 ws: &Workspace<'_>,
413 opts: &ops::CompileOptions,
414 errors: &[UnitTestError],
415) -> CliResult {
416 let args: Vec<_> = errors
418 .iter()
419 .map(|unit_err| format!(" `{}`", unit_err.cli_args(ws, opts)))
420 .collect();
421 let message = match errors.len() {
422 0 => return Ok(()),
423 1 => format!("1 target failed:\n{}", args.join("\n")),
424 n => format!("{n} targets failed:\n{}", args.join("\n")),
425 };
426 Err(anyhow::Error::msg(message).into())
427}
428
429fn report_test_error(
431 ws: &Workspace<'_>,
432 test_args: &[&str],
433 opts: &ops::CompileOptions,
434 unit_err: &UnitTestError,
435 test_error: anyhow::Error,
436) {
437 let which = match unit_err.kind {
438 TestKind::Test => "test failed",
439 TestKind::Bench => "bench failed",
440 TestKind::Doctest => "doctest failed",
441 };
442
443 let mut err = format_err!("{}, to rerun pass `{}`", which, unit_err.cli_args(ws, opts));
444 let (is_simple, executed) = test_error
447 .downcast_ref::<ProcessError>()
448 .and_then(|proc_err| proc_err.code)
449 .map_or((false, false), |code| (code == 101, true));
450
451 if !is_simple {
452 err = test_error.context(err);
453 }
454
455 crate::display_error(&err, &mut ws.gctx().shell());
456
457 let harness: bool = unit_err.unit.target.harness();
458 let nocapture: bool = test_args.contains(&"--nocapture");
459
460 if !is_simple && executed && harness && !nocapture {
461 drop(ws.gctx().shell().note(
462 "test exited abnormally; to see the full output pass --nocapture to the harness.",
463 ));
464 }
465}