Skip to main content

cargo/ops/
cargo_test.rs

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 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.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
73/// Runs the unit and integration tests of a package.
74fn 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    // The unstable doctest-xcompile feature enables both per-target-ignores and
141    // cross-compiling doctests. As a side effect, this feature also gates running
142    // doctests with runtools when target == host.
143    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}