1 use crate::core::compiler::{Compilation, CompileKind, Doctest, UnitOutput};
2 use crate::core::shell::Verbosity;
3 use crate::core::{TargetKind, Workspace};
4 use crate::ops;
5 use crate::util::errors::CargoResult;
6 use crate::util::{add_path_args, CargoTestError, Config, Test};
7 use cargo_util::ProcessError;
8 use std::ffi::OsString;
9
10 pub struct TestOptions {
11 pub compile_opts: ops::CompileOptions,
12 pub no_run: bool,
13 pub no_fail_fast: bool,
14 }
15
run_tests( ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str], ) -> CargoResult<Option<CargoTestError>>16 pub 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 we have an error and want to fail fast, then return.
29 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, 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
run_benches( ws: &Workspace<'_>, options: &TestOptions, args: &[&str], ) -> CargoResult<Option<CargoTestError>>43 pub 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
compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<Compilation<'a>>65 fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult<Compilation<'a>> {
66 let mut compilation = ops::compile(ws, &options.compile_opts)?;
67 compilation.tests.sort();
68 Ok(compilation)
69 }
70
71 /// Runs the unit and integration tests of a package.
run_unit_tests( config: &Config, options: &TestOptions, test_args: &[&str], compilation: &Compilation<'_>, ) -> CargoResult<(Test, Vec<ProcessError>)>72 fn run_unit_tests(
73 config: &Config,
74 options: &TestOptions,
75 test_args: &[&str],
76 compilation: &Compilation<'_>,
77 ) -> CargoResult<(Test, Vec<ProcessError>)> {
78 let cwd = config.cwd();
79 let mut errors = Vec::new();
80
81 for UnitOutput {
82 unit,
83 path,
84 script_meta,
85 } in compilation.tests.iter()
86 {
87 let test_path = unit.target.src_path().path().unwrap();
88 let exe_display = if let TargetKind::Test = unit.target.kind() {
89 format!(
90 "{} ({})",
91 test_path
92 .strip_prefix(unit.pkg.root())
93 .unwrap_or(test_path)
94 .display(),
95 path.strip_prefix(cwd).unwrap_or(path).display()
96 )
97 } else {
98 format!(
99 "unittests ({})",
100 path.strip_prefix(cwd).unwrap_or(path).display()
101 )
102 };
103
104 let mut cmd = compilation.target_process(path, unit.kind, &unit.pkg, *script_meta)?;
105 cmd.args(test_args);
106 if unit.target.harness() && config.shell().verbosity() == Verbosity::Quiet {
107 cmd.arg("--quiet");
108 }
109 config
110 .shell()
111 .concise(|shell| shell.status("Running", &exe_display))?;
112 config
113 .shell()
114 .verbose(|shell| shell.status("Running", &cmd))?;
115
116 let result = cmd.exec();
117
118 if let Err(e) = result {
119 let e = e.downcast::<ProcessError>()?;
120 errors.push((
121 unit.target.kind().clone(),
122 unit.target.name().to_string(),
123 unit.pkg.name().to_string(),
124 e,
125 ));
126 if !options.no_fail_fast {
127 break;
128 }
129 }
130 }
131
132 if errors.len() == 1 {
133 let (kind, name, pkg_name, e) = errors.pop().unwrap();
134 Ok((
135 Test::UnitTest {
136 kind,
137 name,
138 pkg_name,
139 },
140 vec![e],
141 ))
142 } else {
143 Ok((
144 Test::Multiple,
145 errors.into_iter().map(|(_, _, _, e)| e).collect(),
146 ))
147 }
148 }
149
run_doc_tests( ws: &Workspace<'_>, options: &TestOptions, test_args: &[&str], compilation: &Compilation<'_>, ) -> CargoResult<(Test, Vec<ProcessError>)>150 fn run_doc_tests(
151 ws: &Workspace<'_>,
152 options: &TestOptions,
153 test_args: &[&str],
154 compilation: &Compilation<'_>,
155 ) -> CargoResult<(Test, Vec<ProcessError>)> {
156 let config = ws.config();
157 let mut errors = Vec::new();
158 let doctest_xcompile = config.cli_unstable().doctest_xcompile;
159 let doctest_in_workspace = config.cli_unstable().doctest_in_workspace;
160
161 for doctest_info in &compilation.to_doc_test {
162 let Doctest {
163 args,
164 unstable_opts,
165 unit,
166 linker,
167 script_meta,
168 } = doctest_info;
169
170 if !doctest_xcompile {
171 match unit.kind {
172 CompileKind::Host => {}
173 CompileKind::Target(target) => {
174 if target.short_name() != compilation.host {
175 // Skip doctests, -Zdoctest-xcompile not enabled.
176 continue;
177 }
178 }
179 }
180 }
181
182 config.shell().status("Doc-tests", unit.target.name())?;
183 let mut p = compilation.rustdoc_process(unit, *script_meta)?;
184 p.arg("--crate-name").arg(&unit.target.crate_name());
185 p.arg("--test");
186
187 if doctest_in_workspace {
188 add_path_args(ws, unit, &mut p);
189 // FIXME(swatinem): remove the `unstable-options` once rustdoc stabilizes the `test-run-directory` option
190 p.arg("-Z").arg("unstable-options");
191 p.arg("--test-run-directory")
192 .arg(unit.pkg.root().to_path_buf());
193 } else {
194 p.arg(unit.target.src_path().path().unwrap());
195 }
196
197 if doctest_xcompile {
198 if let CompileKind::Target(target) = unit.kind {
199 // use `rustc_target()` to properly handle JSON target paths
200 p.arg("--target").arg(target.rustc_target());
201 }
202 p.arg("-Zunstable-options");
203 p.arg("--enable-per-target-ignores");
204 if let Some((runtool, runtool_args)) = compilation.target_runner(unit.kind) {
205 p.arg("--runtool").arg(runtool);
206 for arg in runtool_args {
207 p.arg("--runtool-arg").arg(arg);
208 }
209 }
210 if let Some(linker) = linker {
211 let mut joined = OsString::from("linker=");
212 joined.push(linker);
213 p.arg("-C").arg(joined);
214 }
215 }
216
217 for &rust_dep in &[
218 &compilation.deps_output[&unit.kind],
219 &compilation.deps_output[&CompileKind::Host],
220 ] {
221 let mut arg = OsString::from("dependency=");
222 arg.push(rust_dep);
223 p.arg("-L").arg(arg);
224 }
225
226 for native_dep in compilation.native_dirs.iter() {
227 p.arg("-L").arg(native_dep);
228 }
229
230 for arg in test_args {
231 p.arg("--test-args").arg(arg);
232 }
233
234 if config.shell().verbosity() == Verbosity::Quiet {
235 p.arg("--test-args").arg("--quiet");
236 }
237
238 p.args(args);
239
240 if *unstable_opts {
241 p.arg("-Zunstable-options");
242 }
243
244 config
245 .shell()
246 .verbose(|shell| shell.status("Running", p.to_string()))?;
247 if let Err(e) = p.exec() {
248 let e = e.downcast::<ProcessError>()?;
249 errors.push(e);
250 if !options.no_fail_fast {
251 return Ok((Test::Doc, errors));
252 }
253 }
254 }
255 Ok((Test::Doc, errors))
256 }
257