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::{CliError, CliResult, GlobalContext, add_path_args};
9use anyhow::format_err;
10use cargo_util::{ProcessBuilder, ProcessError};
11use std::collections::HashMap;
12use std::ffi::OsString;
13use std::fmt::Write;
14use std::path::{Path, PathBuf};
15
16pub struct TestOptions {
17 pub compile_opts: ops::CompileOptions,
18 pub no_run: bool,
19 pub no_fail_fast: bool,
20}
21
22#[derive(Copy, Clone)]
27enum TestKind {
28 Test,
29 Bench,
30 Doctest,
31}
32
33struct UnitTestError {
35 unit: Unit,
36 kind: TestKind,
37}
38
39impl UnitTestError {
40 fn cli_args(&self, ws: &Workspace<'_>, opts: &ops::CompileOptions) -> String {
42 let mut args = if opts.spec.needs_spec_flag(ws) {
43 format!("-p {} ", self.unit.pkg.name())
44 } else {
45 String::new()
46 };
47 let mut add = |which| write!(args, "--{which} {}", self.unit.target.name()).unwrap();
48
49 match self.kind {
50 TestKind::Test | TestKind::Bench => match self.unit.target.kind() {
51 TargetKind::Lib(_) => args.push_str("--lib"),
52 TargetKind::Bin => add("bin"),
53 TargetKind::Test => add("test"),
54 TargetKind::Bench => add("bench"),
55 TargetKind::ExampleLib(_) | TargetKind::ExampleBin => add("example"),
56 TargetKind::CustomBuild => panic!("unexpected CustomBuild kind"),
57 },
58 TestKind::Doctest => args.push_str("--doc"),
59 }
60 args
61 }
62}
63
64pub fn run_tests(ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str]) -> CliResult {
69 let compilation = compile_tests(ws, options)?;
70
71 if options.no_run {
72 if !options.compile_opts.build_config.emit_json() {
73 display_no_run_information(ws, test_args, &compilation, "unittests")?;
74 }
75 return Ok(());
76 }
77 let mut errors = run_unit_tests(ws, options, test_args, &compilation, TestKind::Test)?;
78
79 let doctest_errors = run_doc_tests(ws, options, test_args, &compilation)?;
80 errors.extend(doctest_errors);
81 no_fail_fast_err(ws, &options.compile_opts, &errors)
82}
83
84pub fn run_benches(ws: &Workspace<'_>, options: &TestOptions, args: &[&str]) -> CliResult {
89 let compilation = compile_tests(ws, options)?;
90
91 if options.no_run {
92 if !options.compile_opts.build_config.emit_json() {
93 display_no_run_information(ws, args, &compilation, "benches")?;
94 }
95 return Ok(());
96 }
97
98 let mut args = args.to_vec();
99 args.push("--bench");
100
101 let errors = run_unit_tests(ws, options, &args, &compilation, TestKind::Bench)?;
102 no_fail_fast_err(ws, &options.compile_opts, &errors)
103}
104
105fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<Compilation<'a>> {
106 let mut compilation = ops::compile(ws, &options.compile_opts)?;
107 compilation.tests.sort_by_key(|u| u.unit.clone());
108 Ok(compilation)
109}
110
111fn run_unit_tests(
116 ws: &Workspace<'_>,
117 options: &TestOptions,
118 test_args: &[&str],
119 compilation: &Compilation<'_>,
120 test_kind: TestKind,
121) -> Result<Vec<UnitTestError>, CliError> {
122 let gctx = ws.gctx();
123 let cwd = gctx.cwd();
124 let mut errors = Vec::new();
125
126 for UnitOutput {
127 unit,
128 path,
129 script_metas,
130 env,
131 } in compilation.tests.iter()
132 {
133 let (exe_display, mut cmd) = cmd_builds(
134 gctx,
135 cwd,
136 unit,
137 path,
138 script_metas.as_ref(),
139 env,
140 test_args,
141 compilation,
142 "unittests",
143 )?;
144
145 if gctx.extra_verbose() {
146 cmd.display_env_vars();
147 }
148
149 gctx.shell()
150 .concise(|shell| shell.status("Running", &exe_display))?;
151 gctx.shell()
152 .verbose(|shell| shell.status("Running", &cmd))?;
153
154 if let Err(e) = cmd.exec() {
155 let code = fail_fast_code(&e);
156 let unit_err = UnitTestError {
157 unit: unit.clone(),
158 kind: test_kind,
159 };
160 report_test_error(ws, test_args, &options.compile_opts, &unit_err, e);
161 errors.push(unit_err);
162 if !options.no_fail_fast {
163 return Err(CliError::code(code));
164 }
165 }
166 }
167 Ok(errors)
168}
169
170fn run_doc_tests(
175 ws: &Workspace<'_>,
176 options: &TestOptions,
177 test_args: &[&str],
178 compilation: &Compilation<'_>,
179) -> Result<Vec<UnitTestError>, CliError> {
180 let gctx = ws.gctx();
181 let mut errors = Vec::new();
182 let color = gctx.shell().color_choice();
183
184 for doctest_info in &compilation.to_doc_test {
185 let Doctest {
186 args,
187 unstable_opts,
188 unit,
189 linker,
190 script_metas,
191 env,
192 } = doctest_info;
193
194 gctx.shell().status("Doc-tests", unit.target.name())?;
195 let mut p = compilation.rustdoc_process(unit, script_metas.as_ref())?;
196
197 for (var, value) in env {
198 p.env(var, value);
199 }
200
201 let color_arg = match color {
202 ColorChoice::Always => "always",
203 ColorChoice::Never => "never",
204 ColorChoice::CargoAuto => "auto",
205 };
206 p.arg("--color").arg(color_arg);
207
208 p.arg("--crate-name").arg(&unit.target.crate_name());
209 p.arg("--test");
210
211 add_path_args(ws, unit, &mut p);
212 p.arg("--test-run-directory").arg(unit.pkg.root());
213
214 if let CompileKind::Target(target) = unit.kind {
215 p.arg("--target").arg(target.rustc_target());
217 }
218
219 if let Some((runtool, runtool_args)) = compilation.target_runner(unit.kind) {
220 p.arg("--test-runtool").arg(runtool);
221 for arg in runtool_args {
222 p.arg("--test-runtool-arg").arg(arg);
223 }
224 }
225 if let Some(linker) = linker {
226 let mut joined = OsString::from("linker=");
227 joined.push(linker);
228 p.arg("-C").arg(joined);
229 }
230
231 if unit.profile.panic != PanicStrategy::Unwind {
232 p.arg("-C").arg(format!("panic={}", unit.profile.panic));
233 }
234
235 for native_dep in compilation.native_dirs.iter() {
236 p.arg("-L").arg(native_dep);
237 }
238
239 for arg in test_args {
240 p.arg("--test-args").arg(arg);
241 }
242
243 if gctx.shell().verbosity() == Verbosity::Quiet {
244 p.arg("--test-args").arg("--quiet");
245 }
246
247 p.args(unit.pkg.manifest().lint_rustflags());
248
249 p.args(args);
250
251 if *unstable_opts {
252 p.arg("-Zunstable-options");
253 }
254
255 if gctx.extra_verbose() {
256 p.display_env_vars();
257 }
258
259 gctx.shell()
260 .verbose(|shell| shell.status("Running", p.to_string()))?;
261
262 if let Err(e) = p.exec() {
263 let code = fail_fast_code(&e);
264 let unit_err = UnitTestError {
265 unit: unit.clone(),
266 kind: TestKind::Doctest,
267 };
268 report_test_error(ws, test_args, &options.compile_opts, &unit_err, e);
269 errors.push(unit_err);
270 if !options.no_fail_fast {
271 return Err(CliError::code(code));
272 }
273 }
274 }
275 Ok(errors)
276}
277
278fn display_no_run_information(
282 ws: &Workspace<'_>,
283 test_args: &[&str],
284 compilation: &Compilation<'_>,
285 exec_type: &str,
286) -> CargoResult<()> {
287 let gctx = ws.gctx();
288 let cwd = gctx.cwd();
289 for UnitOutput {
290 unit,
291 path,
292 script_metas,
293 env,
294 } in compilation.tests.iter()
295 {
296 let (exe_display, cmd) = cmd_builds(
297 gctx,
298 cwd,
299 unit,
300 path,
301 script_metas.as_ref(),
302 env,
303 test_args,
304 compilation,
305 exec_type,
306 )?;
307 gctx.shell()
308 .concise(|shell| shell.status("Executable", &exe_display))?;
309 gctx.shell()
310 .verbose(|shell| shell.status("Executable", &cmd))?;
311 }
312
313 return Ok(());
314}
315
316fn cmd_builds(
322 gctx: &GlobalContext,
323 cwd: &Path,
324 unit: &Unit,
325 path: &PathBuf,
326 script_metas: Option<&Vec<UnitHash>>,
327 env: &HashMap<String, OsString>,
328 test_args: &[&str],
329 compilation: &Compilation<'_>,
330 exec_type: &str,
331) -> CargoResult<(String, ProcessBuilder)> {
332 let test_path = unit.target.src_path().path().unwrap();
333 let short_test_path = test_path
334 .strip_prefix(unit.pkg.root())
335 .unwrap_or(test_path)
336 .display();
337
338 let exe_display = match unit.target.kind() {
339 TargetKind::Test | TargetKind::Bench => format!(
340 "{} ({})",
341 short_test_path,
342 path.strip_prefix(cwd).unwrap_or(path).display()
343 ),
344 _ => format!(
345 "{} {} ({})",
346 exec_type,
347 short_test_path,
348 path.strip_prefix(cwd).unwrap_or(path).display()
349 ),
350 };
351
352 let mut cmd = compilation.target_process(path, unit.kind, &unit.pkg, script_metas)?;
353 cmd.args(test_args);
354 if unit.target.harness() && gctx.shell().verbosity() == Verbosity::Quiet {
355 cmd.arg("--quiet");
356 }
357 for (key, val) in env.iter() {
358 cmd.env(key, val);
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") || test_args.contains(&"--no-capture");
431
432 if !is_simple && executed && harness && !nocapture {
433 drop(ws.gctx().shell().note(
434 "test exited abnormally; to see the full output pass --no-capture to the harness.",
435 ));
436 }
437}