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