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 color = gctx.shell().color_choice();
180
181 for doctest_info in &compilation.to_doc_test {
182 let Doctest {
183 args,
184 unstable_opts,
185 unit,
186 linker,
187 script_meta,
188 env,
189 } = doctest_info;
190
191 gctx.shell().status("Doc-tests", unit.target.name())?;
192 let mut p = compilation.rustdoc_process(unit, *script_meta)?;
193
194 for (var, value) in env {
195 p.env(var, value);
196 }
197
198 let color_arg = match color {
199 ColorChoice::Always => "always",
200 ColorChoice::Never => "never",
201 ColorChoice::CargoAuto => "auto",
202 };
203 p.arg("--color").arg(color_arg);
204
205 p.arg("--crate-name").arg(&unit.target.crate_name());
206 p.arg("--test");
207
208 add_path_args(ws, unit, &mut p);
209 p.arg("--test-run-directory")
210 .arg(unit.pkg.root().to_path_buf());
211
212 if let CompileKind::Target(target) = unit.kind {
213 p.arg("--target").arg(target.rustc_target());
215 }
216
217 if let Some((runtool, runtool_args)) = compilation.target_runner(unit.kind) {
218 p.arg("--test-runtool").arg(runtool);
219 for arg in runtool_args {
220 p.arg("--test-runtool-arg").arg(arg);
221 }
222 }
223 if let Some(linker) = linker {
224 let mut joined = OsString::from("linker=");
225 joined.push(linker);
226 p.arg("-C").arg(joined);
227 }
228
229 if unit.profile.panic != PanicStrategy::Unwind {
230 p.arg("-C").arg(format!("panic={}", unit.profile.panic));
231 }
232
233 for &rust_dep in &[
234 &compilation.deps_output[&unit.kind],
235 &compilation.deps_output[&CompileKind::Host],
236 ] {
237 let mut arg = OsString::from("dependency=");
238 arg.push(rust_dep);
239 p.arg("-L").arg(arg);
240 }
241
242 for native_dep in compilation.native_dirs.iter() {
243 p.arg("-L").arg(native_dep);
244 }
245
246 for arg in test_args {
247 p.arg("--test-args").arg(arg);
248 }
249
250 if gctx.shell().verbosity() == Verbosity::Quiet {
251 p.arg("--test-args").arg("--quiet");
252 }
253
254 p.args(unit.pkg.manifest().lint_rustflags());
255
256 p.args(args);
257
258 if *unstable_opts {
259 p.arg("-Zunstable-options");
260 }
261
262 if gctx.extra_verbose() {
263 p.display_env_vars();
264 }
265
266 gctx.shell()
267 .verbose(|shell| shell.status("Running", p.to_string()))?;
268
269 if let Err(e) = p.exec() {
270 let code = fail_fast_code(&e);
271 let unit_err = UnitTestError {
272 unit: unit.clone(),
273 kind: TestKind::Doctest,
274 };
275 report_test_error(ws, test_args, &options.compile_opts, &unit_err, e);
276 errors.push(unit_err);
277 if !options.no_fail_fast {
278 return Err(CliError::code(code));
279 }
280 }
281 }
282 Ok(errors)
283}
284
285fn display_no_run_information(
289 ws: &Workspace<'_>,
290 test_args: &[&str],
291 compilation: &Compilation<'_>,
292 exec_type: &str,
293) -> CargoResult<()> {
294 let gctx = ws.gctx();
295 let cwd = gctx.cwd();
296 for UnitOutput {
297 unit,
298 path,
299 script_meta,
300 } in compilation.tests.iter()
301 {
302 let (exe_display, cmd) = cmd_builds(
303 gctx,
304 cwd,
305 unit,
306 path,
307 script_meta,
308 test_args,
309 compilation,
310 exec_type,
311 )?;
312 gctx.shell()
313 .concise(|shell| shell.status("Executable", &exe_display))?;
314 gctx.shell()
315 .verbose(|shell| shell.status("Executable", &cmd))?;
316 }
317
318 return Ok(());
319}
320
321fn cmd_builds(
327 gctx: &GlobalContext,
328 cwd: &Path,
329 unit: &Unit,
330 path: &PathBuf,
331 script_meta: &Option<UnitHash>,
332 test_args: &[&str],
333 compilation: &Compilation<'_>,
334 exec_type: &str,
335) -> CargoResult<(String, ProcessBuilder)> {
336 let test_path = unit.target.src_path().path().unwrap();
337 let short_test_path = test_path
338 .strip_prefix(unit.pkg.root())
339 .unwrap_or(test_path)
340 .display();
341
342 let exe_display = match unit.target.kind() {
343 TargetKind::Test | TargetKind::Bench => format!(
344 "{} ({})",
345 short_test_path,
346 path.strip_prefix(cwd).unwrap_or(path).display()
347 ),
348 _ => format!(
349 "{} {} ({})",
350 exec_type,
351 short_test_path,
352 path.strip_prefix(cwd).unwrap_or(path).display()
353 ),
354 };
355
356 let mut cmd = compilation.target_process(path, unit.kind, &unit.pkg, *script_meta)?;
357 cmd.args(test_args);
358 if unit.target.harness() && gctx.shell().verbosity() == Verbosity::Quiet {
359 cmd.arg("--quiet");
360 }
361
362 Ok((exe_display, cmd))
363}
364
365fn fail_fast_code(error: &anyhow::Error) -> i32 {
374 if let Some(proc_err) = error.downcast_ref::<ProcessError>() {
375 if let Some(code) = proc_err.code {
376 return code;
377 }
378 }
379 101
380}
381
382fn no_fail_fast_err(
385 ws: &Workspace<'_>,
386 opts: &ops::CompileOptions,
387 errors: &[UnitTestError],
388) -> CliResult {
389 let args: Vec<_> = errors
391 .iter()
392 .map(|unit_err| format!(" `{}`", unit_err.cli_args(ws, opts)))
393 .collect();
394 let message = match errors.len() {
395 0 => return Ok(()),
396 1 => format!("1 target failed:\n{}", args.join("\n")),
397 n => format!("{n} targets failed:\n{}", args.join("\n")),
398 };
399 Err(anyhow::Error::msg(message).into())
400}
401
402fn report_test_error(
404 ws: &Workspace<'_>,
405 test_args: &[&str],
406 opts: &ops::CompileOptions,
407 unit_err: &UnitTestError,
408 test_error: anyhow::Error,
409) {
410 let which = match unit_err.kind {
411 TestKind::Test => "test failed",
412 TestKind::Bench => "bench failed",
413 TestKind::Doctest => "doctest failed",
414 };
415
416 let mut err = format_err!("{}, to rerun pass `{}`", which, unit_err.cli_args(ws, opts));
417 let (is_simple, executed) = test_error
420 .downcast_ref::<ProcessError>()
421 .and_then(|proc_err| proc_err.code)
422 .map_or((false, false), |code| (code == 101, true));
423
424 if !is_simple {
425 err = test_error.context(err);
426 }
427
428 crate::display_error(&err, &mut ws.gctx().shell());
429
430 let harness: bool = unit_err.unit.target.harness();
431 let nocapture: bool = test_args.contains(&"--nocapture");
432
433 if !is_simple && executed && harness && !nocapture {
434 drop(ws.gctx().shell().note(
435 "test exited abnormally; to see the full output pass --nocapture to the harness.",
436 ));
437 }
438}