1use std::ffi::OsString;
2
3use crate::core::compiler::{Compilation, Doctest};
4use crate::core::shell::Verbosity;
5use crate::core::Workspace;
6use crate::ops;
7use crate::util::errors::CargoResult;
8use crate::util::{CargoTestError, Config, ProcessError, Test};
9
10pub struct TestOptions {
11 pub compile_opts: ops::CompileOptions,
12 pub no_run: bool,
13 pub no_fail_fast: bool,
14}
15
16pub 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 !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.config(), 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
43pub 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
65fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<Compilation<'a>> {
66 let mut compilation = ops::compile(ws, &options.compile_opts)?;
67 compilation
68 .tests
69 .sort_by(|a, b| (a.0.package_id(), &a.1, &a.2).cmp(&(b.0.package_id(), &b.1, &b.2)));
70 Ok(compilation)
71}
72
73fn run_unit_tests(
75 config: &Config,
76 options: &TestOptions,
77 test_args: &[&str],
78 compilation: &Compilation<'_>,
79) -> CargoResult<(Test, Vec<ProcessError>)> {
80 let cwd = config.cwd();
81
82 let mut errors = Vec::new();
83
84 for &(ref pkg, ref target, ref exe) in &compilation.tests {
85 let kind = target.kind();
86 let test = target.name().to_string();
87 let exe_display = exe.strip_prefix(cwd).unwrap_or(exe).display();
88 let mut cmd = compilation.target_process(exe, pkg)?;
89 cmd.args(test_args);
90 if target.harness() && config.shell().verbosity() == Verbosity::Quiet {
91 cmd.arg("--quiet");
92 }
93 config
94 .shell()
95 .concise(|shell| shell.status("Running", &exe_display))?;
96 config
97 .shell()
98 .verbose(|shell| shell.status("Running", &cmd))?;
99
100 let result = cmd.exec();
101
102 match result {
103 Err(e) => {
104 let e = e.downcast::<ProcessError>()?;
105 errors.push((kind.clone(), test.clone(), pkg.name().to_string(), e));
106 if !options.no_fail_fast {
107 break;
108 }
109 }
110 Ok(()) => {}
111 }
112 }
113
114 if errors.len() == 1 {
115 let (kind, name, pkg_name, e) = errors.pop().unwrap();
116 Ok((
117 Test::UnitTest {
118 kind,
119 name,
120 pkg_name,
121 },
122 vec![e],
123 ))
124 } else {
125 Ok((
126 Test::Multiple,
127 errors.into_iter().map(|(_, _, _, e)| e).collect(),
128 ))
129 }
130}
131
132fn run_doc_tests(
133 config: &Config,
134 options: &TestOptions,
135 test_args: &[&str],
136 compilation: &Compilation<'_>,
137) -> CargoResult<(Test, Vec<ProcessError>)> {
138 let mut errors = Vec::new();
139
140 let doctest_xcompile = config.cli_unstable().doctest_xcompile;
144 let mut runtool: &Option<(std::path::PathBuf, Vec<String>)> = &None;
145 if doctest_xcompile {
146 runtool = compilation.target_runner();
147 } else if compilation.host != compilation.target {
148 return Ok((Test::Doc, errors));
149 }
150
151 for doctest_info in &compilation.to_doc_test {
152 let Doctest {
153 package,
154 target,
155 args,
156 unstable_opts,
157 } = doctest_info;
158 config.shell().status("Doc-tests", target.name())?;
159 let mut p = compilation.rustdoc_process(package, target)?;
160 p.arg("--test")
161 .arg(target.src_path().path().unwrap())
162 .arg("--crate-name")
163 .arg(&target.crate_name());
164
165 if doctest_xcompile {
166 p.arg("--target").arg(&compilation.target);
167 p.arg("-Zunstable-options");
168 p.arg("--enable-per-target-ignores");
169 }
170
171 if let Some((runtool, runtool_args)) = runtool {
172 p.arg("--runtool").arg(runtool);
173 for arg in runtool_args {
174 p.arg("--runtool-arg").arg(arg);
175 }
176 }
177
178 for &rust_dep in &[&compilation.deps_output] {
179 let mut arg = OsString::from("dependency=");
180 arg.push(rust_dep);
181 p.arg("-L").arg(arg);
182 }
183
184 for native_dep in compilation.native_dirs.iter() {
185 p.arg("-L").arg(native_dep);
186 }
187
188 for &host_rust_dep in &[&compilation.host_deps_output] {
189 let mut arg = OsString::from("dependency=");
190 arg.push(host_rust_dep);
191 p.arg("-L").arg(arg);
192 }
193
194 for arg in test_args {
195 p.arg("--test-args").arg(arg);
196 }
197
198 if let Some(cfgs) = compilation.cfgs.get(&package.package_id()) {
199 for cfg in cfgs.iter() {
200 p.arg("--cfg").arg(cfg);
201 }
202 }
203
204 for arg in args {
205 p.arg(arg);
206 }
207
208 if *unstable_opts {
209 p.arg("-Zunstable-options");
210 }
211
212 if let Some(flags) = compilation.rustdocflags.get(&package.package_id()) {
213 p.args(flags);
214 }
215 config
216 .shell()
217 .verbose(|shell| shell.status("Running", p.to_string()))?;
218 if let Err(e) = p.exec() {
219 let e = e.downcast::<ProcessError>()?;
220 errors.push(e);
221 if !options.no_fail_fast {
222 return Ok((Test::Doc, errors));
223 }
224 }
225 }
226 Ok((Test::Doc, errors))
227}